Previously in this series
- Bounded-Context (this post)
In the previous article, we talked about microservices and how it has been a big change for our industry. If microservices are the foundation of our DevOps practices, Bounded-Contexts are probably the glue that holds it all together.
Bounded-Context is a concept that first appeared in DDD (Domain Driven Design). The idea is to form groups of things that are highly cohesive.
We started our journey by analyzing our current application. We simply used a few post-its to represent the major components of our application and the interaction between them, and ended up with this:
At that point, it was clear to us that we needed to simplify our understanding of the application. It was (almost) impossible for someone to know all of those components and their dependencies by heart. This made changing a piece of code really hard as no one was able to tell for sure the impact of a change. The next step was to take that diagram and find some groups and their boundaries.
Splitting our application in Bounded-Context was the key to make sense of that mess. We used Bounded-Contexts to create a ubiquitous language for every aspect of our architecture.
I know this is very abstract so far, so let's look at an example. In this case, we defined those Bounded-Context as the following:
Our application manages documentation for clinical trials. So this bounded-context represents our core domain and all the logic related to clinical trials.
FrontEnd represents all the public facades we expose. It includes our SPA applications, public APIs and so on. This bounded-context is responsible for managing things like throttling and security. It then routes the request internally to the proper microservice to query data or perform operations.
This one I must admit I'm a bit ashamed of. It's not a real bounded-context per say. It contains all the common components and libraries that are reused by all the other services in the application. It's probably an anti-pattern, but sometimes (for good reasons), rules need to be broken. ¯\(ツ)/¯
Let's be clear, all bounded-context contains their own tests, but some tests are larger than one bounded-context.
i.e. Simulating a complete user flow through browser automation.
This kind of tests will need to be deployed to be run. So creating a bounded-context made sense.
This bounded-context contains all the tooling we've build to help us manage and debug the application. It contains things like a log query engine and debugging tools.
As we are rewriting an old platform and porting it to a more modern architecture, we still need to get some data from the old platform. Instead of polluting all the other bounded-contexts, we decided to consolidate all the code that communicates with the old platform behind a set of microservices in their own bounded-context. This pattern is also known as Anti Corruption Layer (ACL). We don't care too much that this part is not so clean. The goal is to provide a clean interface for other microservices that follow our new standards. In the future, we plan to deprecate this part completely.
Our application is a multi tenant application. This bounded-context makes sure to manage all the information about tenants, users, licenses etc...
Backups are so important that we made sure it's completely separated from the rest of the application. It's deployed and managed separately. That way, if we have an issue with the platform we know we can still use this part to restore a previously working state.
Security is a very complex and important aspect of our platform. This bounded-context contains all the microservices related to authentication and permissions.
This bounded-context contains our admin interface that is not publicly exposed and can only be accessed by our support team. Making it a separate bounded-context allows us to apply different security constraints.
Note: Those microservices are only accessible from our internal network.
What does a bounded-context means exactly?
These bounded-contexts have different representations over specific aspects of our application. They drive DevOps processes, general organization and structure.
Git repository structure
The root of our git repository is splitted per bounded-context which allows a logical way to organize files.
Then, in each bounded-context folder we have an
.sln containing the required
.csproj. Having one
.sln per bounded-context allows developers to work on a much smaller solution, which is by far more efficient. If your bounded-contexts are splitted properly, you should almost never have to work in two
.sln at the same time. This reduces load time, build time and speed up static analysis, and test runs.
i.e. Tenant bounded-context
VS project structure
As mentioned previously, each bounded-context maps to one ServiceFabric application, which itself can contain multiple microservices.
Once deployed in ServiceFabric, we have our 2 microservices:
UserProfileService under the
As for the build pipeline, we have one Continuous Integration (CI) build per bounded-context with specific git triggers based on the physical path on disk. We only launch a build if a file has changed under a specific path. This optimizes the build time and give us the flexibility to have different quality gates per bounded-context.
Tenants-CI build will be launched only if some files are changed under
For each bounded-context we also create one package build. This build will build and package the required artifacts to be deployed. We further have one release pipeline linked to that build completion. That way, when we kick in a package build, if it succeeds, the release pipeline will start rolling that package through all our environments automatically.
Tenants-Package will package the code of the ServiceFabric application and all the PowerShell scripts required for Infrastructure as Code (IaC) for this bounded-context.
In Azure, we structure our resources also per bounded-context. In this situation, one bounded-context maps to an Azure resource group. Resource group are logical containers and are usually used as deployment unit.
Bounded-contexts also have an impact on how we structure the development teams. Our long term goal is to have one team owning one bounded-context. Owning means that the team is reponsible and have the capability of managing all the parts of the Software Development Life Cycle (SDLC). A team is responsible to plan, design, develop, deploy, operate and monitor the bounded-context they own. I won't lie to you, we are still a small company, so teams actually own more than one bounded-context. So far, it's working alright, but in order to scale to the level we want, we know we'll have to add new teams.
As you can see, Bounded-contexts are very important. Their main goal is to create cohesion and independence. When done properly, it makes the development teams go way faster and give them a real sense of ownership. They feel empowered and trusted. All of this without giving up on quality and governance.
Also in this series
- Bounded-Context (this post)