Serverless Architecture Layers & DDD (Part 1) — The Experience Layer
Talking through the 5 Serverless Architecture Layers in detail, starting with the Experience Layer in Part 1.
✔️ Part 1 — Experience Layer
⚪ Part 2 — Cross-cutting Layer
⚪ Part 3 — Domain Layer
⚪ Part 4 — Data Layer
⚪ Part 5 — Platform Layer
In this article we are going to do a deeper dive into the Experience Layer which is layer one 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/
Below is an overview of the five Serverless Architecture Layers which are discussed in the following article link below the diagram:
What is Domain Driven Design in Serverless?
If you are happy you understand DDD in detail then you can move to the next section. If not, I would suggest reading the link below to see how it equates to Serverless in my mind.
The following article covers Domain Driven Design (DDD) with Serverless, and is a pre-requisite read for this article:
Serverless Domain Driven Design
Breaking down DDD concepts with real world examples into a tangible serverless equivalent.
In the next section let’s cover what an ‘Experience Layer’ is, and why we need these experiences.
What is an Experience Layer?
The Serverless Experience Layer is made up of both ‘Presentation and Application Layers’ for consumers (whether that be humans or machines).
Examples could be websites, mobile apps, MFEs (Micro-frontends), Alexa Apps, B2B consumer APIs etc typically for users (through backend for frontend APIs); or for machines this could be IoT devices, or abstraction layers between SaaS products or legacy systems (which could even be on-premise) etc. This is shown below:
As shown above the experience layer is split into two types of APIs:
✔️ Public Experience Layer APIs for typical consuming services (Websites, Mobile Apps, B2B etc). These APIs typically have users as consumers.
✔️ Integration Experience Layer APIs for integrations with 3rd parties such as SaaS products or internal systems (perhaps on-premise for example), which typically have machines as consumers.
“This layer is kept thin. It does not contain business rules or knowledge, but only coordinates tasks and delegates work to collaborations of domain objects in the next layer down” — Eric Evans (DDD)
In part 5 we will discuss how we can create AWS CDK L3 & L4 constructs within an organisation for Experience Layer and Domain Layer solutions (think reusable building blocks for our solutions), reducing the cognitive load on teams, increasing time to market, and baking in best practices and security globally.
Why do we need to think about Experience Layers?
Below are some of the reasons why we need to think about Experience Layers at an enterprise organisation level.
These experience 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”
Orchestration & Bounded Contexts ✔️
The value of an Experience Layer as shown below is that business logic from the Domain Layer services can be reused across many Experience Layer solutions, with any Experience Layer specific orchestration and logic happening here if needed (for example pulling data from multiple domains).
The diagram above shows that the Website BFF is utilising two Domain Layer services (Order and Sales). In reality from a Serverless perspective this could be an AWS Express Workflow within the given BFF, calling the private Domain Layer services using IAM authentication to keep this private to the AWS network.
Note: The Domain APIs are Private APIs only accessible on the AWS network, massively reducing the attack surface from bad actors.
“Experience Layer solutions ideally should be stateless, thin, and only contain business logic pertinent to its consumer” — Lee Gilmore
Ideally what we want is the Domain Services in the Domain Layer to be private and only accessed via one or more Experience Layer APIs as shown below (or communication between domain services):
This means that the domain logic is encapsulated in the correct place (its bounded context) and reusable; only accessible via well-defined and versioned domain APIs or through events.
What typically happens without an Experience Layer? ❌
This prevents what typically happens across enterprise organisations when teams work in their own silos; which is business logic is leaked across solutions and layers, databases are shared directly, and huge duplication of effort; whilst not adhering to domain-driven design and context boundaries:
An example of this would be each experience layer solution above writing the code to track an order, rather than this business logic living in the Orders domain and simply being consumed. When we don’t use an experience layer here we find that we have both tight coupling of services, domain leak, massive duplication, and adding or changing features becomes a logistical nightmare going forward (changing in multiple places across many teams if a bug or new feature request happens)
Over time within an organisation if we don’t design carefully thinking about Serverless Architecture Layers we soon end up with a big ball of mud; which is hard to visualise, hard to understand integration points, huge cognitive load on teams, and hard to understand dependencies.
“Over time within an organisation if we don’t design carefully thinking about Serverless Architecture Layers we soon end up with a big ball of mud; which is hard to visualise, hard to understand integration points, huge cognitive load on teams, and hard to understand dependencies.” — Lee Gilmore
Different auth per consumer ✔️
The next advantage of an Experience Layer is that varying solutions can have different auth requirements for different consumers, but still utilise the same shared business logic within the domain services, as shown below.
In this example above we have a B2B (Business to Business) flow on the right using a standard M2M (OAuth 2 Client Credentials flow), along with a usage plan and API Key for external customers; whereas we have the users logging in with a SSO style flow (perhaps implicit flow).
These can be very thin APIs which utilise the domain services and there centralised business logic (again an example of tracking an order which we don’t want to duplicate).
Both of the Experience Layer APIs would have different AuthZ/AuthN built in for their own specific requirements, but as with the example above, they utilise the same ‘track your order’ logic in one place.
In a domain-driven design world this is very important, as we should only be interacting with a domain (and therefore its aggregates i.e. internal microservices) through its aggregate root (i.e. its well defined versioned domain API), and not going directly into a well encapsulated domain; its internal data stores and internal APIs for example:
“Aggregate is a pattern in Domain-Driven Design. A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An example may be an order and its line-items, these will be separate objects, but it’s useful to treat the order (together with its line items) as a single aggregate. An aggregate will have one of its component objects be the aggregate root. Any references from outside the aggregate should only go to the aggregate root. The root can thus ensure the integrity of the aggregate as a whole.” — Martin Fowler
This is shown in our example below, where we wouldn’t interact directly with ‘Customer Address’ or ‘Customer Preferences’, but would go via its aggregate root of top level ‘Customer’, typically through a well defined versioned REST API (in this example below the Customer and Loyalty Scheme internal microservices sit behind the customers domain service API):
We quite often need to interact with SaaS products (ERP, Finance systems etc) or internal/legacy applications, each of which will no doubt have its own auth and networking requirements (as shown below):
In this situation we may need an integration experience layer API which sits between our consumers and our domain services.
One example would be a legacy on premise solution which we are migrating away from into the cloud, but we still need a way for this solution to interact with our new domain services in the cloud on the private network (Direct Connect between our on-premise solution and AWS network).
Another example would be an internal 3rd party SaaS provider which we need to integrate our domain services with, for example an ERP or finance system.
Some of these needs can be defined below as:
✔️ Different authentication & authorisation requirements for the on-premise legacy app or SaaS product, and we may want to scope access to only certain domains and endpoints (resources). This may also include IP whitelisting.
✔️ We may need to use this integration experience layer as an ACL (Anti-Corruption Layer) which allows us to transpose domain objects (DTO’s essentially), so we don’t take poor legacy design choices into our new clean cloud services (for example poorly named or badly cased properties).
Implement 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. Use this pattern to ensure that an application’s design is not limited by dependencies on outside subsystems. This pattern was first described by Eric Evans in Domain-Driven Design. — Microsoft
✔️ We may also need to transpose properties and objects between the 3rd party SaaS product and our Domain Services so we can integrate them successfully.
They use well documented versioned APIs and event schemas ✔️
When we work in this way we ensure that the Experience Layer teams can easily find and consume versioned API documents and shared event schemas for the various domains they are interacting with in one place as shown below:
This also ensures that the teams are always going via the aggregate root of the domain and not directly to databases or internal services as discussed further above (domains should be well encapsulated with zero trust).
We will talk more about this developer experience in part 5 around the ‘Platforms Layer’, which includes developer experience such as being able to easily find and consume the various schemas.
✔️ Ensures business logic is not leaked and duplicated through an organisation and across many teams. Instead the experiences utilise the logic from the domains.
✔️ Allows us to use different auth requirements per experience, whilst still using the same shared business logic in the domains.
✔️ The experience i.e. UI/Voice/UX should contain no shared domain business logic.
✔️️ The backend APIs for the experience should be stateless.
✔️️ Experiences allow us to integrate with legacy applications or SaaS products whilst still using the same shared business logic in the domain services.
What does this look like in the AWS World?
If we take our learnings from above, we can translate this into what it could look like in the Serverless World on AWS below using Serverless Architecture Layers (very thin high-level slice):
Note: For clarity of the diagram and the concept, some services such as AWS WAF, Amazon CloudFront etc (among others) have been removed for brevity.
Looking at the diagram above you will see:
✔️ We have two public facing experience layer ‘experiences’, one for Web and Mobile using AWS AppSync (GraphQL), and one for B2B customers using Amazon API Gateway; both of which are tied down independently using Amazon Cognito for auth; although these would be separate of course (for example for B2B we use a client-credentials grant flow, Usage Plans and API Keys, and for users we would perhaps just use an implicit flow)
✔️ When the two experiences need to consume one or more private APIs of the domains (in the domain layer) they use a combination of IAM/STS and AWS PrivateLink to perform signed requests through to the private API Gateways in the Domain Layer on the AWS backbone network.
Note: We will cover the domain private APIs and auth mechanisms in detail in part 3
✔️ The domain services (private API Gateways) in the Domain Layer use IAM authentication to authorise their consumers (the experiences or other domains which is cross-account); allowing for scoped auth on the AWS network using resource policies. Domain services can also communicate using IAM internally using their well defined private APIs, but experience layers would never communicate with each other directly.
“In all cases, traffic to your private API uses secure connections and does not leave the Amazon network — it is isolated from the public internet”. — https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-apis.html
I hope you found that useful as a concept for modelling your public facing or internal integration experiences. In the next part of the series we will look at the cross-cutting layer in part 2.
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: