Strangling a monolith with Axon Server
Transforming large legacy monoliths into decoupled, scalable microservices systems is more complex than can be imagined. This blog describes how using Axon Server with the strangler pattern can help simplify this process and speed up the delivery time. Please consider this is general advice that, whilst applied in many cases, requires careful consideration before using it in your particular scenario.
In the past, software applications were built as monoliths until the Service-oriented Architecture approach became popular. Nowadays, many monoliths are still maintained and built because they represent valuable business logic.
Monoliths are easy to deploy and can have a good performance because of the usage of shared memory. Still, there are several reasons to move away from this way of working to a microservices approach:
- if the application has evolved into a big ball of mud
- tight coupling forces you to test and deploy the whole application when adding features
- when the application has become too large and too complex to be able to do automated regression tests
- when starting up, the application becomes very slow
In monoliths (which have not become a big ball of mud), you can distinguish between several functional parts as shown in this simplified example of an e-commerce system:
When a new system is built up from scratch, it is easy to set up a microservices landscape, but there is the burden of an existing system in most cases. To build a copy of the “old system” from scratch costs a lot of money because, for a long period, two systems have to be maintained, and therefore, another approach is desired. This is where the strangler pattern (described by Martin Fowler) comes in: the monolith is not replaced in one high-risk big-bang release but incrementally. Features are replaced piece by piece by microservices, slowly strangling the monolith.
Services in a monolith are called directly, and therefore adding a new microservice for that service adds boilerplate code to your monolith as well as to your new microservice (calling functionality in another service with REST or AMQP). This should happen for every call, and you’ll end up with many pointers from and to services. It’s tempting to create dependencies between services, and when times go by, you could end up with a distributed monolith.
Another challenge is service discovery in a microservice environment. A developer should not worry about where to find the service; it should be location transparent. Axon Framework and Axon Server minimize the lines of code needed for communication between services so that the developer can focus on the business logic.
The first thing that needs to be done is adding Axon Framework to your monolith (or creating a microservice as an interface) to enable communication between the monolith and Axon Server. This implies adding a Command Bus, Query Bus, and Event Bus to send messages.
After having Axon Server installed, it’s recommended to extract a completely independent part of the monolith. So, for the e-commerce example, we’ll zoom into the payment service. The payment service is the interface between the cart and the (external) Payment Service Provider.
The next step is to create a microservice that implements all the payment logic supported by the monolith. First, find the border between the monolith and the payment logic. Sometimes the border is not clear, and you might need to create a sharper border separating concerns into account. Next, find all the commands that should be done at this border, for example, PayOrderCommand or RefundOrderCommand. These commands are sent via Axon Server to the new microservice. The payment service handles the commands and will send Events like OrderPayedEvent or PaymentCancelledEvent. When the payment service needs extra info, e.g., the customer's name, it can send a CustomerInfoQuery via Axon Server. In the monolith in the Axon Framework Interface, you can provide this information by implementing a Query Handler. Later on, when maintaining account information is moved to a separate service, the query can be handled.
By repeating the process of isolating and extracting functionality, all features can be moved to a microservice, and the monolith can be decommissioned:
In the end, the empty monolith and temporary interface with Axon Server can be removed:
It’s not necessary to do this whole decomposition; there’s always a choice to leave some parts of the monolith as-is.
Conclusion
When the decision is made to transition from an existing monolith to a microservice solution, the strangler pattern is an agile approach to accomplish this. Using Axon Server for this exercise makes service location transparent and diminishes the number of lines of code needed for connecting.