Well-built service API may be a game changer. In both client-server & server-server scenario, especially if you're following the latest trend of building fine-grained, loosely coupled (micro)services. I've seen several attempts, in different languages, for different domains & running on different platforms - regardless of these differences, there are some common anti-patterns & mistakes that could quite easily be avoided. My goal in this blog post will be to list the ones I find most meaningful & not-that-obvious.
Before I get into details - keep in mind that each situation is different - there's no "golden template" that fits all scenarios: in some cases practices mentioned before may be considered "the lesser evil".
One more thing - let's assume we'll consider RESTful APIs only - this approach seems to be the natural choice in the majority of cases.
REST stands for CRUD
This is most likely the most significant (& annoying) mistake - and one that may have very meaningful consequences. Designers tend to follow a simplistic approach - hierarchy of objects becomes hierarchy of resources, HTTP verbs are getting mapped straight to CRUD operations. Unfortunately, it:
- sets very strict restrictions on what can be a resource
- makes it much harder to expose sophisticated business operations (usually operations are being mapped to ... entities so they can be "PUT"/"POSTED" in ...)
The key point is to change the way of thinking about the POST operation - it's not supposed to be an "UPDATE" equivalent: its role is to fill for whatever doesn't match other verbs (GET/PUT/DELETE). It's perfectly fine to POST custom operation name / descriptor as an auxiliary operation.
Reflecting the hierarchy of resources (in URLs)
This one is more annoying than harmful - some designers get overzealous with the resource hierarchy: they believe that if resource Y exists somewhere within resource X, URL always has to state that explicitly:
BUT it's not the role of URL to reflect the hierarchy of linked objects (if you need dynamic hierarchy, go for HATEOAS), convention above makes sense only if Y's identifier is relative - in other words, resource with that identifier may happen for many instances of resource X. In other case, this is enough:
Exhibitionism-driven API design
I hate when designers mindlessly expose all the internals - they still may have done their domain modelling work well, but designing an API is a separate process without separate output. If you expose all your entities & allow the separate manipulation on the level of all entities, you're pushing the cross-entity integration logic outside (to API consumers) which is, well - just terrible.
Solution may be very simple - follow the idea of Aggregate Root from Domain-Driven Design. Expose your AR as API's entity (resource), allow only operations that do not break the internal coherency of AR.
Flat, generic API
This may be the most controversial point of this list, but personally I have no doubts about it & I'm totally convinced about its consequences. My point is - in majority of cases, properly designed (micro)services shouldn't be consumed directly by clients. By "properly designed" I mean ones that follow DDD principles (on the level of AR).
Why so? Aren't API supposed to be consumed? All operations on an AR exposed as a resource in such (micro)service will keep AR in a proper, consistent state, BUT it doesn't mean that business needs can be fulfilled by operations executed on the level of this AR. AR design is not based on use cases! Business operations will be driven by UX decisions, ergonomy, efficiency, actual scenarios - and these won't care much about resource boundaries.
Hence patterns like API Gateway & Backends For Front-Ends. (Micro)services are supposed to be atomic elements of composition, for more sophisticated, complex services, which are fit for a single, given purpose (particular client application or consumer group). Not many mention that in detail, but this is also the pattern followed by famous "(micro)service unicorns":
Versioning in URLs
Having versions in URLs may look totally harmlessly (what is more, it has some benefits - like simplified deployment), but it has one meaningful, negative consequence you should make sure you're aware: it makes HATEOAS (& dynamic resource "navigation", in consequence) much harder. A particular resource should have links to several related resources, but if API supports several versions - which one should be linked?
Solution is a no-brainer: content negotiation. It's already built into HTTP protocol, it's simple & very flexible (in terms of what range your resource you want to "version together" - in a bundle).
That's my list. Feel free to share your observations & comments. Not only regarding these entries: would you add anything else to the list? Any constructive feedback highly appreciated.
Pic: © Andrey Popov - Fotolia.com