August 12, 2015 | Microservices
Over the last few months one of my main projects at OpenCredo has involved creating various microservices which are interacted with via REST. We’ve been working with a relatively rich domain model, which in turn has presented a lot of challenges in how to design our various resources. This blog post aims to summarise various techniques and practices which I’ve found helpful in overcoming these challenges.
WRITTEN BY
We are building an auditing system which can be used by customers to request audits on various companies if they want to know more about their business practices. For example, as a clothing retailer, I may believe that one of my factories is not complying with various employment regulations. In this case I should be able to request that an audit takes place on the factory, after which an auditor can accept the audit, investigate the factory, and highlight any issues they might find. Following this, the auditee has to resolve any issues and prove to the auditor they have done so with evidence. They can then publish the audit making it available to me and any other interested third parties to view. Below is a state diagram of the workflow:
At first glance, we see that we have just one entity that is being modified throughout this process – an audit. So one might think when designing the API that it would be a good approach to create a single audit resource with CRUD operations that translate to POST
, GET
, PUT
and DELETE
respectively.
Creating a new audit:
POST /audits
{
"status": "REQUESTED",
"requestByCompany" : "OpenCredo",
"auditCompany" : "Audit Co Limited",
"auditedCompany" : "Tom's Factory",
"issues" : []
}
201 Created
Location www.audit-manager.com/audits/1
Retrieving the newly created audit:
GET /audits/1
200 OK
An idempotent modification of the audit:
PUT /audits/1
{
"status": "ACCEPTED",
"requestByCompany" : "OpenCredo",
"auditCompany" : "Audit Co Limited",
"auditedCompany" : "Tom's Factory",
"issues" : [
{
"name" : "Unsafe working conditions",
"status" : "UNRESOLVED"
}
]
}
200 OK
Deleting an audit:
DELETE /audits/1
This type of course grained, CRUD Rest API, can make life for the API consumer difficult:
accept
audits, and they can only do so if the audit has a state of requested
– so if you tried to make an update that violated these constraints the server would give you a 403
. The more complicated the state machine, the more work for the API consumer trying to figure out what changes are and are not valid, and the more room for error in allowing an end user of a client to make calls which will ultimately fail.If you look at the state diagram, you will notice that although all our changes will apply to the same resource, we still have a finite list of transitions which can take us through the various states. So, rather than modifying the resource directly we could trigger these transitions by interacting with sub resources of the audit.
From another perspective, if you speak in the ubiquitous language of our domain you’ll talk about accepting an audit
, rejecting an audit
, submitting issues
etc. So continuing to speak in this language with our API makes sense.
To accept an audit:
PUT /audits/1/acceptance
201 Created
To reject:
PUT /audits/1/rejection
201 Created
Submit issue:
POST /audit/1/issues
{
"description" : "Breaking minimum wage laws"
}
201 Created
Location /audit/1/issues/1/
Resolve issue:
PUT /audit/1/issues/1/resolution
{
"evidence" : "We've done something to fix it"
}
200 OK
Of course, all our resources are still nouns, so they themselves can be interacted with by the API consumer using the various HTTP verbs as normal:
GET /audits/1/acceptance
200 OK
{
"acceptedBy" : "Andrew Morgan",
"acceptedAt" : "2015-08-07T18:30:27+00:00"
}
Another thing to note, is our various endpoint payloads now only contain the data we need, with some simple endpoints such as /acceptance
not requiring a payload at all. Things have become a lot less bloated than they were when we were just doing PUT
with the entire audit JSON
whenever we wanted to make a change.
Although the API is more fine grained, with each resource mapping to a state transition, we still don’t know what operations are possible on a resource without consulting some documentation, and if we did we wouldn’t know what subset of those operations are available based on the users permissions. This is where HATEOAS comes in, known as hypertext as the engine of application state, which uses hypermedia to dynamically convey possible API interactions to it’s various consumers. In our case, this works particularly well with permissions, as now when we GET
our audit resource we can have various relation types telling us the next possible actions:
GET /audits/1
200 OK
{
"content" : {
"status": "REQUESTED",
"requestByCompany" : "OpenCredo",
"auditCompany" : "Audit Co Limited",
"auditedCompany" : "Tom's Factory"
...
},
"links": [
{"rel" : "self", "href": "/audits/1" },
{"rel" : "accept", "href": "/audits/1/acceptance" },
{"rel" : "reject", "href": "/audits/1/rejection" }
]
}
But when we GET
an audit which has a status of SUBMITTED_TO_FACTORY
, and we are authenticated as the auditee, the available relation types would change:
GET /audits/1
200 OK
{
"content" : {
"status": "SUBMITTED_TO_FACTORY",
"requestByCompany" : "OpenCredo",
"auditCompany" : "Audit Co Limited",
"auditedCompany" : "Tom's Factory",
"issues" : [
{
"content" : {
"name" : "Some issue",
"status" : "UNRESOLVED"
}
"links": {
{"rel" : "self", "href": "/audits/1/issues/1" },
{"rel" : "resolve", "href": "/audits/1/issues/1/resolution" }
},
},
{
"content" {
"name" : "Some other issue",
"status" : "RESOLVED"
}
"links": {
{"rel" : "self", "href": "/audits/1/issues/1" }
}
}
],
...
},
"links": {
{"rel" : "self", "href": "/audits/1" },
{"rel" : "publish", "href": "/audits/1/publication" }
}
And of course, when performing a GET
on the same audit in the same state whilst authenticated as the auditor, the links would change again because the auditor doesn’t have permission to do anything other than view it:
GET /audits/1
200 OK
{
"content" : {
"status": "SUBMITTED_TO_FACTORY",
"requestByCompany" : "OpenCredo",
"auditCompany" : "Audit Co Limited",
"auditedCompany" : "Tom's Factory",
"issues" : [
{
"content" : {
"name" : "Some issue",
"status" : "UNRESOLVED"
}
"links": {
{"rel" : "self", "href": "/audits/1/issues/1" },
},
},
{
"content" {
"name" : "Some other issue",
"status" : "RESOLVED"
}
"links": {
{"rel" : "self", "href": "/audits/1/issues/1" }
}
}
],
...
},
"links": {
{"rel" : "self", "href": "/audits/1" },
}
As possible actions are now driven entirely by the API, we have one less concern for the consumer which no longer needs to worry about permission logic. So for example, a menu on the screen of a single page app could dynamically change by simply parsing the response and adding / removing items based on the presence of relation types. Also, changes to the permission logic will now be non-breaking to the API consumer, allowing the API to evolve independently of it’s clients. In fact the href
values can also change, as long as we keep the same relation types.
One thing to note is whether or not we are breaking the semantics of REST in our relation type naming. You could argue that relation types should be named as resources, not verbs as done in this example. I think in this case there’s value in breaking these semantics, because it allows you to have different relation types for the same URL in order to communicate multiple permutations of permissions on the same resource:
"links": {
{ "rel" : "accept", "href": "/audits/1/acceptance" },
{ "rel" : "view-acceptance", "href": "/audits/1/acceptance" }
}
It’s more of a pragmatic approach than anything, because some potential alternatives would be to make an OPTIONS
request to see what http verbs are possible (meaning > 1 requests), or to add a verb
attribute as an additional one alongside href
which also breaks the semantics.
Just as a footnote it’s worth bearing in mind that if you are going to GET
resources which will have frequently changing link relations, you may not receive the most up to date response unless you set the appropriate cache control headers in your request.
One problem we still have, is not knowing information such as possible http
verbs to use on various resources and their required payloads. We also aren’t sticking to a defined hypermedia representation type, meaning we are making up our own resource structure rather than using a predefined one that various libraries may already be able to understand. This is where something like HAL could come into play:
GET /audits/1
200 OK
Content type application/hal+json
{
...
"_links": {
"curies": [{
"name": "audit",
"href": "http://docs.audit.com/relations/{rel}",
"templated": true
}],
"self" : { "href": "/audits/1" },
"audit:accept" : { "href": "/audits/1/acceptance" },
"audit:reject" : { "href": "/audits/1/rejection" }
}
}
Whilst self
is a predefined relation type, our custom ones are not. We’ve therefore had to create documentation for them, knowing our API consumers will have no prior knowledge of what they do. This is referenced by templating the href
attribute in the curies
element named audit
to produce urls for various bits of API documentation. So we now have http://docs.audit.com/relations/accept
and http://docs.audit.com/relations/reject
respectively. Again this is more useful to our consumers, because they discover resources and their documentation as they become available as interactions, rather than having to spend a long time trapsing through contracts and documentation in advance.
There’s a lot more to HAL
than what has been documented in this blog post, so I’d encourage you to read the spec. It’s currently in draft phase but has support from a variety of tools and frameworks. One that I’ve found useful was Spring HATEOAS.
Taking an entity and exposing it via a Rest CRUD interface is not always the best solution. I hope this has showcased:
This blog is written exclusively by the OpenCredo team. We do not accept external contributions.