Poor cohesion between engineers, product owners, and executive teams can be a limiting factor in software development. It often leads to two problem scenarios:
1. Your Company Struggles to Build Quality Software
The problem is not staff – you have talented engineers, knowledgeable agile professionals, and top business leadership. They are all paid at or above the market rate. Everyone is very motivated to succeed and frustrated with the lack of results.
Yet when engineers, agile professionals and business leaders work together they fail, over and over again. Ask each group what the most important features of the software are and why they are not implemented correctly; they answer as if they all work at different companies. They don’t even use the same language to describe the problem.
Your company will stagnate as a result.
“If you think good architecture is expensive, try bad architecture”
Brian Foote
2. Your Software Disappoints
If this sounds familiar, you’re probably suffering from one or more of the following problems:
Software Can’t be Updated to Perform Required Business Functions
Some of our clients struggle with software that was once acceptable but no longer meets current business needs. For example:
- Customers cannot get through basic workflows without manual entry of data or cumbersome workarounds.
- Meeting new regulatory requirements or even adding new features requires tedious manual workarounds by engineers or other staff.
- Staff end up entering information customers could have entered for themselves if the software provided for it.
- Attempts to incorporate new features fail or can’t be attempted because engineering resources are fully occupied just keeping the software running.
Software Integrates Poorly with Existing Systems
Integration with existing systems can be difficult. This often results in unnecessary workarounds or poor performance. Symptoms include:
- Requiring customers to take unnecessary actions such as entering the same information multiple times in the same workflow.
- Poor integration with existing systems slows the flow of actionable information to key decision makers.
- Upgrades to one system require corresponding upgrades to multiple systems. Each upgrade needs to be intricately sequenced to accommodate a complex web of dependencies.
Software Cannot be Updated Without Introducing New Defects
You have good engineers but, quarter by quarter, their performance degrades:
- It takes weeks to get even small changes or bug fixes through development and testing.
- As a result, the number of production deployments per quarter decreases. The size of each deployment grows correspondingly, increasing the time and difficulty of testing and making it more likely that a defect is released into production.
- With more code and more defects released with each production deployment, the percentage of deployments resulting in customer complaints and emergency hot fixes increases.
- Because the amount of code released with each production deployment is increasing, the time to investigate, fix and test production defects also increases.
- The increase in production defects prompts management to increase the amount of time spent on testing, most often manual testing ‘just to be sure’ the automated tests are running correctly. This further slows the deployment rate, increases the amount of code in each deployment, increases the odds of a production defect, and increases the time to fix that defect.
This becomes a downward spiral, as illustrated in the above diagram. Dissatisfied customers don’t understand why your software can’t be made to work properly. They are exasperated by wasted time, expense, and overall poor quality, and will likely switch to other providers if given the chance.
Why is all this happening?
Put simply, your teams don’t understand one another. A common problem is that engineers, product owners, and businesspeople each have a different understanding of how the software works and what it is supposed to do.
Each group has its own name for particular software features and there is little shared knowledge or even language. Worse, groups don’t realise the extent to which they disagree with the others on what is most important to build or fix first. Each group thinks their own perceptions are correct and that those perceptions should be intuitively obvious to all.
When groups speak about software using terms and language only understood by themselves, other listeners are left to translate it as best they can. Getting agreement, even on the implementation of small changes, becomes very difficult.
Talented Team Members but Ineffective Teams
As a result, engineering teams are unable to build essential features. Repeated attempts to solve the same problem always end in failure, and adding new features or fixing defects is time consuming and error prone. Every production release is followed by a wave of calls to customer support.
The problem is often not due to any individual group. The problem is the way groups work with one another – a symptom of their lack of shared understanding about the business and the software it builds. They don’t speak the same language and the software reflects that.
Four Steps to Better Software Using Domain-Driven Design
Domain-driven design is an industry-standard approach that can be tailored to solve exactly this sort of problem. While it may be a complex process, it can be broken down into a few critically important steps that produce rapid results.
1. Bring Domain Experts and Technologists Together
Domain experts are people who know the business, the products, and the customers. They know what the customer needs and how that aligns with what your software does.
The technologists who design and build the software need to work cohesively with domain experts as one team. However, in many companies, software architecture is disconnected from the business. Domain experts do not understand the software – even in technology driven businesses. This becomes obvious when a business user speaks about software functionality using one set of terms and then a developer speaks about the same functionality using a completely different set of terms.
Bringing domain experts and technologists together can be a lengthy process, but it always starts with building a common language.
2. Build a Common Language
It is very hard for a team to fix a problem if they don’t even use the same terms. So, the first step is to create a dictionary of agreed-upon terms, enabling everyone to speak the same language. We call this a ‘ubiquitous language’, meaning the same terms are used by everyone associated with the software.
Simple misunderstandings can cause expensive software defects. For example, software that manages corporate sales contests and prizes might integrate with both the Sales and Finance departments. In this case the simple term ‘close’ could refer to finalising a sale, the end of a business quarter, or when a sales contest concludes. Disambiguating the term ‘close’ is critical to avoid defects and ensure each department fully understands what the software is doing. Multiplying this example by dozens or hundreds of overloaded terms provides a glimpse into the problems many companies face.
The ubiquitous language is embedded in the software code as well as documentation. Software’s code must speak the language of the business – not the language of the engineers who write it. As staff come and go, the ubiquitous language encapsulated in code and documentation results in continued understanding of the software, and often benefits cultural continuity too.
“The design is the code, and the code is the design. The design is how it works.”
Vaughn Vernon, Implementing Domain-Driven Design
3. Build a Domain Model of Real-World Needs
Once everyone agrees to speak the same language a real-world model of business needs can be created. We call this a domain model.
Domain models vary between two extremes:
- Anaemic models which lead to architectures with multi-purpose common components handling most of the logic and domain objects. These end up being little more than simple data objects with getters and setters and little logic, often known as POJOs. Since there are no clear responsibility boundaries, logic related to any one domain is often scattered across multiple components. Anaemic models fare poorly when mapped to distributed architectures like microservices. The result is often a ‘distributed monolith’ that is worse than the application you started with.
- Rich or robust models on the other hand see domains represented by self-contained components with a single business purpose. They contain both the data and logic necessary to perform their function. Rich models tend to map well to distributed architectures like microservices.
Our approach is to analyse, design, and refactor only those domains that will provide the biggest return to clients. We call these ‘core’ domains, fundamental to the company’s position in the marketplace and giving it a critical edge over the competition. The below diagram illustrates typical core domains for a commercial insurance company.
‘Generic’ domains (which facilitate operational aspects of the business that can often be performed with off-the-shelf software – such as an invoicing system) or ‘supporting’ domains (which relate to core business functions but do not require top engineering talent for build or maintenance) are rarely fully modeled and refactored.
4. Map the Domain Model to Software Architecture
There are exceptions, but most often the target architecture is some form of microservices. In such an architecture, the following can be said of each microservice:
- It’s a small but complete application in itself.
- It’s as decoupled from other microservices as possible.
- It has a single business purpose.
- The service’s single business task is meaningful to business users – there should be no services with only a technical or infrastructure purpose.
- It has all the data and logic required to perform this business purpose.
- It has a clear interface and dependencies so it runs independently of other services and an engineering team can maintain it independently of other engineering teams.
Design is critical here. A poorly designed microservice architecture is often worse than whatever architecture you started with. To build a good microservice architecture it is important to accurately model the underlying domains. Unlike earlier modeling paradigms, domain-driven design practitioners realise that accurately modeling an entire enterprise is theoretically possible, but not practical.
The better approach is to break apart the larger domain model into smaller sections called ‘bounded contexts’. These are often drawn around functional areas of a company, for example a ‘sales’ bounded context or a ‘shipping’ bounded context. A group of bounded contexts is arranged in what is called a ‘context map’.
An interesting point about bounded contexts is that they help determine how microservices communicate with each other and the granularity of information exchanged.
For example, when a sale is completed, communication between microservices within the sales bounded context tends to be synchronous and fine grained. A sales representative’s commissions need to be updated, territory sales numbers recalculated, pipeline information brought up to date, customer information modified. It’s possible to accomplish this through an asynchronous messaging scheme, but messaging disparate pieces of information all related to one action can become cumbersome. It is often easier and more direct to accomplish this with synchronous interactions between services.
On the other hand, communication between bounded contexts tends to be coarse-grained and asynchronous when a sale is made. In this case bounded contexts such as ‘shipping’, ‘inventory’, and ‘finance’ need to know is something like “10 widgets were sold on 12/01/23 for $150 each to customer X”. There is often no need to acknowledge receipt of the message. Once the message is received, shipping proceeds to send customer X the 10 widgets, inventory decrements its totals by 10, finance records $1,500 in new revenue and other bounded contexts perform their own business tasks on the same information. We could accomplish this through synchronous interactions, but it would be slow and there is no need. It is easier and faster to post the message and let each subscriber act on the information as they need to.
As this case illustrates, the domain-driven design process allows for groupings of services that interact with each other in an optimal way. It also accommodates future business model changes. Success in software and business is often won by those who best adapt to change.
Building Software that Shouts the Language of Your Business
Using the context map as a guide, software is gradually altered to increasingly reflect real-world business needs while maintaining continuity of service. It sounds easy but it is not. There is a lot of work and a lot of negotiation that goes into this process.
Domain modeling provides other benefits aside from well-designed software that meets customer needs. When a company knows what its most important tasks are, it can assign the best engineers, agile professionals, and product owners to them. It also results in better communication between technical and non-technical staff. When everyone speaks a ubiquitous language, the shared understanding extends to front line engineers. The code they write uses the same terms and is so clear even businesspeople can understand it. Engineers and businesspeople use the same terms everywhere – no special terms are used by either group. This in turn brings better continuity. The same terminology carries forward as staff enter and leave the company. There is no drift in understanding as time passes.
Ultimately, all staff must understand what the software needs to do, what it does not need to do, and why. This becomes possible when software is modeled on the real-world needs of your business. The end result is software that reflects the mental model of the business experts. The design is the code, and the code is the design. The design is simply how it works.