Bounded Context

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

DDD Bounded Context Definition

A bounded context defines the boundaries within which a Domain Model and Ubiquitous Language are valid

Intro

In a large system, you find that the system is not a single monolithic system, but rather a composition of smaller systems. Rather than modelling these together as one, bounded contexts play an important role in helping you separate the different subsystems and modelling these on their own. Putting it all together in one model tends to become hard to maintain over time and often error-prone due to different requirements between the contexts that have yet to be properly defined. We see that we often have some of the same data across a system and chose to model this only once - making the model include more than what is needed for specific purposes. This leads to bringing in more data than is needed and becomes a compromise. Take for instance the usage of Object-relational mapping and a single model for the entire system approach. If you have a model with relationships and you, in reality, have different requirements you end up having to do a compromise of how you fetch it. For instance, if one of your features displays all the parts of the model including its children; it makes sense to eagerly fetch all of this to save roundtrips. While if the same model is used in a place where only the top aggregate holds the information you need, you want to be able to lazy load it so that only the root gets loaded and not its children. The simple solution to this is to model each of the models for the different bounded contexts and use the power of the ORM to actually map to the database for the needs one has.

The core principle is to keep the different parts of your system apart and not take any dependency on any other contexts. We in Dolittle think that, when designing Line of Business applications, the software design should strive for loose coupling and high cohesion. Understanding the domain and designing a solution that solves the business problem in a way that adheres to the customer’s expectations is no trivial task. Given a large enough problem-domain (it shouldn’t even have to be that big or complex), it should be practically impossible to grasp the whole problem and design the ideal system the first time around. We must be able to adjust, or even completely replace components of the system, to provide new or adjusted functionality and to be able to keep up with the ever-changing requirements and poor translations of the system’s intent and behaviour. Therefore it is with utmost importance that we today, and especially in the future, design software solutions with flexibility, scalability and maintainability in mind.

Domain-driven Design and bounded contexts are tools that can be used to deal with this. By exploring the problem-domain with domain-experts we’ll discover a set of terms used by the domain-experts to explain and describe the moving parts of the domain. These terms form up a ubiquitous language which in essence is just a set of words, agreed upon between the developers and the domain-experts, that forms up a conceptual description of the system. A bounded context is a confined area of the domain that usually just deals with a single business-capability, a sub-domain, in which a term in the ubiquitous language MUST only have a single meaning. For example, in E-commerce we could have defined a ubiquitous language where we have the term “product”. But “product” can have different meaning depending on which business-capability of the system we’re talking about. The business-capability that deals with “shopping carts” may only be interested in the price, availability, and the quantity of the item in the “shopping cart”, while the sub-domain that deals with the stock management may only be interested in the product number, the number of items in stock and a location in storage. These two business-capabilities must be separated because the meaning of the term “product” varies depending on the context in which it it is used. We separate these business-capabilities into their own Bounded Contexts, achieving a full de-coupling of the term “product” between these. We can drastically change, or even remove, the meaning of the common term completely from either Bounded Context and the other wouldn’t care at all. When applied correctly, this way of designing software will help us with maintaining a loosely coupled, highly cohesive system that captures the domain, is maintainable, flexible and robust wile it, at the same time, tackles the complexity of the problem.

If you want to learn more about the concepts of Bounded Context, and also to learn more about Domain-Driven-Design in general, we highly suggest that you read Domain-Driven-Design by Eric Evans. Martin Fowler is also a great resource, he has written about a lot of stuff that’s relevant to those interested in building software, including Bounded Contexts. Check out his website

What it means technically

With this conceptual understanding we’ll provide a more concrete description of an Application and a Bounded Context.

Independent of whatever perception you might have of bounded contexts, we in Dolittle have a very specific idea of what a Bounded Context is in our platform, what an application is and how they are built.

Application

The Application is the top-level building block of the software built upon the Dolittle platform. It bundles the subsystems that make up for the intended functionality of the application / software. An Application is essentially a collection of Bounded Contexts, represented simply as a GUID, the behaviour of the Application is defined by the collective behaviour of its Bounded Contexts. It’s what the tenants register as an application in their Application Registry and the thing that actually gets deployed. A deployed application is observing change in each of the individual Bounded Contexts that form the application, a change in either of these Bounded Contexts will then implicitly change the version of the Application. Each combination of the set of the Application’s Bounded Contexts corresponds to its own unique Application version.

Bounded Context

A Bounded Context is a division of the Application with its own, well-defined, area of responsibility in the business domain with its own unique vocabulary that is commonly understood by the components of the Bounded Context, the domain-experts and the development team(s) working on the Bounded Context. Typically a Bounded Context would be structured as a single, self-contained, application, a project or a solution, with its own codebase, preferably separated from the other Bounded Contexts. It can only belong to a single Application and it will keep a reference, a GUID, which uniquely identifies the Application it belongs to.

