Photo by Markus Winkler on Unsplash

Serverless Architecture Layers & DDD (Part 3) — The Domain Layer

Talking through the 5 Serverless Architecture Layers in detail, this time covering the Domain Layer in part 3.

Contents

Part 1— Experience Layer
⚪ Part 2 — Cross-cutting Layer
✔️ Part 3— Domain Layer
⚪ Part 4 — Data Layer
⚪ Part 5 — Platform Layer

What we will be covering in Part 3:

✔️ Introduction.
✔️ Quick Recap.
✔️ What is Domain Driven Design in Serverless.
✔️ The Domain Layer.
✔️ Technical considerations & deep dive.

Introduction

In this article we are going to do a deeper dive into the Domain Layer which is layer three of the five Serveless Architecture Layers of enterprise organisations, all of which is based on domain-driven design (DDD).

👇 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/

Quick recap

Below is an overview of the five Serverless Architecture Layers which are discussed in the following article:

In the next section let’s cover what DDD is with Serverless, and why it is so important to the Domain Layer.

What is Domain Driven Design in Serverless?

If you are happy you understand DDD in detail then you can move to the next section

The following article covers Domain Driven Design (DDD) with Serverless, and is a pre-requisite read for this article:

The Domain Layer 🍰

The Domain Layer in my opinion is the most important to get right and focus on (this after all is how your business operates). In DDD, Eric Evans describes this as:

Responsible for representing concepts for the business, information about the business situation, and business rules.

From a Serverless Architecture Layers perspective, this allows all of our business logic to be encapsulated correctly into sub domains (domain services), and utilised via our many experiences in the Experience Layer (as shown below):

Example of many experiences utilising the domain logic from the Domain Layer

This prevents what typically happens in enterprise organisations where business logic is duplicated and leaked across many solutions and layers, and databases and internals are shared globally, as shown below:

Example of duplicated business logic leaking through an organisation, and services going directly to databases they don’t own

These domain teams align to the ‘Stream Aligned’ teams in Team Topologies. These are described as “aligned to a flow of work from (usually) a segment of the business domain”

What is a ‘Domain Service’ in DDD?

When it comes to the Serverless Architecture Layers we need to think about the Domain Layer alongside Domain Driven Design (DDD).

The Domain Layer is made up of our many Domain Services, which represent the sub domains, domain models and common language of the overall business. These domain services can then be utilised by our Experience Layer (discussed in Part 1 of the series).

A domain is a subject area where the software will be applied, for example an overall e-commerce domain. As we split this further into sub domains, we find that we have sub domains such as Order, Payment, Customer etc

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. Below we can see the sub domain for Customer which is a domain service.

The logical view of our Domain Service for Customer is shown below:

Domain Service for the Customer Domain (Logical View)

We can see that this Customer Domain Service is made up of multiple microservices (aggregates) behind a well defined versioned REST API.

This pattern ensures that we don’t build monoliths or distributed monoliths as shown in the diagram below:

We want to ensure that our Domain Services are correctly sized

In the AWS Serverless World this Customer Domain Service could equate to the following at a conceptual service and architecture level:

Domain Service for the Customer Domain (AWS view)

We can see that the Customer Domain Service is

✔️ A physical bounded context in its own AWS Account through the use of a VPC, permissions and networking, meaning its business logic is encapsulated correctly behind its interfaces. All microservices within this bounded context can communicate internally with each other (perhaps SQS, SNS or an internal event bus using EventBridge).

✔️ The only way to interact with this domain service is through a well defined versioned API using a Private API Gateway with IAM authentication (this is discussed in detail further into the article). This means that all traffic to and from this domain service is done on the AWS backbone network through PrivateLink (no public access), greatly reducing the attack surface for bad actors. (Domain Services are only accessed by other Domain Services or Experiences)

✔️ Other domains can listen to the Domain Events which the internal microservices (aggregates) within the domain are raising onto the shared enterprise event bus (Amazon EventBridge). Conversely, the domain can also listen to events from other domains too.

If we were to look at this at a code level over conceptual service level it would like the following:

If we zoom out from this Customer Domain Service into our overall Serverless Architecture Layers we can see where it sits conceptually (in red):

The Orders Domain Service in the Overall Serverless Architecture Layers

We can see the Orders domain in red above, alongside the other Domain Services which make up the overall Domain Layer (Stock, Purchase, Sales, Users etc).

Responsible for representing concepts for the business, information about the business situation, and business rules.” — Eric Evans

In an organisation this means we can have separate autonomous teams working on these domains in their own dedicated AWS accounts:

Organisational domain engineering teams, each working directly with business stakeholders and SMEs

Let’s now perform a deep dive into the patterns, and also look at technical considerations.

Technical considerations & deep dive 📐

OK, so we have covered some of the high level domain concepts and thought processes, and see where domains sit in the overall problem space; so now let’s cover some of the technical considerations.

✔️ Private Domain Services

We want to secure and centralise our private domain services so they are only accessed using least privilege on the AWS network.

We can see the logical view of how the private domain services work alongside the experience layer BFFs below:

Example of how the Experience Layer and Domain Layers work hand in hand together

We can then zoom in, and look at this specifically from one Experience calling through to a private Domain Service using IAM authentication below:

