Practical DDD, Part 2 — Architectural Topics 1 — Aggregate and Bounded Context Microservice

Hila Fox
Machines talk, we tech.
6 min readSep 21, 2022

--

In this post, I will share how we use DDD in Augury and our unique business requirements to create our Microservices Architectural guidelines—focusing on three common patterns. We use these guidelines and definitions to consider when to create a new microservice and how to distribute our domain logic into these services. If you haven’t read the previous post and wonder how it all began, the link is attached.

DDD definitions

A Bounded Context (BC) is an aggregation of related areas\business requirements in our product. As our product evolves, more BCs are born. In DDD, a BC contains one or more Aggregates.

An Aggregate is a cluster of associated objects that we treat as a unit for data changes. also frequently known as a business model

An Aggregate contains the following members:

  • Entity — An object fundamentally defined not by its attributes, but by a thread of continuity and identity, for example, a component object within a machine.
  • Value Object — An object that describes some characteristic or attribute but carries no concept of identity. in most cases, it’s just some fields nested in an entity. as an example, a name field
  • An Aggregate Root is a single, specific Entity contained in the aggregate

As we see the possible types of aggregates in the system, we can narrow them down into two types:

  • Simple aggregate — its Root entity is being created via some sort of command
  • Complex Aggregate — its Root entity is being created\updated via domain events, single or multiple, from different aggregates. This causes its core to know more about other domains to fulfill its business requirements.

Practical architecture patterns

In this topic, we have three patterns:

  • Aggregate Microservice — a microservice encapsulating an aggregate.
  • Complex aggregate Microservice — a microservice encapsulating a complex aggregate.
  • Bounded context Microservice — a microservice encapsulating a bounded context.

You might wonder why we chose to represent all three in our system. We can find several conversations when reading about DDD and how different companies transition theories into practical components. One of them is the granularity of our system. Meaning what is the smallest independent technical component we have? As we saw, we have two options: the aggregate microservice and the bounded context microservice.

Let’s consider our needs as a company —

  • Product market fit — our main product has reached product market fit, this means that we have (in those areas) consistent bounded context, interfaces, and aggregates.
  • Growth — Augury has scaled in customers and developers. We need to support the growth and team structure that comes with those changes.
  • Agile mindset — though our main product is in PMF. We still have areas within it or other products that we feel less sure about, these require a more flexible environment.

Let's think of some pros and cons for each approach —

Aggregate microservices:

  • Pro: Dynamic, as this is the smallest representation, we can represent in our system and product. We can always “move” it from squad to squad as our product matures and we refine our modeling.
  • Con: We are at risk of generating a big ball of mud. Suppose we make the modeling and communication patterns between services incorrectly. We will still have the same dependencies, but now we will also have a distributed system, which just adds complexity.

Bounded context microservice:

  • Pro: Less complexity, less cross-service communication, less chatter. As aggregates in the same bounded context communicate more and will do so in the same microservice.
  • Con: We are at risk of generating code and possibly models that are highly dependent on each other. Also, if the need to break a bounded context into two squads or two bounded contexts, do enable team ownership per technical component would require a major refactor.

The decision —

We decided to optimize for growth, meaning being able to be dynamic, thus choosing to model our services in the granularity of Aggregate Microservice. We do offer mitigation for the risk of the complexity and big ball of mud by —

  • Adding the architectural guidelines
  • The architects are involved in every DR in their domain for advice
  • We are building a high-level architectural vision that encapsulates aggregates, bounded contexts (view services, cross-cutting, and orchestrator), and communication patterns to avoid such a path.

Bounded context microservices will be used mainly for the use cases of product areas that are not in product-market fit, meaning the aggregates are unknown and the uncertainty level is high.

When -> Then to create a new aggregate microservice:

  • When we have a business unit in our system that holds a very well-defined and specific state and identity and requires transactional consistency within it -> We will create a specific aggregate service.
  • When we are adding a new business unit as part of an MVP -> We will choose to create a specific aggregate service. This will make deleting it in the future very easy.
  • When a new business unit to our system has commands (CRUD) operations -> Consider it an aggregate with a new aggregate service.
  • When a new business unit will need to consume an existing domain even -> Consider it to be a new aggregate (as its decoupled the producer)

When -> Then to remain in the same aggregate service:

  • When one aggregate has to update another aggregate and requires transactional consistency -> Spoiler alert, we have an architectural topic called “control flows”. It's topic #6, and we will get to it.
  • When we require the option for a full rollback -> We will choose to create a specific aggregate service.

When -> Then to change an aggregate microservice to a complex aggregate service:

  • When we need to change an aggregates state according to a change in another, i.e., consuming a domain event -> We will choose to transition the aggregate microservice to a complex one.
  • When we need to persist data for performance optimization → We will consider transitioning the aggregate microservice to a complex one.
  • When we don’t have enough knowledge/confidence in the state, we need to persist -> We will think twice before making our aggregate complex.

When -> Then to break a monolith/aggregate into several:

  • When we have an aggregate that is currently placed in a monolith -> We will choose to create a specific aggregate service.
  • When we don’t require transactional consistency -> Consider creating another aggregate microservice, as this most possibly is decoupled.
  • When adding new prefixes for endpoints in the service -> Consider creating a new aggregate microservice.
  • When a field is not related to the aggregate but needed for some optimization -> Consider creating a new aggregate with the minimal data from the original aggregate.

When -> Then to create a bounded context microservice:

  • When there are too many unknowns regarding the final structure of the aggregate or aggregates (i.e., bounded context) -> We will choose to create a broader-sized microservice to learn as we go. In the future, we can break it into several aggregate microservices.

To sum up —

  • The aggregate microservice is one of our most common patterns in the system. We chose it as our granularity level to enable growth in areas that reached product market fit.
  • The bounded context microservice is not used much because it represents new product areas. This pattern comes to answer a need where in the company we are still not in product market fit.
  • The main risk we see from choosing low granularity is becoming a big ball of mud. We mitigate this by giving strong guidelines and guidance. (I know there are more risks like complexity, deployment overhead, and stuff like that, but we are not feeling this pain, and we have excellent tools to help us mitigate this as well)

We have several more architectural topics—

  • View microservice — materialized views and all that
  • Cross-cutting services — integrations, infrastructure, and tools in our system.
  • Communication channels — how services communicate.
  • Control flows — orchestration, choreography, and more fun stuff

--

--

Hila Fox
Machines talk, we tech.

Software Architect @ Augury. Experienced with high scale distributed systems and domain driven design.