Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Alessandro Cinelli (cirpo) 
Don’t screw it up! 
how to build durable apis
How to build 
durable 
web APIs
1. Can you predict 
the future?
Dubai Marina ~2000
Dubai Marina 2014
CAN YOU REALLY PREDICT THE FUTURE?
If there’s one thing we learned over the 
past 5 years of development...
Monoliths are disappearing
FULL STACK IS DEAD! 
Microservice Architecture, [...] a 
particular way of designing software 
applications as suites of i...
FULL STACK IS DEAD! 
SERVICE-ORIENTED 
ARCHITECTURES 
Microservice Architecture, [...] a 
particular way of designing soft...
LEGO, something new in a geek talk…
FROM 
a single page application written in
TO 
an hybrid solution
IN TWO WEEKS!!!
HOW?
APIs written in PHP <3
EVERYONE WANTS API
EVERYDAY SERVICES
DEV-ORIENTED SERVICES
API MANIACS
2. HTTP IS HERE 
TO STAY
GET vs POST 
“The difference is that in 
a GET request you have the parameters in 
the url , 
with a POST the parameters a...
GET vs POST
HTTP FUNDAMENTALS
HTTP FUNDAMENTALS 
GET POST
HTTP FUNDAMENTALS 
PATCH DELETE 
GET POST 
PUT HEAD 
OPTIONS
HTTP FUNDAMENTALS 
HEADERS
HTTP FUNDAMENTALS 
Cache-Control 
Cookie Accept-Language 
User-Agent Origin 
HEADERS 
Accept 
Accept-Encoding 
Content-Typ...
HTTP FUNDAMENTALS 
CUSTOM HEADERS
HTTP FUNDAMENTALS 
CUSTOM HEADERS 
N-Location 
N-Locale 
N-Device 
N-Platform 
N-App 
N-Theme
WAKA 
“A new protocol designed to match 
the efficiency of well-designed 
Web Applications” 
http://tools.ietf.org/agenda/...
SPDY/1…3 
A protocol “invented” by Google, which supports: 
extended compression 
multiplexing 
prioritization 
server pus...
SPDY/1…3 
A protocol “invented” by Google, which supports: 
extended compression 
multiplexing 
prioritization 
server pus...
SPDY/1…3 
A protocol “invented” by Google, which supports: 
extended compression 
multiplexing 
prioritization 
server pus...
SPDY/1…3 
A protocol “invented” by Google, which supports: 
extended compression 
multiplexing 
prioritization 
server pus...
HTTP/2.0
HTTP/2.0 
BASED ON SPDY
HTTP/2.0 
WHICH IS A FASTER VERSION OF HTTPS
HTTP/2.0 
WHICH IS A SAFER VERSION OF HTTP
HTTP is definitely here to stay, 
semantics won’t change
3. PLAN FOR 
FAILURE
WORK AROUND BUGS
WORK AROUND BUGS
FAILOVER 
HTTP/1.1 200 OK 
Date: Fri, 25 Apr 2014 16:52:37 GMT 
Content-Type: application/json 
Transfer-Encoding: chunked...
FAILOVER 
HTTP/1.1 200 OK 
Date: Fri, 25 Apr 2014 16:52:37 GMT 
Content-Type: application/json 
Transfer-Encoding: chunked...
DESIGN MISTAKES?
VERSIONING TO THE RESCUE
VERSIONING TO THE RESCUE
VERSIONING TO THE RESCUE
VERSIONING TO THE RESCUE
How to detect the version?
http://api.example.com/v1/… 
How to detect the version?
SIMPLE 
How to detect the version?
BUT HOW TO DETECT IT? 
How to detect the version?
DETECTING THE VERSION
DETECTING THE VERSION 
Here it belongs to 
the route/controller, you need 
it at the 
Request level
USE A HEADER! 
How to detect the version?
DETECTING THE VERSION
LET NGINX DO THE JOB
LET NGINX DO THE JOB 
$req->getHeader(‘API-Version)
LET NGINX DO THE JOB 
api.example.com/v1/customers
LET NGINX DO THE JOB 
api.example.com/customers 
API-Version: 1
LET NGINX DO THE JOB 
Without polluting 
your router or controller class
“I beg to differ”
“I beg to differ” 
URL, subdomain, 
media type, header...
“I beg to differ” 
Picking a wrong 
implementation 
doesn’t matter
“I beg to differ” 
AT ALL.
“I beg to differ” 
How it impacts the 
design of your 
software matters
4. BE PRAGMATIC
GET or POST?
GET or POST? 
api. example.com/login/?username=cirpo&login=wunderbar
5.TESTING
cURL is your best friend 
curl -X GET https://api.example.com/products 
curl -X POST https://api.example com/order -data=”...
cURL is your best friend
PHP 
cuzzle 
cURL command 
from Guzzle requests 
github.com/namshi/cuzzle
cURL is your best friend
cURL is your best friend
cURL is your best friend
HTTPARTY
HTTPIE
Javascript 
mockserver 
mock your APIs 
in a matter of seconds! 
github.com/namshi/mockserver
Javascript 
mockserver
Javascript 
users_GET.mock file 
mockserver
Javascript 
mockserver
TESTING APIS
Android 2.3 native browser…
TESTING APIS
TESTING APIS 
you can even 
decrypt the https responses :)
Javascript 
shisha 
smoke tests made easy! 
github.com/namshi/shish
Javascript 
shisha 
.smoke file
Javascript 
shisha
6. DESIGN
An API is a layer on 
top of your domain
Pick the layer that 
is most suitable 
to your needs
HTTP APIs are a 
good start
REST IS A DREAM!
HTTP METHOD 
POST or PUT?
HTTP METHOD 
PUT or PATCH?
USER TAGS 
/users/johnny/tags
USER TAGS 
deleting a non-existent tag: 
200, 204 or 404?
USER TAGS 
ON STACKOVERFLOW 
THEY’RE 
deleting a non-existent tag: 
STILL FIGHTING 
200, 204 or 404? 
http://stackoverflow...
be consistent
NAMING 
/user/1 
/users 
/order/1 
/orders
NAMING 
/city/1 
/cities 
/curriculum/1 
/curricula
NAMING 
/user/1 
/users 
/order/1 
/orders 
/city/1 
/cities 
/curriculum/1 
/curricula
NAMING 
/user/1 
/users 
/order/1 
/orders 
/city/1 
/cities 
/curriculum/1 
/curricula 
NOT GOD AT ALL!
STICK WITH PLURALS!
NAMING 
/users1 
/users 
/orders/1 
/orders
NAMING 
/cities/1 
/cities 
/curricula/1 
/curricula
NAMING 
/users/1 
/users 
/orders/1 
/orders 
/cities/1 
/cities 
/curricula/1 
/curricula
UNIQUE RESOURCES 
/users/1 
/users/cirpo 
/users/A323K833
UNIQUE RESOURCES 
/orders/15 
/orders/A323K833
UNIQUE RESOURCES 
/orders/15 
AVOID INCREMENTAL 
NUMBERS 
/orders/A323K833 
IF IT’S BUSINESS CRITICAL
Unstructured APIs 
= 
API aggregation
api.example.org/v1/latest-news
latest news + 
metatags + 
banners + 
navigation 
yada yada yada
Sort of a “wild” API 
for your whole app
The client receives a GET 
on /something 
and will let the 
API figure out 
what /u/something 
actually is
Orchestration Layers
https://engineering.groupon.com/2013/misc/i-tier-dismantling-the-monoliths/
“Most APIs are designed by the API 
provider with the goal of maintaining data 
model purity. When building an OL, be 
pre...
DOMAIN 
users 
orders 
stock 
images
DOMAIN 
Think about collections 
not 
controllers
DOMAIN 
PUT/PATCH 
try to always plan for full updates
UNIFORM RESPONSES
CODEBASE ORGANIZATION
CODEBASE ORGANIZATION 
one bundle for each api? 
one bundle for each application? 
one app for each sets of api?
CODEBASE ORGANIZATION 
start with an app 
organize bundles semantically 
create shared bundles
CODEBASE ORGANIZATION 
BUNDLES 
product 
checkout 
warehouse 
generic 
entity
CODEBASE ORGANIZATION 
APP 
product 
BUNDLES 
checkout 
warehouse 
generic 
entity
7. SCALABILITY
CACHE ALL THE THINGS!
MIDDLEWARE
MIDDLEWARE - CONNECT
MIDDLEWARE - STACK
AVOID SESSIONS
EVERYTHING AS A RESOURCE
8. ISSUES
CORS
CORS
(silly) browsers
if a cross-domain 
request is cacheable, 
the android browser 
goes nuts 
(silly) browsers
The request does 
not include 
the Origin header 
(silly) browsers
Status code: 0 
(silly) browsers
WHAT. THE. HECK. 
http://opensourcehacker.com/2011/03/20/android-webkit-xhr-status-code-0-and-expires-headers/ 
(silly) br...
“standards”
Don’t play with fire
1 API, N clients 
consuming it 
Don’t play with fire
desktop browser, 
mobile browser, 
ios app, android app... 
Don’t play with fire
Keep as much logic 
as possible on the 
server 
Don’t play with fire
Less things to 
implement on every 
client and centralized 
implementations 
Don’t play with fire
make it easy for the 
API clients 
Don’t play with fire
POST https://api.example.com/login 
200 OK 
date: Thu, 01 May 2014 21:52:33 GMT 
content-type: application/json 
transfer-...
Security matters
[ 
"odino@gmail.com", 
"cirpo@gmail.com", 
... 
] 
Security matters
for(;;);[ 
"odino@gmail.com", 
"cirpo@gmail.com", 
... 
] 
Security matters
while(1);[ 
"odino@gmail.com", 
"cirpo@gmail.com", 
... 
] 
Security matters
while(1);[ 
"odino@gmail.com", 
"cirpo@gmail.com", 
... 
] 
Security matters
Avoid [...] 
http://bit.ly/json-hijacking 
Security matters
USE {…} 
Security matters
That’s all folks
/ 
github.com/cirpo 
@cirpo
tech.namshi.com/join-us 
we are hiring! 
tech.namshi.com 
github.com/namshi 
twitter.com/TechNamshi
THANKS
CREDITS 
http://www.panoramio.com/photo/30329016 
https://farm3.staticflickr.com/2199/2365883747_3a5c753719_o.jpg 
http://...
Don't screw it up! How to build durable API
Don't screw it up! How to build durable API
Don't screw it up! How to build durable API
Don't screw it up! How to build durable API
Upcoming SlideShare
Loading in …5
×

