This post is an introduction to Domain-Driven Design on AWS. It provides guidance on how to identify business domains within legacy monolithic applications, and how these can be decomposed into a collection of microservices. Starting with a Domain-Driven Design for your microservices will help you garner the benefits of cloud scale in your newly refactored application. Recently AWS announced general availability for AWS Migration Hub – Refactor Spaces. This feature helps you to implement the strangler fig pattern to operate both the legacy and refactored applications across multiple AWS accounts.

Benefits of microservices

Companies are increasingly looking to transform legacy monolithic applications into a collection of microservices to start to take advantage of benefits such as:

  • increased business and technical agility – microservices are less complex to manage and maintain, since each service is only responsible for one task.
  • increased scalability – moving to a microservices architecture lets companies use cloud native technologies. With AWS these typically use serverless compute such as AWS Lambda, Amazon Elastic Container Service, or AWS Fargate. Using these services will help you to scale independently to meet demand, so you only pay for resources as you need them.
  • increased resiliency – microservices are more loosely coupled than a single monolithic application. This means that if one service is unavailable for any reason, then the rest of the application services are unaffected.

However, refactoring applications can be challenging and requires significant time and effort to implement. One of the primary challenges is how to identify what each microservice should do in the new environment. Or, in other words, how do you start to decompose the monolith? There are a few different patterns that can be applied to try and identify the domains of each new microservice. For example, consider a customer facing banking web application.

Example decomposition patterns could include:

  • Transaction type– in this example, one microservice for scheduling a recurring payment, for making a one-off payment, and for a user to change their personal details would be created. This may lead to an improved customer experience with lower latency, since each interaction will not require additional network communication between services. However, it can also lead to the creation of a ‘distributed monolith.’ This happens when services are still tightly coupled because they rely on multiple services across different parts of the business. This makes them harder to maintain and update.
  • Organizational teams– in this example, each development team in the business is responsible for their own microservices. This has advantages with teams being able to innovate and deliver incremental improvements to their service without relying on other teams. Even though each service can operate and scale independently, it can be difficult to release larger changes when dependencies remain across services. This is further compounded when service changes may not be aligned to business goals in the same way for both teams. For example, microservices would be developed separately for the sales department, marketing department, and customer service departments. Because each department may interact with different parts of the legacy application, this architecture has a benefit of delivering software aligned to defined business needs as opposed to purely technological capabilities. However, a disadvantage of this approach is typically the new microservices are tightly coupled to the business model. They are impacted by team and organizational changes that make them difficult to update.
  • Business subdomain – this uses a pattern similar to the organizational teams. However, it breaks down the business capabilities into further subdomains. These subdomains are typically bounded by their use as well, which lends for more alignment to customer needs. For example, customer service may have a microservice for updating customer details, another for raising support cases, and one for registering for loyalty programs. Meanwhile, the marketing department may be divided into launching new campaigns, and generating business insights through analytics. Dividing by business subdomain leads to loosely coupled architectures with services that are aligned to business goals. Each service can scale independently, and is maintainable and extensible. Some alignment with business leaders is required to identify what these subdomains are, which is detailed in the rest of this article.

Domain Driven Design

To decompose a monolith by business subdomains, you can apply Domain Driven Design principles. Domain Driven Design helps to facilitate the delivered software aligns to defined business goals, and therefore the needs of your end customers. Event storming and context mapping are two approaches that can be used to help identify the domains and how they correspond to your defined services. Once you have defined your subdomains, you can begin implementing the new microservices architecture that is optimized for the cloud.

AWS Migration Hub Refactor Spaces lets you use the Strangler Fig pattern to incrementally refactor the monolith into new microservices. This lets you operate the existing application, while it is evolving, and incrementally route traffic to the new services as you create them.

Domain Driven Design requires a deep understanding of the business. It requires time and commitment from both the business experts and the technical implementers. Domain Driven Design should not be used in situations where you require ‘quick-wins.’ Instead, use Domain Driven Design for software that supports the core business domain, rather than for supporting domains. Beginning Domain Driven Design can be achieved through a session of event storming. However, as mentioned before, this is a commitment worth making. It will let you develop software that is more strongly aligned with the needs of the end customers. Also, this helps to create decoupled services that are more scalable and maintainable. The combination of these results with increased business agility.