Example of one Experience calling through to a Domain Service

This works by:

  1. The Experience Layer uses Amazon Cognito to generate an access token for the user, which is then validated on the public facing API.
  2. If validated successfully, the experience layer lambda (BFF Lambda) uses its temporary AWS credentials to generate a signed request using SigV4 and AWS STS specifically for the private domain API it wants to consume (target domain service).
  3. The signed request is made to the domain account API, and the private API Gateway in the domain account validates the signed request using IAM authentication.

A basic code repo example of this process is shown below:

The following diagram shows how your API Gateway Resource Policies work cross-account with our Domain Services to tie down our permissions as needed, one of which is successful, and one which is not:

An example of our policies cross-account when working with our Domain Service APIs

We can see that the calls from the Domain 2 service will fail as the resource policy on the Domain 3 API only allows the execute-api:invoke from Domain 1.

We keep all of the network traffic private on the AWS backbone network by utilising AWS PrivateLink and VPC Endpoints cross account as shown below, either between the experience and one or more domains, or domain to domain(s):

Example of the communication cross account using IAM authentication and AWS PrivateLink

This greatly reduces our attack surface for bad actors, means we have one standard for internal m2m communication globally, the method is not reliant on Amazon Cognito, we can scope auth down to the calling lambda and API resource/method on the API per account if needed, we don’t need to cache access tokens in serverless (like we would with a client-credentials grant Cognito flow), and it is cost effective when we centralise our VPC Endpoints (see below).

Note: In Part 5 we will discuss how much of this architecture above can be abstracted away into reusable L3/L4 CDK Constructs to utilise within an organisation, greatly reducing the cognitive load on teams, and baking in best practice.

✔️ Database choice and consumers

One of the key considerations with the Domain Services is your choice of database, as you will have 1..n consumers of your Domain API, and you will never know what their access pattern requirements are via its API.

“One of the key considerations with the Domain Services is your choice of database, as you will have 1..n consumers of your Domain API, and you will never know what their access pattern requirements are.” — Lee Gilmore

This is shown in the diagram below where we have experiences and other domains as consumers of any given domain service:

We will never know what our 1..n consumers will need, so we need to ensure we have flexible access patterns

We can see above that our many consumers may need to perform pagination, counts, aggregation, filtering and sorting etc all at the same time; so we need to ensure that our Domain Service database choice is flexible and future proofed to allow for this.

“so we need to ensure that our Domain Service database choice is flexible and future proofed.” — Lee Gilmore

This leaves us two key go to choices from the AWS Database World for our Domains which are massively flexible when it comes to access patterns:

✔️ Amazon DocumentDB (Document based database).

✔️ Amazon RDS/Aurora (SQL based database).

DynamoDB is a serverless database technology where you need to know your access patterns up front, so this makes a very poor choice of database for your Domain Services due to the inflexibility, unless the requirements are know, deterministic, and are very simple indeed.

Some people may look at using a combination of datastore technologies for their domain services, such as DynamoDB/DynamoDB streams (writes) and RDS/Aurora (reads), splitting up the reads and writes. This does not work well with a domain API Strategy as your reads are then always eventually consistent compared to your writes, and this can be confusing and error prone for your consumers if they are not aware (i.e. this does not work in an API first company).

✔️ Ins and Outs — the power of contracts

Domain Services should only be accessed through their well defined versioned APIs, and all of the internals should be encapsulated (as shown in the diagram below)

We always go via well defined APIs and never share our internals (bounded context)

As you can see, we should never have another Domain Service reach into another Domain Services database (or queues, topics, anything for that matter!).

Domain Services should only be accessed on the AWS network from other domains or Experience Layer BFFs; and therefore not accessible using CURL, Postman etc

Summary

So lets summarise the need for the Domain Layer and its domain services when it comes to Serverless Architecture Layers:

✔️ Well-defined versioned APIs and events ensure consumers understand how to interact with our domains. (more of this when discussing the Data Layer for events)

✔️ Domain interfaces mean we can change the internals of the domain service without affecting consumers.

✔️ Our domain services are not externally accessible to the outside World, and only accessed using machine to machine flows. (i.e. using private API Gateways and IAM authentication).

✔️ We ensure that consumers don’t interact with the internals of our domains directly, which are well encapsulated behind interfaces (APIs and events), and only through our well defined APIs and events.

✔️ The domain logic can be utilised by other domain services or experiences, preventing anaemic domains being split across a distributed monolith.

I hope you found that useful covering what the Domain Layer is when it comes to Serverless Architecture Layers. In the next article in the series we will cover the Data Layer.

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:

https://www.linkedin.com/in/lee-james-gilmore/
https://twitter.com/LeeJamesGilmore

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!!)

About me

Hi, I’m Lee, an AWS Community Builder, Blogger, AWS certified cloud architect and Global Enterprise Serverless Architect based in the UK; currently working for City Electrical Factors (UK) & City Electric Supply (US), having worked primarily in full-stack JavaScript on AWS for the past 6 years.

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:

--

--

Lee James Gilmore

Principal Serverless Engineer | Enterprise Cloud Architect | Serverless Advocate | Mentor | Blogger | AWS x 6 Certified 🚀