Serverless Domain Driven Design
Breaking down DDD concepts one at a time with real world examples into a tangible serverless equivalent architecture.
👇 Before we go any further — please connect with me on LinkedIn for future blog posts and Serverless news https://www.linkedin.com/in/lee-james-gilmore/
What is Domain Driven Design?
According to Wikipedia, Domain Driven Design, which was created in 2003 by Eric Evans, can be described as:
“Domain-driven design (DDD) is a software design approach focusing on modelling software to match a domain according to input from that domain’s experts.” — https://en.wikipedia.org/wiki/Domain-driven_design
Domain driven design is predicated on the following goals:
- Placing the project’s primary focus on the core domain and domain logic.
- Basing complex designs on a model of the domain.
- Initiating a creative collaboration between technical and domain experts to iteratively refine a conceptual model that addresses particular domain problems.
In my opinion DDD should be at the forefront of architects and engineering teams minds when building out serverless services within an enterprise organisation, especially when using the Serverless Architecture Layers, and creating our Domain Services for the ‘Domain Layer’:
Serverless Architecture Layers 🚀
How and why I believe enterprise organisations should use domain-driven design with the five serverless architecture…
With the concepts being over 20 years old and ‘fuzzy’ in places, this is my own interpretation of how the concepts can be modelled with Serverless architectures.
What is Domain Driven Design in Serverless?
Below we will go through key concepts of Domain Driven Design (DDD) when it comes to serverless architecture, and how this has a direct impact on our Serverless Domain Services within the Serverless Architecture Layers:
Domain (and Domain Service)
A domain is a subject area where the software will be applied, for example an overall e-commerce domain (shown above). As we split this further into sub domains (diagram below) we find that we have sub domains such as Order, Payment, Customer etc
A Sub Domain equates to a single Domain Service. Below we have the Customer domain service which is made up of one or more AWS microservices.
A sub domain is essentially a logically separated part of the overall domain based on its encapsulated business capabilities, which is a domain in its own right. Above we can see the sub domain for Customer which is a domain service.
In our Serverless World we would split our overall domain (Insurance domain example below) based on decomposed business capabilities (into sub domains), resulting in a set of Domain Services.
The illustration above shows that the ‘Sales’ and ‘Marketing’ sub domains are broken down into smaller encapsulated inherently linked microservices.
The ‘Purchasing’ and ‘Claims’ models are important business differentiators for ‘Sales’, and are split into two separate microservices. ‘Marketing’ is decomposed by using supporting business functionalities, such as ‘Campaigns’, ‘Analytics’, and ‘Reports’, and the domain service is broken down into three microservices.
✔️ In the AWS Serverless World a Sub Domain would equate to a well defined Domain Service which encapsulates multiple serverless microservices behind a well defined versioned REST API through its bounded context (see below).
The Domain Model is your structured knowledge of the problem within the sub domain. The Domain Model should represent the key concepts of the problem domain, and it should identify the relationships among all of the entities within the scope of the domain.
✔️ In the AWS Serverless World the Domain Model itself could be described as a diagram(s), code examples, or even written documentation of the problem statement. Perhaps all of these! The important thing is this information should be accessible and understandable by everyone in the organisation.
There are three types of Sub Domains which are listed below:
The Core domains encapsulate your business differentiating functionality which gives you competitive advantage as an organisation. This is where you should focus your most effort and resources as an enterprise organisation.
These are domains that are complex, give us advantages over our competitors, change often, and should be developed internally.
The Core domain should deliver about 20% of the total value of the entire system, be about 5% of the code base, and take about 80% of the effort.
From a Serverless Architecture Layers perspective the Core domains would look like so:
🟠 Supporting (Auxiliary)
Supporting domains should help support the core domains, supporting functions related directly to what the business does. These are domains which can not be bought off the shelf, and underpin the core domain (without these sub domains the core domains could not function)
“Supporting domains are candidates to be outsourced to less experienced development teams or 3rd parties”
These are domains which are less complex, don’t change as often, and are candidates to ‘potentially’ outsource or buy.
From an enterprise architecture perspective a generic domain consists of business functionality that is ‘facilitated’ but not core to the business. An example could be the ability to generate an invoice.
Generic domains are those in which you should look at “buy before build” and purchasing from a 3rd party.
These domains are typically very complex to build, offer no competitive advantage to the business, do not change very often, and should ‘ideally’ be bought off the shelf. Why would you build these internally?
An entity is an object with a unique identity that persists over time. For example, in our e-commerce example above, orders and customers would be entities. In our diagram below a customer would have an immutable ‘Customer ID’. We also have a ‘Loyalty Scheme’ for customers which is an entity in its own right, and is linked to ‘Customers’ within that sub domain.
“For example, an entity is an object defined not by its attributes, but its identity. As an example, most airlines assign a unique number to seats on every flight: this is the seat’s identity.” — https://en.wikipedia.org/wiki/Domain-driven_design
✔️ In the AWS Serverless World we would never have another domain service interact directly with either of these entities, they would go via the Domain Service API.
Value objects represent typed values, that have no conceptual identity, and are linked to a domain entity. In our example above we have ‘Customer Address’ which has no identity, but is linked to the ‘Customer’ entity which does.
✔️ In the AWS Serverless World this could equate to the tables, collections or items within our datastore such as DocumentDB or DynamoDB which are linked to the top level entity within the same store (perhaps via primary or partition keys).
An aggregate defines a consistency boundary around one or more entities. Exactly one entity in an aggregate is the root (i.e. the way to interact with the overall entity). Lookup is done using the root entity’s identifier (e.g. ‘Customer ID’). Any other entities in the aggregate are children of the root, and are referenced by following pointers from the root.
An example would be ‘Customer’ as the root, and ‘Customer Preferences’ as a child entity (which will no doubt have its own Value Objects).
✔️ In the AWS Serverless World within a Domain Service, this would be one or more internal microservices which sit behind the Domain Services well defined REST and event interfaces. An Aggregate equates to a Microservice i.e. the entity and the business logic for that microservice within an aggregate.
In our example above, we can see that the Bounded Context can be interpreted as encapsulation around the overall Domain Model (one or more aggregates/microservices). As shown below, the Customer bounded context includes both the ‘Customer’ and ‘Loyalty Scheme’ aggregates (microservices).
This means that we can easily reason the in’s and out’s from the domain service (reduced cognitive load as other domain teams can see the integration contracts using OpenAPI or Swagger), albeit through a well defined versioned REST API, or through producing and consuming domain events. This means that serverless teams can work behind that contract safely without affecting other teams, and can deploy independently in a loosely coupled manner (one or more services behind that API).
“A bounded context is simply the boundary within a domain where a particular domain model applies. We can group functionality according to whether various functions will share a single domain model.” — https://docs.microsoft.com/en-us/azure/architecture/microservices/model/domain-analysis
✔️ The way I see this personally in the Serverless World is internal microservices (aggregates & entities) can communicate internally within that bounded context (domain service, and perhaps VPC); but the in’s and out’s from the overall domain service itself is through the well defined API or through domain events (in’s and out’s via the bounded context).
An Anti-Corruption Layer is the implementation of a façade or adapter layer between different subsystems that don’t share the same semantics.
This layer translates requests that one subsystem makes to the other subsystem, ensuring that an application’s design is not limited by dependencies on outside subsystems.
✔️ In the Serverless Architecture Layers World, Anti-Corruption Layers are typically modelled as Integration layers within the Experience Layer. An example would be an Experience Integration Layer sitting between a legacy application and a new cloud service, who’s job is to translate the DTOs (in’s and out’s) to ensure we don’t take the poor design of the legacy app to our new cloud service.
Domain Services vs Application Services
Eric Evans distinguishes between domain services, which ‘encapsulate domain logic’, and application services, which ‘provide technical functionality’, such as user authentication, document generation, sending communications etc. Domain services are often used to model behavior that spans multiple entities.
Application services are typically part of the ‘Cross-cutting Layer’ when it comes to Serverless Architecture Layers.
✔️ In the Serverless Architecture Layers, Domain Services make up the distinct components that make up the Domain Layer.
Domain events can be used to notify other parts of the overall system (other sub domains within the overall domain) when something happens via its aggregates (internal microservices), within our well encapsulated domain.
As the name suggests, domain events should mean something within the overall domain. For example, “A delivery was cancelled” is a domain event being produced by the Delivery sub domain.
From an Experience Layer perspective we can still raise events which are not necessarily ‘domain events’ as such, for example in the BFF of the Website a customer logging in could be a valid event. We could also utilise sentiment analysis to raise an event that ‘Customer X is unhappy’.
✔️ In the Serverless World domain events can be raised across the overall Domain using services such as Amazon EventBridge (cross-account).
Ubiquitous Language is the term Eric Evans uses in Domain Driven Design for the practice of building up a common, rigorous language between engineering teams, the business, and users. This language should be based on the Domain Model used in the software — hence the need for it to be rigorous, since software doesn’t cope well with ambiguity.
The diagram below shows what this could equate to in the AWS Serverless World moving from the overall diagram and DDD descriptions above, showing an individual Domain Service:
✔️ A Domain is the overall problem space, which could equate to an AWS Organization in the Serverless World.
✔️ A Sub Domain equates to a Domain Service (which could be an Amazon Private API Gateway in its own AWS account within a VPC as a physical bounded context). Many Domain Services make up the Domain Layer in the Serverless Architecture Layers pattern.
This could be a code repository for the sub domain which includes multiple independent AWS CDK apps.
✔️ A Bounded Context equates to the encapsulation of internal microservices (Aggregates) within a Domain Service (i.e. the in’s and out’s of the sub domain via well defined versioned REST APIs or Domain Events). As discussed above this could be a VPC and Private API Gateway in its own organisational AWS account, and using EventBridge to consume and publish domain events.
This could be provisioned in its own ‘Infra’ AWS CDK app and deployed independently.
✔️ An Aggregate equates to an internal microservice within the Domain Service, for example this could be Lambdas, DynamoDB tables, Step Functions or S3 buckets etc etc. Multiple aggregates (internal microservices) can live within a single bounded context, communicating internally through queues, events, messages or other internal private API Gateways, for example. (although not accessible from the outside world unless through the well defined REST and Event interfaces of the overall Domain Service)
These microservices can be provisioned in their own AWS CDK apps and deployed independently.
✔️ An Entity equates to the top level entity of the datastore of a Aggregate/Microservice (for example DynamoDB as described above).
✔️ A Value Object is part of the entity datastore above, for example a collection/table of items linked to the entity. In a DynamoDB example this could be an adjacent list pattern, where the value object is a child of the parent, and has no identity itself.
✔️ A Domain Event equates to the EventBridge events we produce from our internal microservices (Aggregates) within our Domain Service, and consume from other Domain Services.
The shared ESB (Enterprise Service Bus) would be an AWS CDK app which deploys an EventBridge bus into its own shared AWS account within an organisation.
OK, How big should a microservice (aggregate) be?
Before we finish the summary, one question that is often asked when a Domain Service includes encapsulated microservices, is how big should they be?
To answer this lets look at the ideals of a microservice:
Each component service in a microservices architecture can be developed, deployed, operated, and scaled without affecting the functioning of other services. Services do not need to share any of their code or implementation with other services. Any communication between individual components happens via well-defined APIs.
Each service is designed for a set of capabilities and focuses on solving a specific problem.
What we want to ensure with DDD and the Serverless Architecture Layers is that we don’t end up with a distributed monolith (i.e. huge monolithic domain services), and we also want to ensure that we don’t end up with thousands of tiny microservices which we can’t understand or reason with (i.e. the opposite of this)!
The ideal is to ensure that our Domain Service (for a given Sub Domain) is made up of multiple autonomous microservices which are well encapsulated, solve their part of the overall puzzle, can be worked on and deployed independently, and between them facilitate the business requirements of the sub domain. The ideal as shown above would be the circle on the far right.
Reusable components and the CDK
So we now understand how we can apply DDD to serverless architectures, and use the approach to model our Domain services that make up the ‘Domain Layer’, but imagine if we could abstract this all away behind a reusable pattern? We can! This is where the AWS CDK and L3/L4 constructs come in.
In a future article we will cover an example of building out a custom CDK construct for a Domain Service which can be utilised within an enterprise organisation, through teams importing this from a shared code repository.
I hope you found that useful as a real world example of how domain driven design can support us in building out domain services within our ‘Domain Layer’ of the five Serverless Architecture Layers.
Wrapping up 👋
Please go and subscribe on my YouTube channel for similar content!
I would love to connect with you also on any of the following:
If you enjoyed the posts please follow my profile Lee James Gilmore for further posts/series, and don’t forget to connect and say Hi 👋
Please also use the ‘clap’ feature at the bottom of the post if you enjoyed it! (You can clap more than once!!)
I consider myself a serverless advocate with a love of all things AWS, innovation, software architecture and technology.”
*** The information provided are my own personal views and I accept no responsibility on the use of the information. ***
You may also be interested in the following: