Assemblage overview: step 3 - defining a service architecture
application architecture architecting dark energy and dark matter assemblage microservice architecture service architectureStep 1 of the Assemblage definition process distills the requirements into a set of system operations that collectively define the application’s behavior and Step 2 identifies the subdomains that implement those system operations. In this article, I describe step 3 of the Assemblage process, which designs the service architecture.
A service architecture is a (mostly) technology neutral description of a microservice architecture consisting of the services, their responsibilities, subdomains, APIs and collaborations. In particular, inter-service communication is described abstractly using, for example, message channels and other enterprise Integration patterns rather than specific technologies such as Apache Kafka topics.
You will learn how to define the service architecture by grouping subdomains to form services and applying the service collaboration patterns to realize system operations that span multiple services. I describe how the architecture is shaped by the dark energy and dark matter forces. Let’s start with an overview of how to define a service architecture.
Overview of designing a service architecture
The service architecture is designed incrementally, one operation at a time. First, you rank the operations by descending importance. Next, you work your way down the list of operations, designing an operation realization. An operation realization is an architecture modification that adds and updates architectural elements - services, operations, event handlers, and message channels - in order to implement an operation. The service architecture is a composition of each operation’s realization.
Let’s look at how to design a service service, starting with ranking system operations.
Rank system operations by descending importance
Your first task is to rank the system operations by descending importance. Let’s first look at why ranking matters and after that I’ll discuss ranking criteria.
Why ranking matters
Since the service architecture is defined incrementally, one operation at a time, the order in which you consider the operations matters. A higher ranked operation will potentially play a much greater role in shaping the architecture. The higher its ranking the more likely it will be that some or all of its subdomains will be unassigned. You will have more freedom in how you assign its subdomains to services. There’s a greater chance of resolving the dark energy and dark matter forces.
How to rank system operations
There are several ways of assessing a system operation’s importance including:
- High business criticality - important to design these operations so that they are highly available
- High technical risk - e.g. complexity, large data volumes, high throughput, low latency, etc.
- Competitive advantage - e.g. important to be able to rapidly experiment and innovate
- …
For example, let’s imagine that you are working on the FTGO application. You’d probably rank those system operations that support the ordering flow (from order creation to delivery) higher than those that implement other functionality. A ranking might look something like:
createOrder()
acceptOrder()
- …
After ranking the system operations, you will work your way down the list of system operations, incrementally creating the architecture, by designing an operation realization for each one.
Designing an operation realization
You will design an operation realization for each system operation. The design of an operation realization occurs in context of the architecture that’s been defined so far. This architecture is composed of the realizations for the previously designed operations. Initially, that architecture is empty. Each operation realization adds, and updates architectural elements such as services, operations, event handlers, and message channels et.
There are potentially multiple ways to implement an operation. I’ll first describe how to implement a system operation in terms of collaborating services. After that, I’ll describe how evaluate and compare the candidate implementations using dark energy and dark matter forces and pick the best one.
Designing a system operation realization
To design an operation realization you need to do two things. First, you must assign the operation’s unassigned subdomains to new or existing services Next, you must also add elements to the services, such as operations, event publishers, and handlers, that facilitate collaboration. Let’s look at how to design an operation realization in more detail.
Assigning an operation’s subdomains to services
The first step of designing an operation realization is to ensure that all of the operation’s subdomains are assigned to services. Some or all of the operation’s subdomain might have already been assigned to services when designing higher ranked operations. You’ll only be able to assign the operation’s unassigned subdomains to services. The lower an operation’s rank the greater the less freedom you will have to assign subdomains. It’s likely that for many low ranked operations, all of their subdomains will already have been assigned. Let’s look at an example from the FTGO application.
About the acceptTicket()
operation
Let’s imagine that you are part way through designing the architecture for the FTGO application. You’ve already designed various operations and defined an architecture consisting of the following services:
Consumer Service
- contains theConsumer Management
domainOrder Service
- contains theOrder Management
domainRestaurant Service
- contains theRestaurant Management
domainKitchen Service
- contains theKitchen Management
domain
And now, you now want to implement the acceptTicket()
operation, which is invoked when a restaurant accepts a Ticket
, which represents a consumer’s order, and commits to having it ready for pickup at a particular time.
This operation is implemented by two subdomains: Kitchen Management
and Delivery Management
.
It updates the state of the Ticket
in the Kitchen Management
subdomain to ACCEPTED
and schedules a Delivery
in the Delivery Management
subdomain.
AssigningacceptTicket()
’s subdomains
The Kitchen Management
subdomain has already been assigned to a Kitchen Service
.
We, therefore, need to decide where to assign the Delivery Management
subdomain.
For this simple example, there’s 5 possible assignments of the Delivery Management
subdomain: anyone of the 4 existing services or to a new Delivery Service
.
A more complex operation that has multiple unassigned subdomains could have many more possible assignments.
To simplify the rest of the discussion, let’s consider just two candidate assignments for the Delivery Management
subdomain:
- The
Kitchen Service
- A new
Delivery Service
The following diagram shows the two candidate assignments:
Let’s look at how to design the service collaborations for each of these candidate realizations.
Designing the service collaboration for an operation
The second step of designing an operating realization is to determine how the services should collaborate to implement the operation and the add elements that enable the collaboration.
The assignment of an operation’s subdomains to services results in an operation that’s either local or distributed.
A local operation is one who’s subdomains are in a single service, where as a distributed operation spans multiple services.
In the FTGO example, acceptOrder()
in candidate #1 is local to the Kitchen Service
where as in candidate #2 it’s distributed across the Kitchen Service
and the Delivery Service
.
Let’s first look at how to design the service collaboration for a local operation.
Designing local operations
If all of an operation’s subdomains are assigned to a single service, then the operation is local to that service.
The service collaboration for a local operation is very simple.
In acceptOrder()
candidate #1, the API Gateway simply forwards the acceptOrder()
request to the Kitchen Service
.
The Kitchen Service
has an acceptOrder()
operation that changes the state of the Ticket
to ACCEPTED
and creates a Delivery
.
Designing distributed operations
If an operation’s subdomains are assigned to two or more services, then the operation is distributed.
There are often numerous possible ways to design the service collaboration for a distributed operation: different combinations of the service collaboration patterns and even multiple ways to apply a given pattern.
For example, since the acceptOrder()
operation in candidate #2 updates entities in two services, it needs to be implemented as Saga, which spans the Kitchen Service
and the Delivery Service
.
One possible saga design is to use choreography-based Saga that updates the state of the Ticket
, and publishes a TicketAccepted
event, which triggers the creation of a Delivery
.
You could, however, use an orchestration-based saga instead.
Moreover, there are often multiple possible different sequences of steps in a saga.
Ideally, you should consider all possible designs.
To simplify the discussion let’s assume that acceptOrder()
is implemented by a chorography-based saga:
This saga works as follows:
- The API Gateway routes the
acceptTicket()
request to theKitchen Service
- The
Kitchen Service
has anacceptTicket()
operation that changes the state of theTicket
toACCEPTED
and publishes aTicketAccepted
event - The
Delivery Service
has an event handler for theTicketAccepted
event that schedules aDelivery
Let’s now look at how to evaluate the candidate realizations for an operation.
Evaluating candidate system operation realizations
If there are multiple possible system operation realizations, you need to evaluate them using the dark energy and dark matter forces and pick the one that best resolves the forces.
Let’s first look at how to evaluate the two candidates for acceptOrder()
operation using the dark energy forces.
After that, I’ll describe how to evaluate them using the dark matter forces.
Evaluating the candidates using dark energy forces
Candidate #1, which puts both subdomains in the Kitchen Service
, resolves each of the dark energy forces as follows
- ❌ Simple components -
Delivery Management
is a complex subdomain andKitchen Management
is a complicated subdomain and should ideally be implemented by separate services. - ❌ Team autonomy -
Delivery Management
andKitchen Management
are owned by separate teams and should ideally be implemented by separate services. - ❌ Fast deployment pipeline -
Delivery Management
is a complex subdomain andKitchen Management
is a complicated subdomain and should ideally have separate deployment pipelines. - ✅ Support multiple technology stacks - the two subdomains use the same technology stacks and so there’s no issue with putting them in the same service
- ✅ Segregate by characteristics - the two subdomains have similar characteristics and so there’s no issue with putting them in the same service
As you can see, it only partially resolves these forces.
Most notably, it doesn’t adequately resolve the Team Autonomy
force.
In comparison, candidate #2, which assigns the Delivery Management
subdomain to a separate service, resolves the all of dark energy forces.
Let’s now look at these candidate realizations resolve the dark matter forces.
Evaluating the candidates using dark matter forces
In candidate #1, acceptTicket()
is a local operation.
As a result, this design resolves all of the dark matter forces.
In comparison, in candidate #2, acceptOrder()
is a distributed operation.
However, it still adequately resolves the dark matter forces:
- ✅ Simple interactions - there’s a single event so interactions are simple
- ✅ Efficient interactions - there’s a single event so interactions are efficient
- ✅ Prefer ACID over BASE - although it’s eventually consistent, it’s quite simple
- ✅ Minimize runtime coupling - since communication is asynchronous, there’s no runtime coupling
- ✅ Minimize design time coupling - there’s minimal design time coupling
Selecting the best operation realization
Once you have evaluated the candidate realizations, you need to pick the best one.
Here’s how the two example design candidates compare:
Consequently, candidate #2 is the best overall design because it resolves the all of the dark energy forces and adequately resolves the dark matter forces.
Updating the service architecture
The chosen operation realization is then applied to the architecture, which results in the following changes:
- Updates the
API Gateway
- routesacceptTicket()
request to theKitchen Service
- Updates the existing
Kitchen Service
:- Add the
Delivery Management
subdomain to this service - Add the service operation
acceptTicket()
- Add a
TicketAccepted
published event
- Add the
- Adds a new
Delivery Service
:- Add the
Delivery Management
subdomain to this service - Add an event handler for the
TicketAccepted
event that schedules aDelivery
- Add the
Once you’ve designed the operation realization for acceptTicket()
, you can then move on to the next operation in the list.
What’s next
The process of designing a service architecture described in this article is a series of operation-level design decisions. As a result, there’s a risk that some decisions will be suboptimal from a global perspective. In the next article, I describe step 4 of the Assemblage architecture design process, which evaluates the service architecture in order to identify areas for improvement.
Need help with accelerating software delivery?
I’m available to help your organization improve agility and competitiveness through better software architecture: training workshops, architecture reviews, etc.