Domain Driven Design may not be suited for all software. For example, CRUD-based services are typically transactional and data driven. However, if they are distributed across many services, this can add unnecessary complexity. In this case, Domain Driven Design may not be a strong fit because it requires coordination between services for simple transactions. It is important to evaluate if you have the licenses required to decompose the application sufficiently. That is, when you don’t fully own the code for an application (for example, commercial software), it may not be viable to break into subdomains using Domain Driven Design.

Event Storming and Bounded Contexts

Event storming is a popular type of workshop that helps both business and technology teams align on what a solution should deliver. This is done without being distracted with the specifics of how it will be implemented. This means it may take longer for teams to start delivering source code. However, all of the teams will see better alignment regarding what each microservice should be responsible for. As a result of event storming, stakeholders end up speaking a ‘ubiquitous language’, such that there is little confusion around what the solution should do.

The workshop for event storming is a brainstorming exercise. In this session, all of the stakeholders of the solution work together to identify business events that correspond to domains. In the banking example, a business event may be a customer who applies for a new account. In the workshop the group will begin to identify the entity that triggered the event, the processes that must happen as a result, and any subsequent event that is triggered from the source event.

Following an event storming session, teams can identify the domains of their business, and then the contexts in which they operate. These can be used to identify use for and the communication that happens between each microservice and its context. Once the domains are defined with the help of the business experts, then the technical implementers can then begin designing the solution.

The resulting artifact from the event storming session is a domain model. You can use the domain model to identify a series of bounded contexts. A bounded context is the boundary where each domain is applied. The account opening example can be thought of as the bank’s ‘Account Onboarding Context.’ In the full system there would be other contexts, such as a payment context, transaction context, and fraud context. Identifying the business events that cause communication between different bounded contexts helps identify how your microservices will communicate with each other in the new architecture.

An example process of a customer applying for a new account shows the process from verification through approval, notification, and account opening.

This example context map provides only a sample of the core domains that a banking application may include. There will also be a number of supporting and subsequent domains. For example, shipping a new debit card to a customer after account opening is completed. Although it is necessary to have a service that manages this, it is not part of the core domain of the account opening application.

A context map shows the core domains of accounts, transactions, payments, fraud, and account onboarding, and how they interact with supporting domains such as mailing and printing.

Incremental Refactoring to Microservices

After you have decomposed your monolith, the next question is how to manage which requests are routed to the monolith or sent to new microservices. This task becomes simpler with the launch AWS Migration Hub Refactor Spaces. With your AWS Refactor Spaces Environment, you can configure an application proxy to both the legacy and refactored services. At the outset, the monolith application’s endpoint will be the default service. This is so that traffic is still routed to the legacy application until the new microservices are developed. Once a microservice is ready to be used, you can add a service and route for it to the Refactor Spaces application. This is done while shielding your end consumers from the infrastructure and application changes that have taken place. In addition, AWS Refactor Spaces manages the network fabric for you. It orchestrates the API Gateway and Transit Gateway the environment to allow you to route the traffic to the new service. Thereafter, you can incrementally add new services to the Refactor Spaces application as you use the Strangler Fig pattern. This is done so that eventually all of the traffic is shifted from the monolith to be handled by the new microservices.

The following diagram shows how the monolith receives some of the traffic while you are incrementally adding in the new microservices in the banking application example. The application users are shielded from the changes from the monolith to the microservice.

An architecture diagram depicting a user making a request through the refactor spaces AWS account. Refactor Spaces directs the traffic to either the legacy application, or the newly refactored microservices accounts whilst shielding the end user from the changes.

Conclusion

In this blog we have shown how to decompose monolithic applications into a collection of microservices using Domain Driven Design through event storming workshops, and how AWS Migration Hub Refactor Spaces can take out the heavy lifting as you begin to take advantage of cloud native technologies to run these microservices.

To find out more about AWS Migration Hub Refactor Spaces, visit the Migration Hub console.

About the author

Jake Walker

Jake is an Associate Solutions Architect based in London with a software development background, and joined AWS on a graduate scheme in September 2020. He specializes in migrations and modernization and enjoys helping customers modernize to take advantage of cloud native technologies.

Categories: Management Tools