Don't screw it up! How to build durable API

2,186 views

Published on

This is a talk I gave at IPC 2014 in Munich.
It's about how to build durable web apis based on the experience gained at Namshi while we were developing our SOA architecture

Published in: Internet
  • Be the first to comment

Don't screw it up! How to build durable API

  1. 1. Alessandro Cinelli (cirpo) Don’t screw it up! how to build durable apis
  2. 2. How to build durable web APIs
  3. 3. 1. Can you predict the future?
  4. 4. Dubai Marina ~2000
  5. 5. Dubai Marina 2014
  6. 6. CAN YOU REALLY PREDICT THE FUTURE?
  7. 7. If there’s one thing we learned over the past 5 years of development...
  8. 8. Monoliths are disappearing
  9. 9. FULL STACK IS DEAD! Microservice Architecture, [...] a particular way of designing software applications as suites of independently deployable services http://martinfowler.com/articles/microservices.html
  10. 10. FULL STACK IS DEAD! SERVICE-ORIENTED ARCHITECTURES Microservice Architecture, [...] a particular way of designing software applications as suites of independently deployable services http://martinfowler.com/articles/microservices.html
  11. 11. LEGO, something new in a geek talk…
  12. 12. FROM a single page application written in
  13. 13. TO an hybrid solution
  14. 14. IN TWO WEEKS!!!
  15. 15. HOW?
  16. 16. APIs written in PHP <3
  17. 17. EVERYONE WANTS API
  18. 18. EVERYDAY SERVICES
  19. 19. DEV-ORIENTED SERVICES
  20. 20. API MANIACS
  21. 21. 2. HTTP IS HERE TO STAY
  22. 22. GET vs POST “The difference is that in a GET request you have the parameters in the url , with a POST the parameters are in the request’s body”
  23. 23. GET vs POST
  24. 24. HTTP FUNDAMENTALS
  25. 25. HTTP FUNDAMENTALS GET POST
  26. 26. HTTP FUNDAMENTALS PATCH DELETE GET POST PUT HEAD OPTIONS
  27. 27. HTTP FUNDAMENTALS HEADERS
  28. 28. HTTP FUNDAMENTALS Cache-Control Cookie Accept-Language User-Agent Origin HEADERS Accept Accept-Encoding Content-Type Referer If-Modified-Since If-None-Match
  29. 29. HTTP FUNDAMENTALS CUSTOM HEADERS
  30. 30. HTTP FUNDAMENTALS CUSTOM HEADERS N-Location N-Locale N-Device N-Platform N-App N-Theme
  31. 31. WAKA “A new protocol designed to match the efficiency of well-designed Web Applications” http://tools.ietf.org/agenda/83/slides/slides-83-httpbis-5.pdf
  32. 32. SPDY/1…3 A protocol “invented” by Google, which supports: extended compression multiplexing prioritization server push
  33. 33. SPDY/1…3 A protocol “invented” by Google, which supports: extended compression multiplexing prioritization server push
  34. 34. SPDY/1…3 A protocol “invented” by Google, which supports: extended compression multiplexing prioritization server push
  35. 35. SPDY/1…3 A protocol “invented” by Google, which supports: extended compression multiplexing prioritization server push
  36. 36. HTTP/2.0
  37. 37. HTTP/2.0 BASED ON SPDY
  38. 38. HTTP/2.0 WHICH IS A FASTER VERSION OF HTTPS
  39. 39. HTTP/2.0 WHICH IS A SAFER VERSION OF HTTP
  40. 40. HTTP is definitely here to stay, semantics won’t change
  41. 41. 3. PLAN FOR FAILURE
  42. 42. WORK AROUND BUGS
  43. 43. WORK AROUND BUGS
  44. 44. FAILOVER HTTP/1.1 200 OK Date: Fri, 25 Apr 2014 16:52:37 GMT Content-Type: application/json Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Encoding Cache-Control: stale-if-error=3600, stale-while-revalidate=6000 Age: 0 Via: 1.1 varnish X-Cache: MISS Alternate-Protocol: 443:npn-spdy/2
  45. 45. FAILOVER HTTP/1.1 200 OK Date: Fri, 25 Apr 2014 16:52:37 GMT Content-Type: application/json Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Encoding Cache-Control: stale-if-error=3600, stale-while-revalidate=6000 Age: CACHE 0 AVAILABLE IF BACKEND IS DOWN Via: 1.1 varnish X-Cache: MISS Alternate-Protocol: 443:npn-spdy/2
  46. 46. DESIGN MISTAKES?
  47. 47. VERSIONING TO THE RESCUE
  48. 48. VERSIONING TO THE RESCUE
  49. 49. VERSIONING TO THE RESCUE
  50. 50. VERSIONING TO THE RESCUE
  51. 51. How to detect the version?
  52. 52. http://api.example.com/v1/… How to detect the version?
  53. 53. SIMPLE How to detect the version?
  54. 54. BUT HOW TO DETECT IT? How to detect the version?
  55. 55. DETECTING THE VERSION
  56. 56. DETECTING THE VERSION Here it belongs to the route/controller, you need it at the Request level
  57. 57. USE A HEADER! How to detect the version?
  58. 58. DETECTING THE VERSION
  59. 59. LET NGINX DO THE JOB
  60. 60. LET NGINX DO THE JOB $req->getHeader(‘API-Version)
  61. 61. LET NGINX DO THE JOB api.example.com/v1/customers
  62. 62. LET NGINX DO THE JOB api.example.com/customers API-Version: 1
  63. 63. LET NGINX DO THE JOB Without polluting your router or controller class
  64. 64. “I beg to differ”
  65. 65. “I beg to differ” URL, subdomain, media type, header...
  66. 66. “I beg to differ” Picking a wrong implementation doesn’t matter
  67. 67. “I beg to differ” AT ALL.
  68. 68. “I beg to differ” How it impacts the design of your software matters
  69. 69. 4. BE PRAGMATIC
  70. 70. GET or POST?
  71. 71. GET or POST? api. example.com/login/?username=cirpo&login=wunderbar
  72. 72. 5.TESTING
  73. 73. cURL is your best friend curl -X GET https://api.example.com/products curl -X POST https://api.example com/order -data=”{...}” curl -X DELETE ... curl -X PATCH ...
  74. 74. cURL is your best friend
  75. 75. PHP cuzzle cURL command from Guzzle requests github.com/namshi/cuzzle
  76. 76. cURL is your best friend
  77. 77. cURL is your best friend
  78. 78. cURL is your best friend
  79. 79. HTTPARTY
  80. 80. HTTPIE
  81. 81. Javascript mockserver mock your APIs in a matter of seconds! github.com/namshi/mockserver
  82. 82. Javascript mockserver
  83. 83. Javascript users_GET.mock file mockserver
  84. 84. Javascript mockserver
  85. 85. TESTING APIS
  86. 86. Android 2.3 native browser…
  87. 87. TESTING APIS
  88. 88. TESTING APIS you can even decrypt the https responses :)
  89. 89. Javascript shisha smoke tests made easy! github.com/namshi/shish
  90. 90. Javascript shisha .smoke file
  91. 91. Javascript shisha
  92. 92. 6. DESIGN
  93. 93. An API is a layer on top of your domain
  94. 94. Pick the layer that is most suitable to your needs
  95. 95. HTTP APIs are a good start
  96. 96. REST IS A DREAM!
  97. 97. HTTP METHOD POST or PUT?
  98. 98. HTTP METHOD PUT or PATCH?
  99. 99. USER TAGS /users/johnny/tags
  100. 100. USER TAGS deleting a non-existent tag: 200, 204 or 404?
  101. 101. USER TAGS ON STACKOVERFLOW THEY’RE deleting a non-existent tag: STILL FIGHTING 200, 204 or 404? http://stackoverflow.com/questions/2342579/http-status-code-for-update- and-delete
  102. 102. be consistent
  103. 103. NAMING /user/1 /users /order/1 /orders
  104. 104. NAMING /city/1 /cities /curriculum/1 /curricula
  105. 105. NAMING /user/1 /users /order/1 /orders /city/1 /cities /curriculum/1 /curricula
  106. 106. NAMING /user/1 /users /order/1 /orders /city/1 /cities /curriculum/1 /curricula NOT GOD AT ALL!
  107. 107. STICK WITH PLURALS!
  108. 108. NAMING /users1 /users /orders/1 /orders
  109. 109. NAMING /cities/1 /cities /curricula/1 /curricula
  110. 110. NAMING /users/1 /users /orders/1 /orders /cities/1 /cities /curricula/1 /curricula
  111. 111. UNIQUE RESOURCES /users/1 /users/cirpo /users/A323K833
  112. 112. UNIQUE RESOURCES /orders/15 /orders/A323K833
  113. 113. UNIQUE RESOURCES /orders/15 AVOID INCREMENTAL NUMBERS /orders/A323K833 IF IT’S BUSINESS CRITICAL
  114. 114. Unstructured APIs = API aggregation
  115. 115. api.example.org/v1/latest-news
  116. 116. latest news + metatags + banners + navigation yada yada yada
  117. 117. Sort of a “wild” API for your whole app
  118. 118. The client receives a GET on /something and will let the API figure out what /u/something actually is
  119. 119. Orchestration Layers
  120. 120. https://engineering.groupon.com/2013/misc/i-tier-dismantling-the-monoliths/
  121. 121. “Most APIs are designed by the API provider with the goal of maintaining data model purity. When building an OL, be prepared to sometimes abandon purity in favor of optimizations and/or performance.” Daniel Jacobson, director of engineering for the Netflix API http://www.infoq.com/presentations/API-Revolution
  122. 122. DOMAIN users orders stock images
  123. 123. DOMAIN Think about collections not controllers
  124. 124. DOMAIN PUT/PATCH try to always plan for full updates
  125. 125. UNIFORM RESPONSES
  126. 126. CODEBASE ORGANIZATION
  127. 127. CODEBASE ORGANIZATION one bundle for each api? one bundle for each application? one app for each sets of api?
  128. 128. CODEBASE ORGANIZATION start with an app organize bundles semantically create shared bundles
  129. 129. CODEBASE ORGANIZATION BUNDLES product checkout warehouse generic entity
  130. 130. CODEBASE ORGANIZATION APP product BUNDLES checkout warehouse generic entity
  131. 131. 7. SCALABILITY
  132. 132. CACHE ALL THE THINGS!
  133. 133. MIDDLEWARE
  134. 134. MIDDLEWARE - CONNECT
  135. 135. MIDDLEWARE - STACK
  136. 136. AVOID SESSIONS
  137. 137. EVERYTHING AS A RESOURCE
  138. 138. 8. ISSUES
  139. 139. CORS
  140. 140. CORS
  141. 141. (silly) browsers
  142. 142. if a cross-domain request is cacheable, the android browser goes nuts (silly) browsers
  143. 143. The request does not include the Origin header (silly) browsers
  144. 144. Status code: 0 (silly) browsers
  145. 145. WHAT. THE. HECK. http://opensourcehacker.com/2011/03/20/android-webkit-xhr-status-code-0-and-expires-headers/ (silly) browsers
  146. 146. “standards”
  147. 147. Don’t play with fire
  148. 148. 1 API, N clients consuming it Don’t play with fire
  149. 149. desktop browser, mobile browser, ios app, android app... Don’t play with fire
  150. 150. Keep as much logic as possible on the server Don’t play with fire
  151. 151. Less things to implement on every client and centralized implementations Don’t play with fire
  152. 152. make it easy for the API clients Don’t play with fire
  153. 153. POST https://api.example.com/login 200 OK date: Thu, 01 May 2014 21:52:33 GMT content-type: application/json transfer-encoding: chunked connection: close set-cookie: login=...; cache-control: no-cache Don’t play with fire { “email"=>"alessandro.cinelli@gmail.com", "firstName"=>"Alessandro", "lastName"=>"Cinelli", “birthday”=>"14/09/1985", }
  154. 154. Security matters
  155. 155. [ "odino@gmail.com", "cirpo@gmail.com", ... ] Security matters
  156. 156. for(;;);[ "odino@gmail.com", "cirpo@gmail.com", ... ] Security matters
  157. 157. while(1);[ "odino@gmail.com", "cirpo@gmail.com", ... ] Security matters
  158. 158. while(1);[ "odino@gmail.com", "cirpo@gmail.com", ... ] Security matters
  159. 159. Avoid [...] http://bit.ly/json-hijacking Security matters
  160. 160. USE {…} Security matters
  161. 161. That’s all folks
  162. 162. / github.com/cirpo @cirpo
  163. 163. tech.namshi.com/join-us we are hiring! tech.namshi.com github.com/namshi twitter.com/TechNamshi
  164. 164. THANKS
  165. 165. CREDITS http://www.panoramio.com/photo/30329016 https://farm3.staticflickr.com/2199/2365883747_3a5c753719_o.jpg http://news.buzzbuzzhome.com/2013/04/top-7-aerial-photos-cities.html https://www.flickr.com/photos/superlekker/5917559189/sizes/l https://www.flickr.com/photos/derekbruff/12336187505/sizes/l https://www.flickr.com/photos/chberge/3803475294/sizes/l https://www.flickr.com/photos/neilsingapore/8057578769 https://www.flickr.com/photos/dionnehartnett/6805481856/sizes/l https://www.flickr.com/photos/thomashawk/186339737 https://www.flickr.com/photos/cesarastudillo/3981364314/sizes/l https://www.flickr.com/photos/an_untrained_eye/6630719431 https://www.flickr.com/photos/30835738@N03/7936491790/sizes/l https://www.flickr.com/photos/deboni/2959228565/sizes/l https://www.flickr.com/photos/ghalog/6782751111/sizes/l https://www.flickr.com/photos/timzim/177640262/sizes/o/ https://www.flickr.com/photos/innoxiuss/2824204305 https://www.flickr.com/photos/hawk59/6038847752/sizes/l https://www.flickr.com/photos/remydwd/5487417702/sizes/l https://www.flickr.com/photos/rammorrison/4359793666/sizes/o/ https://www.flickr.com/photos/piers_nye/2501994750/sizes/o/ https://www.flickr.com/photos/danielygo/7559750132/sizes/l https://www.flickr.com/photos/msc72/2600035028/sizes/l https://www.flickr.com/photos/sicilianitaliano/3609275241/sizes/l https://www.flickr.com/photos/scottmontreal/7235110028/sizes/l https://www.flickr.com/photos/piet_musterd/6170853224/sizes/l https://www.flickr.com/photos/music_embassy/7137413247/sizes/l http://upload.wikimedia.org/wikipedia/commons/9/9c/William_James_b1842c.jpg http://theverybesttop10.files.wordpress.com/2013/08/the-world_s-top-10-things-no-person-with-a-ocd-should-see-1.jpg https://www.flickr.com/photos/62244271@N03/8553590682/sizes/l

×