Refactoring to Microservices – Using a Document as State
In a previous installment of our Microservice refactoring effort, I’ve introduced a ShopManager and a Clerk to implement the shopping process (see this blog). I ended up with a JSON document transferred between services. To make life easy for myself I just parsed all of the document using Spring magic. This time I will discuss the downside of this strategy and show an alternative.
If you’re interested in details, you can find the code I’m referring to here:
git clone git@github.com:xebia/microservices-breaking-up-a-monolith.git
git checkout tags/document_v2
The code can be found in the services/messages folder.
So, last time I ended up with a JSON document sent around from one service to the next, looking like [this]:
{
"uuid": "a89a65ae-6d1a-42e5-a8f1-11b6d190286e",
"status": 0,
"webUser": {
"uuid": "f7871043-9224-4ac7-8f5b-67976ff54137",
"username": "webuser051dcde5-43d7-434d-b363-3dee6dc137ce",
"password": "password"
},
"orderr": {
"uuid": "662dbb25-cf36-440b-998a-93bac6abbdaf",
"ordered": 1456001059094,
....
"total": 0.0
}
}
In order to parse this document, I created classes for each concept and a Clerk class as a container. While working on this version I frequently had to change some part of the domain classes and ended up making the same change in all of my services. This seemed to go against the general microservices idea of independent components. And of course it’s a lot of rather boring work.
To improve the design I implemented an idea inspired by Greg Young’s CQRS/DDD course. The idea is to store the JSON document as is and use only the parts that are needed. Marshalling and unmarshalling is done by domain classes.
As an example, let’s look at the [payment component].
The sequence diagram below shows the interaction between classes.
The version of Clerk in the payment service holds a reference to a Payment as well as the full text of the document it receives from the shopManager service (in [EventListener]). You can imagine that during the payment process a customer will add details to the payment that are stored in the payment data store. When the payment is finalized, an updated version of everything we know about the shopping session (as represented by a Clerk) is sent back to the shopManager service.
In the example updating and sending the resulting document is handled by [PaymentController] when it receives a PUT message with the customer’s credit card data. PaymentController updates the Payment with card number and date, stores this updated information in its database and then sends a new Clerk JSON message on its way to the shopManager service. There’s some implicit processing going on here (I’m not really happy with this but haven’t found a better solution yet): Clerk’s getDocument() method updates the document attribute by inserting a new version of the Payment data and then converting this to JSON. In a future version of this code I hope to find a better way. Maybe by storing the document separately from clerk in its own table and making the update-before-send more explicit.
This version of the domain code is smaller because the domain classes in each service are more focused on a single problem. This means it will be easier to make changes because a change in, say, the Shipment class in the fulfillment service will not impact payment or shopManager.