Recently I attended the Devoxx in Paris, I want to share what I’ve learned from the talk (in French on YouTube now when I edit this article): “Model Mitosis : ne plus se tromper entre les microservices et le monolithe”, which will be translated as “Model Mitosis: Don’t make a mistake between microservices and monoliths anymore”, by Julien Topçu and Josian Chevalier from Shodo.
Imagine we have to design an airplane booking system, we can start by observing the client’s behavior first, the client would want to search, select, and then book, so we have at least Search
and Booking
in the domain.
In the beginning, we had a monolith architecture application, which contained Search
, Booking
, and a SharedKernel
, they are all in the same domain, so there are too many responsibilities for a single domain.
When we separate these domains, we find that the Booking
and Search
's lifecycle is tightly bound, Search
needs a list of SpaceTrains to search, then Booking
needs only one Fare (named Price later) to book.
SpaceTrain in Booking
is created to answer the Search
the problem, but it’s also used to answer the Booking
problem.
Problem 1:
Dependency problem, Booking
needs Search
to provide a selected SpaceTrain so it can complete the job of booking. So the code just imported Search
into Booking
, which is coupled.
Solution 1:
Introduce in Booking
a searchId
and a service called RetrieveSearchService
, which are included in the SPI (Service Provider Interface), and will be implemented by an antiCorruptionLayer
outside of the two services, so there is no need to import Search
anymore. It’s called dependency injection.
Problem 2:
The client wants to choose a seat in either first class or second class, for different levels of comfort in these classes that the SpaceTrain can provide.
SeatLocation is declared in Booking
, but Search
needs to see if the seat is compatible with the client’s choice.
There is a Fare in SharedKernel
. It calculates the price with a taxRate
, which is retrieved from Booking
, so the two are coupled.
Solution 2:
Copy Fare and ConfortClass to both Search
and Booking
.
Rename Fare to Price in both of them.
Make it the Booking
’s responsibility to calculate the price.
Problem 3:
The Schedule
is broken because “Departure cannot be in the past”.
New booking departure time depends on the actual booking status.
Solution 3:
Duplicate Schedule
class into both Booking
and Search
.
The booking domain should check that the departure is not in the past for any new booking, and the previous booking is finished.
Schedule
in Booking
only checks if the arrival is after departure, while in Search
it checks both if departure is after now and arrival is after departure.
Finally, in SharedKernel
we have only the Amount and Currency, we can rename SharedKernel
as Money
repository, which will serve as a generic subdomain, used by both Search
and Booking
.
In conclusion, sometimes decoupling can be done by duplicating the models, or in more sophisticated cases, by specializing/customizing the model in each service.
How to find the boundaries? What is a SharedKernel
? What should be put in and what should not? Everything in the SharedKernel
must have the same meaning, the same presentation (structure and type), the same invariants, and the same business rules, for all the bounded contexts that use it, all the others should be separated.
And between teams, the SharedKernel
should not be shared.
So that’s all I’ve learned. Here is the source code in GitLab: Model Mitosis.