Abstract
This article is a result of more than a year of research and study (2019–2020) into the microservice design pattern, domain driven design (DDD), feature driven design (FDD), hexagonal (ports and adapters) architecture, and event storming. I started studying just microservices but was quickly lead to the other topics because they all seemed to have a synergy with each other. I gained a lot of knowledge during my studies, but was disappointed this knowledge was mostly theoretical. Practical questions like, “What is a domain?” or “How do I deconstruct my monolith?” were hand-waved with the answer, “It depends”. As a software architect, I felt I needed to be able to give better answers.
The purpose of this article is to make an attempt to combine all of these theoretical patterns, designs, and architectures with the ultimate goal of creating a pragmatic strategy for deconstructing a monolith into microservices. By pragmatic, I mean an actual step-by-step guide that is realistic and actionable that will get you from monolith to microservices.
Disclaimer
This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.
This post also assumes you are not Google, Facebook, Uber, Spotify, etc, who necessarily have solution patterns which are largely not applicable to most everyday applications.
Why a Strategy?
You might be asking yourself, “A pragmatic strategy for deconstructing a monolith? A step-by-step guide that is realistic and actionable?”
IMPOSSIBLE!
I bet that is what you are thinking. After all, “You don’t know MY monolith!”
That is true. I do not know YOUR monolith, but I know MONOLITHS. I also know that no strategy will ever be 100%. But what if the strategy is 90%? Or even 75%? Personally, I would rather start with some kind of strategy instead of no strategy at all.
Why is a pragmatic strategy for deconstructing a monolith needed? Why not just start coding and see what happens? Let us take a look at a few thoughts about doing this.
First, consider well-known architect Grady Booch’s comments about agile and architecture. Agile (typically scrum) is all the rage and with agile you throw away documentation and design in favor of code, code, code! The common thought is that agile will eventually get you to a good architecture. This is just not true. We all know it is not true, but we continue to do it anyway. Why? Because of the hope that agile may get you a good architecture by accident. What does Grady Booch have to say about this? “Don’t count on it!…A good architecture is intentional and must be thought out and planned by an experienced architect” (Booch, youtube, 2016). The bottom line is this: If your strategy for deconstructing your monolith is to code, code, code and wing it, then good luck!
Next, are the ever-present time and budget questions that must be answered, but how do you answer them? Managers ask, “How long will it take?”, which really translates into, “How much will it cost?” About this, Grady Booch says, if it doesn’t cost much to change, it is design, otherwise it is architecture. (Booch, youtube, 2016). Designing solutions in an existing monolith is cheap (Mas Ruiz, dev.to, 2019). Completely rewriting the monolith as microservices is not cheap. But how much “is not cheap”? To answer this question you will need to know roughly how many microservices you are going to need. Knowing this will allow you to estimate lots of other costs associated with developing, building, and running microservices. How are you going to figure how many microservices you need? To do that, you need some kind of pragmatic strategy for deconstructing a monolith. It will not be perfect, but it will get you started with making a reasonable estimate.
Finally, software architects just have to do better! The microservice design pattern is big on concepts, ideas and technology, but small on strategy. In much of my research, DDD and microservices go hand in hand as a “way to do it”. But, consider this very common DDD question: “What are the bounded contexts of my application?” A software architect may respond, “It depends” or “They will eventually emerge”. We must have better answers. We must have some kind of pragmatic strategy which enables us to answer questions like these.
The Strategy
My pragmatic strategy for deconstructing a monolith into microservices consists of five realistic and actionable steps:
Step 1. Define “Microservice”
Step 2. Deconstruct the Monolith by Features
Step 3. Understand Bounded Context
Step 4. Define Domains
Step 5. Embrace The Hexagon
That is it. Only five steps! Simple, huh? Let the endless debate begin! Well before it begins, let us at least walk through the steps.
Step 1. Define “Microservice”
What is a microservice? This is a loaded question! It needs to be answered before moving forward though. The answer is extremely important because your understanding of what a microservice is will influence how you decide to implement them. For example, if you think of a microservice as a layer of your current layered architecture (account data), then you will end up building a distributed monolith. If you think of a microservice as remote methods or utilities (pattern match this data), you will end up building a Death Star. So defining “microservice” is critical.
Of all the definitions I have seen, the one I like the best was re-published on DZone from Maruti Techlabs’s. Figure 1 shows the definition (Makadia, dzone, 2020).
Figure 1 - Techlab’s Microservice Best Practices
From figure 1, I derive my definition for “microservice”:
A microservice is a stand-alone, self-contained product that carries out a single important business feature.
This definition is my starting point for my pragmatic strategy. I specifically use the words “product” and “feature” for reasons you will discover as you continue reading. Stating that a microservice is something that is responsible for providing a business feature implies that it is something more than just a layer, method or utility.
A microservice is more than just a layer of your current architecture. Yes, a layer of your code handles account data, but this by itself is not a complete business feature. It may be something needed to implement a feature, but it is not a stand-alone, self-contained product by itself.
A microservice is more than just a method or utility of your current code base. Yes, there is some wonderful code to parse and pattern match data, but, this is not a stand-alone, self-contained business feature by itself either.
So a microservice is a stand-alone, self-contained product that carries out a single important business feature. This definition is not only clear and concise, but it also lines up perfectly with the well-known characteristics of microservices. Figure 2 shows these characteristics (Ma, medium, 2018).
Figure 2 - Characteristics of Microservices
Let us see how these characteristics line up with my microservice definition.
- Single purpose is building a product which carries out a single important business feature, not twenty different things.
- Loose coupling is the degree of interdependence between software modules (Pagade, 2021). Being stand-alone means not needing a dozen other things (other microservices) up and running in order to work successfully.
- High cohesion is the degree to which the elements inside a module belong together (Pagade, 2021). Being self-contained means that in one place (typically a single source code repository or project) you find everything you need to update when the business feature requires a change.
Although this is a great definition for microservice, there is still more work to do. This definition depends on knowing what the important business features are. Do you know what they are for you monolith? Next, we will take a look at how to really determine what the business features are of your monolith.
Step 2. Deconstruct the Monolith by Features
Now that we have defined a microservice as a stand-alone, self-contained product that carries out a single important business feature, how do you determine your monolith’s business features? Answering this question is the very heart of deconstructing the monolith.
To help answer this question, I turn to the blog To Domain Driven Design by Kevin Mas Ruiz. In his blog, he discusses how important it is to stop thinking of your code as a single application (monolith) but instead to think of it as a platform of independent products. He writes, “…we need to think [of] our business as a business platform: we don’t have a product, we have a set of products. Those products are a set of features that apply to a persona.” (Mas Ruiz, dev.to, 2019).
Did you see the word feature? This is promising because we are trying to figure out some way of generating a list of business features of the monolith.
To further illustrate how to think about your application as a platform of independent products instead of a monolith, Mas Ruiz writes, “For example, based on this pattern, we can define our shopping platform like in the following diagram:”
Figure 3 - Shopping Platform
In figure 3 above, Mas Ruiz uses the green stickies to represent what he calls a product of the shopping platform. Since each product contains one or more features, the green stickies also represent the features of the shopping platform.
What is interesting about Mas Ruiz using green stickies is that green stickies are also used in Event Storming (you starting to see the synergy of these concepts?). In Event Storming, green stickies represent a view (Brandolini, 2018). A view is typically a webpage, but it can represent any kind of user interface of an application. Mas Ruiz uses green stickies to represent a product/feature of the platform and Brandolini uses green stickies to represent a user interface of an application. Combining these ideas together leads to the following:
green stickie == business feature == webpage
This equivalency is important because it hints at a way to determine your monolith’s business features. Try this…
Login to your monolith. As you click through it, create a list of every webpage with its name/title/description. Supplement with a screenshot if you want. Continue until you have visited every webpage of your monolith. Once you are done…
Congratulations! You have just finished deconstructing your monolith into microservices.
WAIT!
WHAT?
You probably think I have lost my mind. Maybe. Remember my pragmatic strategy started with a definition for microservice. Let us revisit the definition.
A microservice is a stand-alone, self-contained product that carries out a single important business feature.
In a monolith, each webpage is (typically) created for a specific reason. In other words, each webpage carries out a single important business feature. Therefore, we can extend the equivalency above to include microservice:
green stickie == business feature == webpage == microservice
This equivalency basically means a monolith can be deconstructed into microservices by simply implementing each webpage as its own stand-alone, self-contained product. I can already hear you screaming objections! That is OK. I understand that deconstructing a monolith so each webpage becomes its own product will immediately raise a LOT of objections. However, as Mas Ruiz has suggested, we need to stop thinking of our application as a single solution (monolith) and instead think of the application as a platform of independent products which all work together to provide a solution. I hope that my reasoning for deconstructing a monolith so each page becomes a product of the platform solution has some validity.
Remember, my purpose is to attempt to create a pragmatic strategy for deconstructing a monolith. No strategy will EVER be 100%, and that is OK. Starting with some strategy is always better than starting with no strategy at all. May it be the case that multiple webpages are group together into a microservice? Of course! But for a strategy, you need to start somewhere.
If you are still with me, congratulations! I know it is not easy and you are probably hesitant. Now that you know each webpage of your monolith will become a microservice, the next thing we want to look at is how this relates to DDD bounded contexts.
Step 3. Understand Bounded Context
Domain Driven Design is…interesting to say the least. Lots of theory, little practice. I want to discuss DDD as part of my pragmatic strategy because if you research “how to implement microservices”, you will find suggestions that DDD bounded contexts can be used to define a microservice. The idea is that if you know what a “bounded context” is, then you know how to define - and therefore implement - a microservice. At least that is the theory.
Let us start by defining a DDD bounded context.
A bounded context is a boundary inside of which lives multiple components of a software model. A component has a specific meaning and behavior within the boundary. Behavior within a bounded context is idempotent and/or ACID. A component with the same name - but with different meaning and behavior - must exist in its own boundary. (Vernon, 2016, p. 327)
Given this definition for a DDD bounded context, recall Step #2 of my pragmatic strategy that I made this radically controversial equivalency:
business feature == webpage == microservice
This equivalency basically means a monolith can be deconstructed into microservices by simply implementing each webpage as its own stand-alone, self-contained product. Now let us propose that a DDD bounded context also defines a microservice. If this is true, it would mean bounded context can be added to the equivalency like this:
webpage == business feature == microservice == bounded context
However, is this equivalency true? Can bounded context just be thrown in there? The answer is Yes. Let us break down the definition of bounded context to see why.
A bounded context contains software model components that have very specific meaning inside that bounded context. In the DDD world, this specific meaning is how you create a ubiquitous language (UL). UL is the most important concept in DDD. Everything in DDD is built on UL. UL is intended to define exact meanings for words so that when you use those words everyone understands what you are talking about. Let me illustrate with an example.
Suppose you get the following request from a security auditor: “Please provide a list of all the log files on the server”.
WAIT A MINUTE!
A server can have dozens of different log files for dozens of different services running on the server. Plus, there are lots of different servers performing different tasks all throughout your environment. So what log files is the security auditor looking for? For what services? For what servers? These questions demonstrate the need for a UL within a context. The problem is not with the auditor’s request. The problem is that for the context of the request - security audit - the meanings of the words “server” and “log file” have not been adequately defined. In order to respond to the auditor’s request, the team must first get a meeting with the auditor, ask a bunch of questions, and determine what exactly the auditor means by “server” and “log files”. At the meeting, the team learns the auditor is interested in remote SSH connections to the VM running the database. Now the words “server” and “log files” have a very specific meanings. A UL for the context - security audit - has been established so everyone can understand each other when the words “server” and “log files” are used.
Now suppose there is a production problem with the application. In order to troubleshoot, the team starts a conversation about getting the “log files on the server”. Does the team confuse this conversation with the security audit conversation? No, of course not! But why not? After all, the same words, “server” and “log files”, are being used. The team does not get confused because there are two different contexts: security audit and production problem. Within each context, “server” and “log files” have specific and different meanings. Bringing this back to DDD, “security audit” and “production problem” are bounded contexts and “server” and “log files” are software model components.
Now that we have a better understanding of bounded context, how does it relate to my pragmatic strategy? To answer this question, let us look at a common data-related objection to my strategy. The objection goes something like this:
20 different webpages of the monolith use policy data, so they should all be in the same microservice, right? But if I put 20 webpages in the same microservice, that gets me right back to building a monolith. So I think I will need to extract policy as its own microservice. But policy is not a stand-alone, self-contained business feature by itself, so if I extract it on its own then I will start to have tight coupling between microservices. Oh boy!
Mas Ruiz addresses this objection when discussing cross-product modules. He gives the illustration shown in figure 4. The “1 click purchase” feature seems to need the same data as the “Purchase” feature (Mas Ruiz, dev.to, 2019). If that is the case, then it would seem both features should collapse together instead of being independent. But before doing that, Mas Ruiz says to dig deeper.
Figure 4 - Cross-Product Modules
Mas Ruiz uses the word “modules” to describe the orange boxes in figure 4. These orange boxes represent the data needed for both “1 click purchase” and “Purchase”. Notice the words in the orange boxes are identical!
NOTE Do not get these orange boxes mixed up with Event Storming orange stickies. In this case, there is no similarity between the two.
BUT WAIT!
We have seen this identical word problem before when breaking down the definition of bounded context…remember the “server” and “log files” example above? Yes, in figure 4, the words “buyers”, “books”, “stock”, and “shipment” are used for both the “1 click purchase” feature and the “Purchase” feature. But Mas Ruiz’s point is if you dig deeper into them you will discover the views of the data for each feature will be different. So within the “1 click purchase” feature, “books” has one meaning and in the “Purchase” context, “books” has a different meaning. This is the whole point of DDD bounded contexts.
If you think about it, this should not be surprising. Each webpage is created to carry out a specific business feature. Each business feature has a different (if somewhat subtle) view of the data and operations on the data. In fact, if the view and operations were exactly the same, you would have multiple pages performing the exact same business feature!
NOTE This is not outside the realm of possibility. Over time, you will lose both the development and business teams that first created the application. Knowledge is lost, so features may be forgotten and re-implemented.
So is this equivalency true?
webpage == business feature == microservice == bounded context
The answer is yes! A webpage is created to perform some specific business feature. Within the context of this business feature, the view and operations on the data has a very specific meaning which does not exist on any other webpage. Therefore, it can and should be implemented as a stand-alone, self-contained product as my microservice definition suggests.
NOTE If you think back to the 2000 year time frame when service-oriented architecture (SOA) was all the rage, you can now understand why that concept failed so miserably. SOA set out to be the one-definition-to-rule-them-all so to speak. If you needed policy information, SOA tried to centrally define “Policy” for the ENTIRE organization. DDD bounded contexts tell us you just can not do that!
BUT WAIT!
Without a doubt, different webpages will need to use similar, but not identical, data. If each webpage is its own microservice and is developed as a stand-alone, self-contained, independently-deployable product, how does each microservice get the data it needs?
Great question! For possible answers, go to Micoservices.io and on the homepage search for “data management”. On Micoservices.io, Chris Richardson documents a number of different strategies for how to handle data and databases. Spoiler alert, it is not easy! This is especially true when considering ACID requirements of business operations. I specifically say “business operations” here instead of transactions because the requirement should be that when the button is clicked, everything gets done. It does not matter if it is transactional or not, just as long as everything gets done.
My recommendation for data management is the shared database pattern (Richardson, n.d., Pattern: Shared database). For implementation, keep microservices isolated to a database schema created just for that microservice. The data in the schema is handled in various ways including:
- TABLE. Data is owned by that schema (microservice).
- VIEW & MATERIALIZED VIEW. Read-only sharing of data from other schemas (microservices).
- TRIGGER Read-only copies of data from other schemas (microservices).
Handling data is a very, very complicated topic unto itself. For microservices, the one-database-per-microservice diagram is what everyone loves to see and therefore what everyone strives to implement. But, given its massive complications, is it worth it? (Richardson, n.d. Virtual bootcamp:…). Like my post disclaimer says, unless you are Google, Facebook, Uber, etc., probably not! I highly recommend you research data strategies on your own.
Step 4. Define Domains
The next thing to do in this pragmatic strategy is to define domains. For DDD, Defining domains would seem to be a very important because, after all, it is in the name: Domain Driven Design. I think the bounded context is a much more important DDD concept, but domains have value too.
Despite the word “domain” being part of the name Domain Driven Design, a domain is actually one of the most confusing and least explained concepts of DDD. In Domain-Driven Design Distilled, Vernon does not give a definition of domain. The best we get is a discussion about something called a core domain.
“When compared with all the software your organization uses, a Core Domain is a software model that ranks among the most important, because it is a means to achieve greatness. A Core Domain is developed to distinguish your organization competitively from all others. At the very least it addresses a major line of business. Your organization can’t excel at everything and shouldn’t even try. So you choose wisely what should be part of your Core Domain and what should not. This is the primary value proposition of DDD, and you want to invest appropriately by committing your best resources to a Core Domain.” (Vernon, 2016, pg. 13).
This really does not define domain. It gets even more confusing because in addition to domains there are also subdomains. The words “domain” and “subdomain” seem to be used interchangeably adding even more confusion. For example, Vernon lists three types of subdomains:
Types of Subdomains
- Core Domain
- Supporting Subdomain
- Generic Subdomain (Vernon, 2016, pg. 46)
Now wait just a minute. “Core Domain” is a type of subdomain? That does not make any sense. “Core Domain” should be a domain right? It is in the name! So why is it listed as a type of subdomain? If it is a type of subdomain, then why is it not called “Core Subdomain”? If this is not confusing enough, an attempt is made to establish a relationship between domain/subdomain and bounded context. You can imagine how well this goes.
For my pragmatic strategy, I ignore all of the DDD book treatments of domain and subdomain because they just do not work or make any sense.
My definition of domain:
A domain is a high-level, one- or two-word description that can be used to logically group together the stand-alone, self-contained products that carry out the important business features of the application platform.
That is it. Very simple. Here are examples of domains I have encountered a lot:
- Reporting
- Configuration
- Administration
- Health Check
- Performance
Get the idea? If your application gets a lot of data from 3rd parties, you may have a Data Processing domain. If your application converts files into different formats, you may have a Converter domain. If your application processes orders, you may have an Order Processing domain.
An application should have a relatively few number of domains meaning that all of the features of the application should logically fit into relatively few categories. If you find yourself writing down dozens of different domains, then either one of two things are happening.
- Your monolith is big hodgepodge of a lot of different responsibilities. This is very telling!
- What you are writing down is not high-level enough. If you have ABC File Import, XYZ File Processing, and 123 Data Validation as domains, all three of these can probably be assigned to a Data Processing domain.
Try this. Remember that list of webpages from Step #2 of my pragmatic strategy? For each one, assign it a one- or two-word, high-level category. Do this for every line of the list.
Congratulations! You just assigned a domain to every product of your application platform!
NOW WHAT?
There is some value in assigning domains. Domains can help make return on investment (ROI) decisions. Take the Reporting domain for example. Would it be better for the software developers to code custom reports into the application? Or, would it be better to purchase a reporting tool and allow users to create whatever reports they want? This is an ROI decision. If the primary purpose of your application is reporting on extremely complicated data, then it is best for the software developers to code them. If the primary purpose of your application is something else, then buying a reporting tool to free up developers' time might be a better ROI. If you find yourself writing down dozens of different domains, that may indicate you application is performing a lot of unrelated business functions and maybe some of them are the responsibility of other teams? Or maybe new teams need to be formed? Again, ROI decisions for the organization.
Overall, the domain concept is not difficult as long as you avoid the DDD book treatments and stick with my definition.
Step 5. Embrace the Hexagon
The final step of my pragmatic strategy is to embrace the Hexagon. What in the world does this mean?
If you go through Steps 1–4, you will get a good idea of what your application will look like - on paper - rewritten from monolith to microservices. The paper can be used for project planning, budgeting, time estimates, event storming, and lots of other things. However, what you do not have yet is code. Remember, “A good architecture is intentional and must be thought out and planned by an experienced architect” (Booch, youtube, 2016). Now that you have done this, software developers will eventually need to start writing code. The Hexagon will help make sure code is organized properly.
Figure 5 - O’Reilly Plan First Code Later Sticker
Alistair Cockburn documented the hexagonal (ports and adapters) architecture in a work published in 2005. Cockburn stated the intent of the architecture is to “Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.” (Cockburn, 2005). The hexagonal layers of the architecture allows code to be organized and written to adhere to the intent of the architecture. Figure 6 gives an overview of the layers.
Figure 6 - Hexagonal Architecture Layers
Hexagonal Architecture is a huge topic all by itself. I encourage you to research it more on you own. Here is a high-level summary, describing the purpose of each layer.
UI
The UI is not actually one of the layers, it is everything outside of the hexagon. It is called UI because it is common for some kind of user interface to want to interact with your application. However, as shown in figure 6, there are lots of other ways of interacting with your application, such as HTTP, JMS, SMTP, and SFTP. Each interaction has a unique protocol it uses to communicate with your application. When communication does occur, it happens with the outer most hexagon layer, the framework layer.
Framework Layer
The Framework layer is responsible for:
- Accepting a raw driver request through some communication protocol.
- Converting a raw request into an application specific request, then sending this application specific request down to the next layer, the Application layer.
- Converting the application specific response into a raw driver response and sending the response back through the communication protocol.
Typically the Framework layer is the smallest of the layers since raw driver requests are simply converted and passed to the application layer for handling. The Framework layer does not contain any business code. The Framework layer communicates with the Application layer through an application primary port. This is simply a fancy way of saying an instance of an Application layer class is injected into an instance of a Framework layer class.
A Spring @RestController
is a perfect example of a Framework layer class. Its primary responsibility is to accept a raw HTTP protocol request, do something with it, then send a raw HTTP protocol response back. The “do something with it” part is handled by an Application layer class that gets created and injected into the @RestController
by Spring. Next, we will look at these Application layer classes in more detail.
Application Layer
The Application layer is the hardest layer to understand because of its unique position between the Framework layer and the Domain layer. The Application layer is responsible for:
- Converting an application specific request into an orchestration of domain operations performed by the Domain layer.
- Providing the Domain layer concrete implementations of the exit points out of the Domain layer.
- Converting the domain response into an application specific response, sending the response back up to the Framework layer.
Let us take a look at each of these responsibilities in more detail.
First, the Application layer takes an application specific request and converts it into an orchestration of domain operations performed by the Domain layer. Like the Framework layer, the Application layer does not contain any business code. But the Application layer does know what Domain layer operations need to be executed to fulfill the application specific request. The Application layer communicates with the Domain layer through a domain primary port. This is simply a fancy way of saying an instance of a Domain layer class is injected into an instance of an Application layer class.
Second, the Application layer is responsible for providing the Domain layer with concrete implementations of the exit points out of Domain layer. What are exit points? Well, at some point, the code in the Domain layer is going to need to communicate with the outside world. Databases are the most well known example of this kind of communication. Other examples are message brokers, email, and files. Suppose code in the Domain layer needs to retrieve and store data. This is an exit point. Though the Domain layer needs to retrieve and store data, it does not need to know how the data is retrieved and stored. Because it does not need to know the how, those operations are defined by interfaces only. It is then the responsibility of the Application layer to provide concrete implementations of these interfaces so that when a dependency injection engine (CDI, Spring, etc.) wires everything together, the data is retrieved and stored in a manner that best suites the application. The interfaces in the Domain layer are the domain secondary ports and the classes in the Application layer that implement these interfaces are the application secondary adapters.
Third, any responses from the Domain layer are converted into an application specific response and passed back up to the Framework layer. Remember the Framework layer then takes this application specific response and converts it into a raw driver response and sends the response back through the communication protocol.
Domain Layer
Finally is the inner most layer, the Domain layer. This is the most important layer because it is finally in this layer where all of the business code is located. The Framework layer and Application layer have specific responsibilities, but are basically pass throughs to the Domain layer.
Being protected by the other layers makes the code in the Domain layer very easily reusable. The code has no knowledge of the HTTP protocol or how to talk to a database. So as your application evolves, the code in the Domain layer should easily evolve with it. And that is what you want. For example, suppose you are changing from an N-Tier architecture based on Struts to a microservice architecture based on Angular and Spring boot. The Domain layer, with all the business code, should not care. It should be equally reusable in both applications. If the architecture change reveals the need for code in the Domain layer, the code in the Domain layer was not created correctly. Similarly, if the application’s architecture further evolves into use cloud-native services (like AWS Lambda), again, the Domain layer, with all the business code, should be reusable without any changes. This is a tall order and takes discipline on the software development team to achieve. Knowing the responsibilities of each hexagon layer helps achieve it.
Being protected by the other layers makes the code in the Domain layer very easily testable. Because interfaces are used to define exit point operations, unit test frameworks need only to mock these interfaces with suitable responses for each test. If unit tests require the startup of a complex testing runtime to simulate a database or other external resources, the unit tests are wrong. Keep the focus on testing the business code, not simulating a runtime environment. If unit tests are written this way, it fulfills the primary intent of the hexagonal architecture, which is to “Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.” (Cockburn, 2005).
This was a very quick and very high-level review of hexagonal architecture. Again, I encourage you to research it more on your own. Now that you understand the purpose of each layer, we can translate this into code.
Code
When the team starts coding, the first thing to do is create a source code repository for each microservice. Remember, a microservice is a stand-alone, self-contained product that carries out a single important business feature, so the code for a microservice should exist in its own source code repository. Suppose you need to code the ABC Reconciliation Report business feature. Create a source code repository with the following name:
abc-reconciliation-report
Now that you have a repository, what do you put in it? Follow The Hexagon and create projects based on the layers. Physically these are file system folders created inside the repository. Logically these folders are referred to by different names depending on the technology you are using. If you are using Java and Maven, you may refer to these folders as modules of a multi-module Maven project. If you are using C#, you may refer to these folders as projects of a solution. For simplicity, I will refer to them as projects. What you name the projects identifies the layer of The Hexagon the code is for and also declares the intent of the code. For example, you may have projects which look like this:
abc-reconciliation-report
/api
/jms
/application
/domain
/ui
/api A hexagon Framework layer module containing REST endpoints. Provides a way the outside world may interact with the domain. Responsible for translating HTTP(S) requests into application layer objects for processing.
/jms A hexagon Framework layer module containing messaging (JMS) listeners. Provides a way the outside world may interact with the domain. Responsible for translating raw JMS messages into application layer objects for processing.
NOTE You may be asking, “Why not name the project ‘framework’ and put all Framework layer code in one project?” Recall from figure 6, There may be many different UI components that want to communicate with your application and each component may use a different protocol for communication. You want to separate the protocol handlers into different projects. So a project for handling HTTP REST communication should be separate from the project handling JMS message communication. Following The Hexagon, each project can re-use the code from the Application and Domain layers.
/application A hexagon Application layer module containing domain orchestration and domain implementations. These implementations provide a way the domain may interact with the outside world (exit points).
/domain A hexagon Domain layer containing the important business code. This layer should have no knowledge of the Framework or Application layer code. It should be easily testable without any external runtime environments and easily reusable as Framework or Application layer code evolves.
/ui A UI compoent. Responsible for translating user input into HTTP(S) - typically - to interact with /api for requests.
As you can tell by the the names and responsibilities of these projects, everything for the abc-reconciliation-report is contained in a single repository. This really helps the code adhere to the characteristics of a microservice. Let us revisit these characteristics (Ma, medium, 2018) and see how well code code matches up.
- Single purpose. The name of the repository is abc-reconciliation-report. I think that pretty much defines its purpose. If the code is doing anything other than this, the software developers got the code wrong.
- Loose coupling. Hopefully, the Application layer code which implements the Domain layer exit points keeps coupling loose. If not, the software developers got the code wrong.
- High cohesion. If you need to make a change to the ABC Reconciliation Report, it should be clear that this repository is the only place you need to go. If not, the software developers got the code wrong.
Summary
That is it! As a software architect, you can use steps 1–4 of my pragmatic strategy to plan the architectural shift from monolith to microservices. As a software developer, you can use step 5 to organize the source code repositories and the contents of each repository and get coding!
It has been a journey, right? Here is a summary.
I wanted to learn how to deconstruct a monolith into microservices. To learn this, I studied microservice design pattern, domain driven design (DDD), feature driven design (FDD), hexagonal (ports and adapters) architecture, and event storming. After a year, I learned a lot of theory but little practice. So I decided to create my own pragmatic strategy for deconstructing a monolith into microservices; A step-by-step guide that is realistic and actionable.
My strategy is implemented with five steps:
Step 1. Define “Microservice” A microservice is a stand-alone, self-contained product that carries out a single important business feature.
Step 2. Deconstruct the Monolith by Features
business feature == webpage == microservice.
Click through your monolith and create a list of every webpage. Congratulations! You have just finished deconstructing your monolith into microservices
Step 3. Understand Bounded Context
webpage == business feature == microservice == bounded context.
A webpage defines a bounded context because the data and behavior on that webpage is unique and not found on other webpages.
Step 4. Define Domains A domain is a high-level, one- or two-word description that can be used to logically group together the stand-alone, self-contained products that carry out the important business features of the application platform (microservices). Go get the list of all the webpages of your application. For each line, write down a one- or two-word, high-level description of the product. You have just assigned a domain to every product of your application platform. Use domains to make ROI decisions.
Step 5. Embrace The Hexagon Use the concepts from hexagonal architecture to organize the source code repositories and the contents of each repository so that software developers know which code goes in which projects so code with similar responsibilities stay together in the same layer.
That is it. Get started. Have fun. Remember no strategy is 100%, so be pragmatic, but also be flexible.
References
Booch, Grady. (2016, June 30). SATURN 2016 Keynote: Architecting The Unknown with Grady Booch [VIDEO]. https://www.youtube.com/watch?v=RJ3v5cSNcB8&t=2685s.
Makadia, Mitul. (2020, February, 26). Break a Monolith to Microservices — 12 Best Practices and Design Principles. https://dzone.com/articles/break-a-monolith-to-microservices-12-best-practice.
Mas Ruiz, Kevin. (2019, December 2). To Domain Driven Design. https://dev.to/kmruiz/to-domain-driven-design-6ao.
Brandolini, Alberto. (2018, November 7). 50,000 Orange Stickies Later - Alberto Brandolini - GOTO 2018. https://www.youtube.com/watch?v=NGXl1D-KwRI&ab_channel=GOTOConferences.
Ma, Xiao. (2018, October 17). Microservice Architecture at Medium. https://medium.engineering/microservice-architecture-at-medium-9c33805eb74f.
Fowler, Martin (2011, July 14). CQRS. https://martinfowler.com/bliki/CQRS.html.
Cockburn, Alistair. (2005, April 01). Hexagonal architecture. alistair.cockburn.us. https://alistair.cockburn.us/hexagonal-architecture/.
Vernon, Vaughn. (2016). Domain-Driven Design Distilled. Addison-Wesley
Richardson, Chris. (n.d.). Microservice Architecture. Microservices.io. https://microservices.io/.
Richardson, Chris. (n.d.). Pattern: Shared database. https://microservices.io/patterns/data/shared-database.html.
Richardson, Chris (n.d.). Virtual bootcamp: Distributed data patterns in a Microservice architecture. https://www.chrisrichardson.net/virtual-bootcamp-distributed-data-management.html.
Pagade, Ganesh. (2021, May 25). Difference Between Cohesion and Coupling. https://www.baeldung.com/cs/cohesion-vs-coupling.
Cockburn, Alistair. (2005, January 4). Hexagonal architecture. https://alistair.cockburn.us/hexagonal-architecture/.