Many engineering teams spend a lot of time building software to meet business needs, but not enough time trying to understand the business itself. Teams quickly start projects and rush to meet requirements. Early successes are quickly overshadowed by the difficulty to adapt to additional feature requests, changing requirements, or new a set of customers with different needs.
What many organizations end up with is a big ball of mud. Code is in one giant code base, often referred to as a monolith. In the very worst cases, there are multiple versions of the same code with slight modifications to support the varying needs for each customer. The code quickly becomes too complex to safely manage all the requirements for each feature and customer.
This leads to several very common problems:
- Engineers struggle to clearly communicate with domain experts.
- Changes have unintended effects across the system.
- Throughput becomes an issue that can’t be solved by adding more engineers.
- Releases become blocked for various reasons unrelated to the code needing to be released.
- There is not adequate test coverage, which leads to recurring bugs.
- Engineering is treated as a helpless, untrusted necessary evil of the business rather than a vital partner.
It’s very likely that you’ve experienced several of these problems before, and you may have even struggled with how to fix them in your organization.
Solving all of these problems starts by gaining alignment with the business. If you look at your company structure, it’s unlikely your company is represented as one big department with a singular focus. Most likely your company has several departments, each with their own unique set of roles, responsibilities, and problem domains as it relates to the system you are building. If your company can’t be represented as one big entity, why should your software represent itself that way – as one big ball of mud? The goal is to organize your code and your people around these problem domains.
Domain Driven Design (DDD) was introduced in 2004 by Eric Evans. DDD is the concept that the structure and language of software code (class names, class methods, class variables) should match the business domain. Simply stated, it means that your code should model real-world business and processes as closely as possible.
Many teams focus on the language part of DDD. They enjoy the added benefits of modeling their code after the real world and, in turn, solve many of the problems mentioned above. DDD has many advantages far beyond naming your classes and methods correctly. DDD is also meant for strategic use. At Realtracs, we use DDD to structure our system in a way that our squads can strategically organize around parts of the system. This allows us to more closely align with the various problem domains within our organization.
DDD has several main concepts, including:
- Context: The setting that determines the meaning of a word.
- Domain: A sphere of knowledge influence or activity.
- Model: A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain.
- Ubiquitous Language: A language structured around the domain model and used by all team members to connect all the activities of the team with the software.
Strategic DDD deals with how we organize a system. As a thought experiment, think about what the domain of a User means within your system? Does a User mean the same thing to someone in the context of membership as it does to someone in the context of billing? What about in the context of authenticating the user to the system? Do these contexts use the same language to talk about them? Does the user domain have the same attributes in all these contexts?
Strategic Design uses the following terminology:
- Bounded Context: A conceptual limit where a domain model is applicable and has its own Ubiquitous Language.
- Core Domain: The place where we focus most of our attention and time.
- Sub Domain: Supporting domain to the core domain.
- Domain Events: Something that happened in the domain that you want other parts of the domain to be aware of.
What are your core and sub domains? What domains belong in which bounded contexts? In the example above, the User exists differently in 3 different contexts, potentially with different domains:
- Auth
- User – Password, Username …
- Role
- Billing
- User – Payment method, Balance …
- Products
- Subscriptions
- Purchases
- Payments
- Refunds
- Membership
- User – First and Last Name, Email, Address …
- Office
- Company
Auth, Billing, and Membership are three real bounded contexts that exist in our system. Each of these contexts are assigned to a squad, which is responsible for it. Since they helped define it, the language in the code aligns with the language used by non-engineers working on those problem domains. This allows for quick and easy collaboration on the team without confusion or things getting lost in translation. A simplified example of squad alignment can be seen in the diagram below.
In theory, it’s not difficult to separate out the bounded context of your domains to different squads. In practice, it takes dedication and hard work in order to prevent contagion across contexts. Contagion occurs when the boundaries between the contexts start to blur. As an example, say that Membership needed to know whether someone has fully paid their bill. To solve this issue, the development team decides to access the billing data directly.
Contagion prohibits our ability to work independently in teams and to deploy separately. Contagion increases the scope of testing and limits our ability to do continuous integration. Protecting these contexts is integral to how Realtracs operates as a team.
Each bounded context (auth, billing, membership) should start by being in its own code repository. It should be separately deployable and have its own data store, if needed. In order to build a full system, these contexts need to interact with each other. Contexts can communicate with each other synchronously through web services or asynchronously by passing domain events through messaging systems such as queues or topic streams. In all cases, the contexts should operate on a need-to-know basis and should expose as little data as possible to the requester.
Already have a big ball of mud? So did we! It’s never too late to start taking little steps towards a better architecture and better alignment with your business. Breaking away from a monolith design is something many engineers can relate to, so stay tuned for more on this topic.