Considering HTTP Methods PUT And PATCH Indicators Of An Anemic Domain Model And A Leaky Abstraction
A few years ago, I wrote about how I don't like "Update" methods in my API design. In that post, I talked about my "feelings" on the matter because I couldn't really articulate the red flags that were going off in the back of my mind. Over the weekend, however, I was reading through Implementing Domain-Driven Design by Vaughn Vernon when it suddenly occurred to me that everything he was saying about the anemic domain model applied to the way that I think about HTTP API design. Finally, I think I have the right words needed to codify my feelings. I think the PUT
and PATCH
HTTP methods indicate an anemic domain model and a leaky abstraction. Which, in most cases, is not a good thing.
In Implementing Domain-Driven Design (DDD), Vernon talks about the "Ubiquitous Language", which is the set of terms (aka, Design Patterns) that the entire team agrees to use in order to describe a set of shared definitions:
Thus, the Ubiquitous Language is a team pattern used to capture the concepts and terms of a specific core business domain in the software model itself. The software model incorporates the nouns, adjectives, verbs, and richer expressions formally formulated and spoken by the close-knit team. Both the software and the tests that verify the model's adherence to the tenets of the domain capture and adhere to this Language, the same one spoken by the team. (Vaughn Vernon)
While I am a total noobie when it comes to Domain-Driven Design (DDD), when I read this passage, I translate it in my head to mean that the Ubiquitous Language describes the state of an application and the state transformations that can be applied to it in terms of "business use-cases". Now, in the book, Vernon looks at how the Ubiquitous Language applies to the domain model; but, I think it can also be applied to the HTTP API surface-area that sits above the application core.
For example, Vernon looks at a Customer
entity that supports the following public setters:
Customer.setStreetAddress1( string )
Customer.setStreetAddress2( string )
Customer.setCity( string )
Customer.setStateOrProvince( string )
Customer.setPostalCode( string )
Customer.setCountry( string )
This set of methods creates an anemic domain model because it doesn't reveal any intention that relates to the business use-cases. There's no obvious way to tell whether or not these methods all need to be called together; or, under what conditions they are even allowed to be called. All that logic, complexity, and know-how is pushed into the responsibility of the consuming code.
This set of methods also creates a bit of a leaky abstraction because it exposes the fact that the address is stored as a set of properties on the Customer
object, each of which can be mutated independently.
Vernon suggests that we can replace these anemic public setters with a method that describes the business-use cases in terms of the Ubiquitous Language. Something like:
Customer.relocateTo( Address )
Now, instead of knowing how, when, why, and in which combination to call a series of setter methods, we are calling a single method, .relocateTo()
, which describes the state mutation in terms of the business use-cases - "relocation" - and the agreed upon term, Address
.
Circling back to the whole point of this post, this refactoring away from an anemic domain modal can then be applied to the HTTP layer that is used to interact with the application core. Instead of allowing the Customer
object to be manipulated with a PATCH
method, which is essentially an anemic set of public setter methods:
PATCH /customers/{ uuid }
... we can create an API end-point that uses the same ubiquitous language and reflects the set of business uses-cases that the application supports:
POST /customers/{ uuid }/relocate-to
Using this mindset, we can all but get rid of the PUT
and PATCH
HTTP methods. Instead, we can use POST
to create and transition state, GET
to read it, and DELETE
to remove it.
Heck, I'd even be open to getting rid of the DELETE
HTTP method:
DELETE /customers/{ uuid }
... and replacing it with a POST
and a term from the Ubiquitous Language:
POST /customers/{ uuid }/deactivate
If you start crafting your HTTP API surface area to reflect the processes of the business and the terms from the Ubiquitous Language, you could probably do everything you need with just GET
and POST
. I'm not saying you should do this, necessarily (getting rid of DELETE
); I'm just having fun extending the thought exercise).
I've never liked the PUT
and PATCH
HTTP methods. But, I could only ever describe this through "gut feelings" on the matter. As I'm reading up on Domain-Driven Design (DDD), I think I finally have ways to articulate those feelings in more meaningful ways.
PUT
HTTP Method
Epilogue On Amazon S3 And The In my post, I talk about using a meaningful API design that captures the richness of the business and reflects the ubiquitous language. However, that needs to be taken in context; Domain-Driven Design (DDD) is all about context. Take, for example, Amazon S3. The S3 API allows users to upload arbitrary data to an arbitrary end-point using the PUT
HTTP method. In this context, this is completely fine because S3 isn't trying to reflect the richness of your business - it only tries to provide a storage API with a file-system-like schema. In that context, HTTP methods like PUT
and DELETE
make total sense since they align with the richness of the S3 business.
PUT
And PATCH
Methods
Epilogue On Leaky Abstractions And The When I say that PUT
and PATCH
can indicate a leaky abstraction, what I mean is that I often see PUT
and PATCH
methods being provided by engineers who view their API as a thin layer over a data-persistence mechanism. The thought process being, "I have a service that manages Customer data; so, I'll need to create end-points, like GET, POST, PATCH, and DELETE that allow that data to be managed." So, with this mindset, HTTP methods like PUT
and PATCH
can indicate that the service is nothing more than a leaky abstraction around the underlying database.
This becomes especially obvious when a service provides DELETE
and PATCH
end-points that never get used, indicating that the service was never designed to reflect the richness of the business, and only the implementation of the underlying database.
Reader Comments
It sounds like you might dig Event Sourcing.
Here's a pretty comprehensive look at CQRS + Event Sourcing: https://www.microsoft.com/en-us/download/details.aspx?id=34774
In 2019 I implemented an event sourced system which had me move to only using POST and GET http methods. I'm slowly working on an article about the experience :)
If you've never looked at ES before check out Greg Young videos on YouTube for an introduction.
@Jason,
Thanks for the link, I will take a look. I know "of" Event Sourcing; but, I haven't had any hands-on experience with it. I've definitely watched a few videos from Greg Young. There was also another guy, Juri something? who also had a lot of articles about Event Sourcing. It's definitely a fascinating idea.
@Jason,
The other guy I was thinking about was Udi Dahan -- http://udidahan.com . He has some great articles on hexagonal application architecture and events and things of that nature.
@Ben,
Alberto Brandolini is also a great source for Event Storming, he named and codified the approach. In particular the things necessary to make it work speak to human psychology in a way that challenges people.
Cheers,
Michael
@Ben,
Cool, looks like Udi contributed to that CQRS/ES book I mentioned. I've started to go through it over this winter break and it's enjoyable. I like the structure of the book a lot, as a journey of software development.
@Michael,
Alberto is fantastic, as well as event storming. If you haven't seen it yet, check out what Adam Dymitruk is doing with Event Modeling at eventmodeling.org
@Jason,
Ha ha, very cool -- small world with all this stuff. I'll take a look at the Event Modeling link. Sounds interesting.