On Github sufw / TechEd2013-CD207
http://slides.bluet.com.au/cd207
Sascha Wenninger (@sufw) + Custodio de Oliveira (@zcust01)
Technical Architect
Focus: SAP Integration
Wannabe Performance Engineer
Enterprise IT != Boring
Co-founder of Blue T
Focus: Clean Code, ABAP, WDA/FPM, BRF+
Interested in Mobile, HANA (brainwashed by SAP Marketing)
Do, or do not. There's no try.
Got curious about REST a few years ago.
Read some books/blogs and watched some conference talks
Designed some simple trivial APIs HTTP Handlers in ERP
Thanks, Oliver Widder (Geek and Poke)
Seamless fit with existing online presence
Easy to use for ~80,000 business customers
Integration to ERP and CRM for data & functions
Initially considered SAP's Biller Direct
Ultimately decided on a custom build
Stateless Java front-end built on Spring
Nobody on the team had built a full RESTful API before.
To sell the idea, we built a SOAP API in parallel.
Abandoned this after ~4 weeks once comfort was built.
Hypertext As The Engine Of Application State
Makes development and maintenance easier in the long run
Hard-code/configure:
Yes, it's the nuclear option
However, in an enterprise we usually have different problems:
Put a version ID in the URL somewhere
http://myerp.acme.com/api/v1/customer/42
http://v1.api.myerp.acme.com/customer/42
Purists might prefer header-based versioning...
>>> GET http://api.acme.com/customer/42 HTTP/1.1 >>> Accept: application/vnd.acme.com.customer+json;v=2,application/json;q=0.5 <<< 200 OK <<< Content-Type: application/vnd.acme.com.customer;v=1
"Version in URL" is easier to do well
Works with the ICM
We didn't do this :-(
and regretted it before the project was finished
Put a "revision" ID in every resource:
HTTP/1.1 200 OK ... Etag: 2-020e25b0efb0ea57349fe2593a3c9cfe Content-Type: application/vnd.acme.com.foobar+json
{ "_id": "e2593a3c925b0efb0ea57349fe2c4b2c", "_rev": "2-020e25b0efb0ea57349fe2593a3c9cfe", "foo": "bar", "bar": "baz" }
Borrowed from CouchDB :-)
Know when your cache is stale
>>> GET order/12345 HTTP/1.1 >>> If-None-Match: 2-020e25b0efb0ea57349fe2593a3c9cfe <<< HTTP/1.1 304 Not Modified
or
<<< HTTP/1.1 200 OK <<< Etag: 3-550e8400e29b41d4a716446655440000 <<< ... (and here is the new one)
>>> PUT order/12345 HTTP/1.1 >>> If-Match: 2-020e25b0efb0ea57349fe2593a3c9cfe >>> {updated Payload} <<< HTTP/1.1 200 OK <<< Etag: 3-550e8400e29b41d4a716446655440000 <<< ...
...later...
>>> PUT order/12345 HTTP/1.1 >>> If-Match: 2-020e25b0efb0ea57349fe2593a3c9cfe >>> {updated Payload} <<< HTTP/1.1 409 Conflict <<< ...
aka Optimistic Locking
Many things in SAP are not explicitly versioned.
Cost to derive version ID may be significant
Use where it makes sense
You're just asking for trouble.
>>> GET customer/42 HTTP/1.1
<<< HTTP/1.1 200 OK <<< Etag: 2-020e25b0efb0ea57349fe2593a3c9cfe <<< {Payload}
>>> PUT customer/42 HTTP/1.1 >>> If-Match: 2-020e25b0efb0ea57349fe2593a3c9cfe >>> {updated Payload}
<<< HTTP/1.1 200 OK <<< Etag: 3-550e8400e29b41d4a716446655440000 <<< {updated Payload}
HTTP/2.0 will add proper support for this
In the meantime, there are non-RFC implementations (e.g. OData)
Caution with these
GET customer/42 HTTP/1.1
{ "_id": "42", "_rev": "12-eee8f9c6b8a9477b9eb4954c2b00acf9", "name": { "firstname": "Joe", "lastname": "Bloggs", "_links": {"self": "/customer/42/name"} }, "addresses": [ { "_id": "1", "_rev": "...", "type": "mail", "city": "Richmond", "street": "...", "_links": {"self": "/customer/42/address/1"} }, { "_id": "2", "_rev": "...", "type": "work", "city": "Melbourne", "street": "...", "_links": {"self": "/customer/42/address/2"} }], "_links": { "self": "/customer/42", "http://rel.acme.com/addresses": "/customer/42/addresses" }}
GET customer/42/addresses HTTP/1.1
{ "addresses": [ { "_id": "1", "_rev": "...", "type": "mail", "city": "Richmond", "street": "...", "_links": {"self": "/customer/42/address/1"} }, { "_id": "2", "_rev": "...", "type": "work", "city": "Melbourne", "street": "...", "_links": {"self": "/customer/42/address/2"} }], "_links": { "self": "/customer/42/addresses", "parent": "/customer/42" } }
PUT customer/42/address/1 HTTP/1.1 If-Match: 1-77ee3bf06b0f4616931870ed772842e5
{ "type": "mail", "city": "East Melbourne", "poBox": "PO Box 418", ... }
Server replies:
HTTP/1.1 200 OK Etag: 2-5a7a1d9157544d668ddb1dae41401c87
POST customer/42/addresses HTTP/1.1
{ "type": "home", "city": "Richmond", "street": "Bridge Road", ... }
Server replies:
HTTP/1.1 201 Created Location: customer/42/address/3 Etag: 1-9d406e22d08a4d1187b584665ffd9c7b
How to construct the POST request?
They could read docoumentation...
or:
GET customer/42 HTTP/1.1
{ "_links": { "self": "/customer/42", "http://rel.acme.com/addresses": "/customer/42/addresses" } }
GET customer/42/addresses HTTP/1.1
{ "type": "", "city": "", "street": "", "poBox": "", "state": "", ... }
POST customer/42/addresses HTTP/1.1
{ "type": "home", "city": "Richmond", "street": "Bridge Road", "poBox": null, "state": "Victoria", ... }
HTML has some nice, universally understood form semantics
But can add complexity...
If in doubt, prefer simplicity
e.g. for Atom
e.g. for JSON
{ "_links": { "edit": { "href": "/customer/42/addresses/edit", "name": "edit-or-add" } }}
yes, it's HAL
One of the most tangible benefits of the REST style
Caching is the magic sauce that makes the web run.
REST is the web, for machines
SAP ICM Cache for long-lived static content
Ehcache for session-based caching.
Think about this from day # 1
Let it inform the design of your representations
We decided not to do pagination
"GET items 250...300"-type queries are inefficient in ABAP
API returned full list of results; cached in Ehcache
Client app then paginated through cache content
HTTP is an Application Protocol.
Use the rich list of Status Codes for signalling
And then there's the 7xx codes ;-)
There's 78 of them on Wikipedia
Don't reinvent the wheel.
We want to see the list of invoices
GET customer/42/invoices HTTP/1.1
But there isn't any...
you do?
{ "invoices": [], "_links": { "self": "/customer/42/invoices", "parent": "/customer/42" } }
REST is not a Protocol
REST is an Architectural Style
There is no one way of doing things
RFC 2616 (HTTP/1.1)
Apigee Best Practices eBook and blog
Blogs by Erik Wilde, Mark Nottingham or Stu Charlton
The Yahoo rest-discuss group
New ABAP REST Library on SCN
etc.
When implementing RESTful APIs, a lot fewer choices are made for you.
Speed of implementation is not an aim of REST.
Long-term evolvability is.
Get involved in client development
Helps you build a better API
Use stubs for isolation
Helps communication and coordination
Build APIs which are usable
Avoid flights of fancy
"Why don't we implement _________?"
Working with non-SAP developers helps to avoid SAPinese
WERKS: Warehouse | Store | Factory KUNNR: Customer ID 0000000042: 42 X: true
Remember Lesson #1?
Make sure your consumers use hyperlinks
not often; just sometimes
e.g. change some URLs from /customer to /b2bCustomer
If both sides follow HATEOAS, nothing should break ;-)
It's non-trivial to build a decent RESTful API.
It will take time, and probably more than 1 attempt.
Your project might not need a full-blown RESTful API.
And that's okay!
Just don't call it REST ;-)
Tweet us at @sufw and @zcust01
Come to Networking Lounge 1 at 2:30pm
The slides are here: http://slides.bluet.com.au/cd207
"It's a SOA", by Oliver Widder (Geek and Poke)
Nuclear Burst Grunge Flags, by Nicolas Raymond