restjsonapis-120711135859-phpapp02
TRANSCRIPT
-
7/28/2019 restjsonapis-120711135859-phpapp02
1/77
Beautiful REST + JSON APIs
Les Hazlewood
CTO, Stormpath
stormpath.com
-
7/28/2019 restjsonapis-120711135859-phpapp02
2/77
About
CTO Co-Founder StormpathIdentity and Access Management APIUser security automations (e.g.
authentication, password reset)
Active Directory for the cloudon steroids
Founder, PMC Chair, Apache ShiroLeading Java Security Framework+200k downloads
-
7/28/2019 restjsonapis-120711135859-phpapp02
3/77
Outline
APIs, REST & JSON REST Fundamentals Design:
Base URL Versioning Resource Format
Return Values Content Negotiation References (Linking) Pagination Query Parameters Associations Errors IDs Method Overloading
Resource Expansion Partial Responses Caching & Etags Security Multi Tenancy Maintenance
-
7/28/2019 restjsonapis-120711135859-phpapp02
4/77
APIs
Applications Developers Pragmatism over Ideology Adoption Scale
-
7/28/2019 restjsonapis-120711135859-phpapp02
5/77
Why REST?
Scalability Generality Independence Latency (Caching) Security Encapsulation
-
7/28/2019 restjsonapis-120711135859-phpapp02
6/77
Why JSON?
Ubiquity Simplicity Readability Scalability Flexibility
-
7/28/2019 restjsonapis-120711135859-phpapp02
7/77
HATEOAS
Hypermedia As The Engine Of Application StateFurther restriction on REST architectures.
-
7/28/2019 restjsonapis-120711135859-phpapp02
8/77
REST Is Easy
-
7/28/2019 restjsonapis-120711135859-phpapp02
9/77
REST Is *&@#$! Hard(for providers)
-
7/28/2019 restjsonapis-120711135859-phpapp02
10/77
REST can be easy
(if you follow some guidelines)
-
7/28/2019 restjsonapis-120711135859-phpapp02
11/77
Fundamentals
-
7/28/2019 restjsonapis-120711135859-phpapp02
12/77
-
7/28/2019 restjsonapis-120711135859-phpapp02
13/77
Resources
Nouns, not Verbs
Coarse Grained, not Fine Grained
Architectural style for use-case scalability
-
7/28/2019 restjsonapis-120711135859-phpapp02
14/77
/getAccount
/createDirectory
/updateGroup
/verifyAccountEmailAddress
What If?
-
7/28/2019 restjsonapis-120711135859-phpapp02
15/77
/getAccount/getAllAccounts/searchAccounts/createDirectory
/createLdapDirectory/updateGroup/updateGroupName/findGroupsByDirectory/searchGroupsByName
/verifyAccountEmailAddress/verifyAccountEmailAddressByTokenSmells like bad RPC. DONT DO THIS.
What If?
-
7/28/2019 restjsonapis-120711135859-phpapp02
16/77
Keep It Simple
-
7/28/2019 restjsonapis-120711135859-phpapp02
17/77
The Answer
Fundamentally two types of resources:
Collection Resource
Instance Resource
-
7/28/2019 restjsonapis-120711135859-phpapp02
18/77
Collection Resource
/applications
-
7/28/2019 restjsonapis-120711135859-phpapp02
19/77
Instance Resource
/applications/a1b2c3
-
7/28/2019 restjsonapis-120711135859-phpapp02
20/77
Behavior
GETPUTPOSTDELETEHEAD
-
7/28/2019 restjsonapis-120711135859-phpapp02
21/77
Behavior
POST, GET, PUT, DELETE
1:1Create,Read,Update,Delete
-
7/28/2019 restjsonapis-120711135859-phpapp02
22/77
Behavior
Asyouwouldexpect:
GET=Read
DELETE=Delete
HEAD=Headers,noBody
-
7/28/2019 restjsonapis-120711135859-phpapp02
23/77
Behavior
Notsoobvious:
PUTandPOSTcanbothbeusedfor
CreateandUpdate
-
7/28/2019 restjsonapis-120711135859-phpapp02
24/77
PUT for Create
Idenfierisknownbytheclient:
PUT /applications/clientSpecifiedId
{
}
-
7/28/2019 restjsonapis-120711135859-phpapp02
25/77
PUT for Update
FullReplacement
PUT /applications/existingId{
name: Best App Ever,
description: Awesomeness
}
-
7/28/2019 restjsonapis-120711135859-phpapp02
26/77
PUT
Idempotent
-
7/28/2019 restjsonapis-120711135859-phpapp02
27/77
POST as CreateOnaparentresource
POST /applications
{name: Best App Ever
}
Response:
201 Created
Location: https://api.stormpath.com/applications/a1b2c3
-
7/28/2019 restjsonapis-120711135859-phpapp02
28/77
POST as UpdateOninstanceresource
POST /applications/a1b2c3
{name: Best App Ever. Srsly.
}
Response:
200 OK
-
7/28/2019 restjsonapis-120711135859-phpapp02
29/77
POST
NOTIdempotent
-
7/28/2019 restjsonapis-120711135859-phpapp02
30/77
Media Types
FormatSpecificaon+ParsingRules Request:Acceptheader Response:Content-Typeheader application/json application/foo+json application/foo+json;application
-
7/28/2019 restjsonapis-120711135859-phpapp02
31/77
Design Time!
-
7/28/2019 restjsonapis-120711135859-phpapp02
32/77
Base URL
-
7/28/2019 restjsonapis-120711135859-phpapp02
33/77
http(s)://api.foo.com
vs
http://www.foo.com/dev/service/api/rest
-
7/28/2019 restjsonapis-120711135859-phpapp02
34/77
http(s)://api.foo.com
Rest Clientvs
Browser
-
7/28/2019 restjsonapis-120711135859-phpapp02
35/77
Versioning
-
7/28/2019 restjsonapis-120711135859-phpapp02
36/77
URL
https://api.stormpath.com/v1
vs.
Media-Typeapplication/json+foo;application,v=1
-
7/28/2019 restjsonapis-120711135859-phpapp02
37/77
Resource Format
-
7/28/2019 restjsonapis-120711135859-phpapp02
38/77
Media Type
Content-Type: application/json
When time allows:
application/foo+json
application/foo+json;bar=baz,v=1
-
7/28/2019 restjsonapis-120711135859-phpapp02
39/77
camelCase
JS in JSON = JavaScript
myArray.forEach
NotmyArray.for_each
account.givenName
Notaccount.given_name
Underscores for property/function namesare unconventional for JS. Stay consistent.
-
7/28/2019 restjsonapis-120711135859-phpapp02
40/77
Date/Time/Timestamp
Theres already a standard. Use it: ISO 8601
Example:
{
,
createdTimestamp: 2012-07-10T18:02:24.343Z
}
Use UTC!
-
7/28/2019 restjsonapis-120711135859-phpapp02
41/77
HREF
Distributed Hypermedia is paramount! Every accessible Resource has a unique
URL. Replaces IDs (IDs exist, but are opaque).{href: https://api.stormpath.com/v1/accounts/x7y8z9,
}
Critical for linking, as well soon see
-
7/28/2019 restjsonapis-120711135859-phpapp02
42/77
Response Body
-
7/28/2019 restjsonapis-120711135859-phpapp02
43/77
GET obvious
What about POST?
Return the representation in the responsewhen feasible.
Add override (?_body=false) for control
-
7/28/2019 restjsonapis-120711135859-phpapp02
44/77
Content Negotiation
-
7/28/2019 restjsonapis-120711135859-phpapp02
45/77
Header
Accept header Header values comma delimited inorder of preferenceGET /applications/a1b2c3
Accept: application/json, text/plain
-
7/28/2019 restjsonapis-120711135859-phpapp02
46/77
Resource Extension
/applications/a1b2c3.json
/applications/a1b2c3.csv
ConvenonallyoverridesAcceptheader
-
7/28/2019 restjsonapis-120711135859-phpapp02
47/77
Resource Referencesaka Linking
-
7/28/2019 restjsonapis-120711135859-phpapp02
48/77
Hypermedia is paramount. Linking is fundamental to scalability. Tricky in JSON XML has it (XLink), JSON doesnt How do we do it?
-
7/28/2019 restjsonapis-120711135859-phpapp02
49/77
Instance Reference
GET /accounts/x7y8z9
200 OK
{
href: https://api.stormpath.com/v1/accounts/x7y8z9,
givenName: Tony,
surname: Stark,
,
directory: ????}
-
7/28/2019 restjsonapis-120711135859-phpapp02
50/77
Instance Reference
GET /accounts/x7y8z9
200 OK
{
href: https://api.stormpath.com/v1/accounts/x7y8z9,
givenName: Tony,
surname: Stark,
,
directory: {href: https://api.stormpath.com/v1/directories/g4h5i6
}
}
-
7/28/2019 restjsonapis-120711135859-phpapp02
51/77
Collection Reference
GET /accounts/x7y8z9
200 OK
{
href: https://api.stormpath.com/v1/accounts/x7y8z9,
givenName: Tony,
surname: Stark,
,
groups: {
href: https://api.stormpath.com/v1/accounts/x7y8z9/groups
}
}
-
7/28/2019 restjsonapis-120711135859-phpapp02
52/77
Reference Expansion
(aka Entity Expansion, Link Expansion)
-
7/28/2019 restjsonapis-120711135859-phpapp02
53/77
Account and its Directory?
-
7/28/2019 restjsonapis-120711135859-phpapp02
54/77
GET /accounts/x7y8z9?expand=directory
200 OK
{
href: https://api.stormpath.com/v1/accounts/x7y8z9,
givenName: Tony,
surname: Stark,
,directory: {
href: https://api.stormpath.com/v1/directories/g4h5i6,
name: Avengers,
creationDate: 2012-07-01T14:22:18.029Z,
}
}
-
7/28/2019 restjsonapis-120711135859-phpapp02
55/77
Partial Representations
-
7/28/2019 restjsonapis-120711135859-phpapp02
56/77
GET /accounts/x7y8z9?fields=givenName,surname,directory(name)
-
7/28/2019 restjsonapis-120711135859-phpapp02
57/77
Pagination
-
7/28/2019 restjsonapis-120711135859-phpapp02
58/77
Collection Resource supports queryparams:
Off
set Limit/applications?offset=50&limit=25
-
7/28/2019 restjsonapis-120711135859-phpapp02
59/77
GET /accounts/x7y8z9/groups
200 OK
{href: /accounts/x7y8z9/groups,offset: 0,
limit: 25,first: { href: /accounts/x7y8z9/groups?offset=0 },previous: null,
next: { href: /accounts/x7y8z9/groups?offset=25 },
last: { href: },items: [{href:
},{
href:
},
]}
-
7/28/2019 restjsonapis-120711135859-phpapp02
60/77
Many To Many
-
7/28/2019 restjsonapis-120711135859-phpapp02
61/77
Group to Account
A group can have many accounts An account can be in many groups Each mapping is a resource:GroupMembership
-
7/28/2019 restjsonapis-120711135859-phpapp02
62/77
GET /groupMemberships/23lk3j2j3
200 OK
{
href: /groupMemberships/23lk3j2j3,
account: {
href:
},group: {
href:
},
}
-
7/28/2019 restjsonapis-120711135859-phpapp02
63/77
GET /accounts/x7y8z9
200 OK
{
href: /accounts/x7y8z9,
givenName: Tony,
surname: Stark,
,groupMemberships: {
href: /groupMemberships?accountId=x7y8z9
}
}
-
7/28/2019 restjsonapis-120711135859-phpapp02
64/77
Errors
-
7/28/2019 restjsonapis-120711135859-phpapp02
65/77
As descriptive as possible As much information as possible Developers are your customers
-
7/28/2019 restjsonapis-120711135859-phpapp02
66/77
POST /directories
409 Conflict{status: 409,
code: 40924,
property: name,
message: A Directory named Avengersalready exists.,
developerMessage: A directory namedAvengers already exists. If you have a stalelocal cache, please expire it now.,
moreInfo: https://www.stormpath.com/docs/api/errors/40924
}
-
7/28/2019 restjsonapis-120711135859-phpapp02
67/77
IDs
-
7/28/2019 restjsonapis-120711135859-phpapp02
68/77
IDs should be opaque Should be globally unique
Avoid sequential numbers (contention) Good candidates: UUIDs, Url64
-
7/28/2019 restjsonapis-120711135859-phpapp02
69/77
-
7/28/2019 restjsonapis-120711135859-phpapp02
70/77
POST /accounts/x7y8z9?_method=DELETE
-
7/28/2019 restjsonapis-120711135859-phpapp02
71/77
Caching &Concurrency Control
-
7/28/2019 restjsonapis-120711135859-phpapp02
72/77
Server(inialresponse):
ETag: "686897696a7c876b7e
Client(laterrequest):
If-None-Match: "686897696a7c876b7e
Server(laterresponse):
304 Not Modified
-
7/28/2019 restjsonapis-120711135859-phpapp02
73/77
Security
-
7/28/2019 restjsonapis-120711135859-phpapp02
74/77
Avoid sessions when possibleAuthenticate every request if necessaryStateless
Authorize based on resource content, NOT URL!
Use Existing Protocol:Oauth 1.0a, Oauth2, Basic over SSL only
Use Custom Scheme:Only if you provide client code / SDKOnly if you really, reallyknow what youre doing
-
7/28/2019 restjsonapis-120711135859-phpapp02
75/77
Maintenance
-
7/28/2019 restjsonapis-120711135859-phpapp02
76/77
Use HTTP Redirects
Create abstraction layer / endpoints whenmigrating
Use well defined custom Media Types
-
7/28/2019 restjsonapis-120711135859-phpapp02
77/77
Thanks!
Check out Stormpath.com/Docs forreferences and examples
Sign up at Stormpath.com Follow us on @goStormpath