Starting the migration from monolith to microservices can be daunting. Still more daunting is to have spent a couple years on it and still not understand “what done looks like.” If you have an ORM-based monolith, there’s a strong temptation to do a data-first migration: to move a model or set of models into a CRUD service and then call it using HTTP instead of the database.
At first it seems like this is the easiest way to get to services and to “break the monolith.” The truth is that most often this path ends with a distributed monolith with tightly coupled models and APIs that, while tolerable, do not bring joy.
You can instead migrate using an API-first approach to create interfaces that you want to work with for years.
We Mean Business (Processes)
So, if not a data model-first migration, how do you start? Begin by asking what are well understood business processes both in terms of logic and the data which is transformed. For those processes, what methods exist to expose that data for transformation, and what other processes transform the same data? By implementing and testing the business process and potentially also the data stored as an API, you can see how working with it feels — or if it’s not quite the right way to look at the process of change in the system.
Keeping clear on which type of service and API you’re creating helps you make better tradeoffs in design and implementation. For example, a business process service concentrates the business logic for a set of transformations to data. A data store service concentrates data that is closely interrelated or generally changes together.
You may find yourself creating data store APIs in your monolith, as for you it’s the easiest way to expose data for transformation with external business process APIs. That’s ok. You may find yourself using data store APIs from the monolith. That’s ok too — as long as the data store API is a designed interface, not just a copy of a model. (Implementing a “anti-corruption layer” will help you keep from being dependent on assumptions built into the current set of models.)
Taking an API-first approach forces the choice of what kind of service something will be. Instead of copying models across and then putting CRUD on top of them, you need to decide how to interact with the data first, what scope it has, which transformations you’ll want to expose and which you’ll want to hide. Then, more importantly, implement the API as quickly as possible and see how it is to use from the current clients or monolith.
I’m a fan of gRPC/Protobuf-driven design for quick prototyping with tools like Truss. Even if you don’t want to use Protobuf as an interface or gRPC as an RPC method, it can help you do API-first testing quickly. It’s always acceptable to “burn the prototype” and start over once you’ve validated the API design.
Figuring out in days — instead of years — that the API you originally designed is unwieldy when doing common activities is career changing. It’s life changing.
Separate and Validate
If the cost to set up a new service in your environment is high, you may end up combining business process and data store APIs on a single service, but I strongly suggest that you keep them distinct from a modeling and implementation standpoint. The business process part of the service should use the data store APIs to do data transformations, instead of going directly against the underlying data store. You want to be continually validating your APIs’ practical usefulness and refactoring if they fall short.
Perfect Is the Enemy of Good
Taking an API-first approach to microservices migrations allows you to learn faster, to develop APIs you want to work with, and gives a clear place to address data model / interface tech debt. Rapid prototyping and validation of assumptions about use and performance will save you months or even years of time.
Don’t try to design the perfect migration plan or even the perfect interface. Prototype, use, change, evolve. Building services this way gives you a taste of the future agility you’ll experience and starts you down that path.