Bounded Contexts MUST NOT have a dependency on another Bounded Context as this will result in high coupling. It’ll not only defeat the purpose of modelling the application using bounded contexts in the first place but it will also set the stage for an exponential growth in complexity and technical debt when the solution scales and grows. If two Bounded Contexts shares a subset of the same terms, ambiguity of the terms would arise and it could, in the end, be the cause of unwanted increase in complexity and confusion. It would result in a loss of precision and focus, not only in the system itself, but also in the codebase and thus among the developers working on the Bounded Context. The Bounded Context SHOULD be defined in a manner which allows the components that make up the Bounded Context be as cohesive as possible. This makes perfect sense from a Domain-Driven-Design perspective, we want to define our bounded contexts to have responsibility for a small, confined, area of the domain. Its responsibilities SHOULD NOT touch multiple areas of the domain, this will inevitably lead to confusion, ambiguity and a loss of focus. A Bounded Context SHOULD, therefore, consist of components that belong closely together, working together towards a common goal, providing the functionality for a single business-capability. Building Bounded Contexts this way closely adheres to the core principles we in Dolittle have when it comes to building software, the SOLID design principles and for preserving a strong cohesion.

According to DDD, you can have some shared code between bounded contexts. They call this a shared kernel. It’s permissible, but you’d have to think very, very carefully before doing it. Every sharing between Bounded Contexts is a coupling. Don’t do it for trivial things (e.g. sharing concepts or value objects), just replicate them in the Bounded Contexts that needs them.

Topology; Modules and Features

The Bounded Context is an essential concept in DDD, but by now you should also have the impression that the Bounded Context is something very specific and concrete in the Dolittle platform. All Bounded Contexts has, for example, a very concrete structure. We want developers to think of Bounded Contexts as its own, stand alone, project. How you actually structure this is dependent on the programming language you’re using, but in terms of C# we would suggest that you structure an Application this way: Application C# Structure

Here you can see how we in Dolittle would structure an Application. The Application would, for example, be a Github repository and it would typically have Bounded Contexts sitting inside its Source. Each Bounded Context is a folder with a solution (.sln) file and contains all the necessary domain areas; Concepts, Domain, Events, Read and an optional interaction layer called, in this case, Web.

Anyway, how you structure your actual Application is not that important, however, the internal structure of the Bounded Context and its domain areas is what’s important. When you create a Bounded Context based on the Dolittle platform, and you have a reference to the Dolittle Build tool, what will happen when you compile the Bounded Context is that we’ll look at the structure of your Bounded Context project and create a topology object that defines the topology of the Bounded Context and put it inside a topology.json configuration file (configuration file is described later on). The topology will essentially be a recursive structure with features (or modules and features if modules is enabled, described later on). A feature, in terms of the Dolittle platform, is essentially just a way to group Artifacts. Features are uniquely identified throughout the Application by a GUID (explained later). We group Artifacts together by Feature, this is to preserve a strong cohesion between the components that belongs together while at the same time we can also cross cut a lot of other concerns i.e. defining a user’s access of the Bounded Context / Application based on which Modules / Features / Artifacts the user has authorization to read, write and/or execute.

So for example if you’ve enabled the option to structure the topology with modules, the “Domain” area SHOULD look something like this if:

+-- Bounded Context 1
|   +-- BoundedContextName.sln
|   +-- bounded-context.json
|   +-- Domain
|   |   Domain.csproj
|   |   +-- Module 1
|   |   +---- Feature 1
|   |       | Command.cs
|   |       | CommandInputValidator.cs
|   |       | CommandBusinessValidator.cs
|   |       | CommandHandler.cs
|   |       | SecurityDescriptor.cs
|   |       | CommandHandler.cs
|   |       | AggregateRoot.cs
|   |       | Service.cs
|   |       +-- Sub-Feature 1
|   |    |    | Command.cs
|   |    |    | CommandInputValidator.cs
|   |    |    | CommandBusinessValidator.cs
|   |    |    | CommandHandler.cs
|   |    |    | SecurityDescriptor.cs
|   |    |    | CommandHandler.cs
|   |    |    | AggregateRoot.cs
|   |    |    | Service.cs
|   |   +---- Feature 2
|   |       | Command.cs
|   |       | CommandInputValidator.cs
|   |       | CommandBusinessValidator.cs
|   |       | CommandHandler.cs
|   |       | SecurityDescriptor.cs
|   |       | CommandHandler.cs
|   |       | AggregateRoot.cs
|   |       | Service.cs
|   |   +-- Module 2
|   |   +---- Feature 1
...
Artifacts

A Bounded Context will eventually consist of a set of Artifacts, they are what actually defines the behaviour and functionality of the Bounded Context. The Artifacts will be Events, Commands, Command Handlers, Aggregate Roots, Queries and Read Models. You should read about the different types of Artifacts to gain an understanding of how they’ll impact the Application / Bounded Context. Each Artifact is an entity in the Application, uniquely identified throughout the Application in which the Bounded Context belongs. An Artifact belongs to a single Feature.

The configuration

The configuration files are subject to change

When you create a new Bounded Context there are some configuration that needs to be done in order for the Bounded Context to work as intended. The configuration file is called bounded-context.json and it looks like this:

{
  "application": "0d577eb8-a70b-4e38-aca8-f85b3166bdc2",
  "boundedContext": "f660966d-3a74-44e6-8268-a9aefbae6115",
  "boundedContextName": "Shop",
  "backend": {
    "language": "csharp"
  }
}