Skyscrapers in IT

QuantumSoft
10 min readSep 10, 2021

“A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away”

Antoine de Saint-Exupéry

Software development is a complex, challenging process. A developer’s main problem may be the complexity of their domain and it can lead to an increase in support costs and loss of understanding how the system works as a whole. Rules and procedures are necessary for dealing with this problematic issue which leads developers towards the belief that software engineers should always start by simplifying things before scaling them into something more complicated or difficult to understand.

Many books on writing clean code, applying Object-Oriented Programming (OOP), design patterns, and SOLID principles focus solely on the level of creating a class or module. These regulations, however, do not provide enough guidance for complicated systems with numerous business logic needs that span across many subsystems.

Domain-Driven Design (DDD) provides methods to decompose complex system structures into smaller chunks, while structuring subsystem components separately from each other in order to maintain focus within individual domain models.

The purpose of this article is to popularize DDD and focus on strategic concepts, which is often given the least attention when applying this approach.

A Golden Hammer For Golden Nails

Before we dive into the details of domain-driven design, we first need to ask whether we are using a “golden hammer” and then we should try to understand which tool is best for our purposes. When choosing a system implementation method, the key factor is to reduce the complexity and cost of development. According to Martin Fowler’s classification, there are two ways to structure your business layer:

  1. Transaction scripts
  2. Domain models

To help you keep track of the procedures in your code, a transaction script may be beneficial. In most situations, it will adequately suffice for application development. The following steps might be included in a separate script: validating input data, verifying authorization, checking business rules, and updating the model’s state into a database. The service layer is identical to the database transaction boundary. Sometimes the service layer is extracted as it allows for code sharing across scenarios.

This approach has its advantages: simplicity and speed of implementation. Disadvantages can appear as the system grows. Signs of problems can manifest as the following:

  • Entities do not control the correctness of changing their state;
  • An increase side effects in a business scenario that cannot be completed in a reasonable amount of time within a single transaction;
  • Anemic models, when most of the logic is scattered across service classes and model classes are left without behavior, which leads to the developers losing sight of the overall picture and communication with the subject area.

It is feasible to convert a transaction scenario into domain modeling, but it is time-consuming and may necessitate significant refactoring and communication. To make the best choice, we’ll need to assess the requirements for the future system as well as anticipate how long it will take to build it, the number of use cases involved, the number of roles that will be needed in business processes, and external system integration.

Based on personal experience, I can say that one of the simplest and most indicative signs of complexity is the estimated time period for development. If the preliminary assessment for the project is within one or two years and that is when the active development of the system ends and it goes into support mode, it is likely that the cost of finding an exact domain model will not pay off.

If the time period implies active development for three years or more, you should pay attention to the other requirements, while also taking into account the amount of human resources needed to complete the project. The most detailed information about the complexity inherent in software systems is described in Grady Booch’s book [4] in the “Complexity” chapter.

Practical design

The story of the Tower of Babel indicates that one of the most essential ingredients for success is a firm grasp on objectives and effective communication. Immersion in the domain is an important aspect of software development.

Throughout this time, the developer is exposed to a wide range of new words and techniques. DDD makes it simple to learn about the domain. The developers should interact with domain experts during system design by asking questions and ensuring that both the developer and the expert are on the same page in terms related to those words.

As a result of the natural process of communication, a single glossary should be established. In this instance, the single glossary is not a list of specialist words from the domain; instead, it incorporates important terms from both domains.

In DDD, this concept is called a ubiquitous language. It is worth noting that this is very similar to the principles that underlie OOP for designing classes and their interfaces. DDD tries to elevate this idea to an absolute so that even an expert looking at the code can understand and ensure that the program is correct. Therefore, it is imperative that all the terms are clear and consistent. In the process of developing a ubiquitous language, it is also worth paying attention to how consistent the terms are with each other.

Software systems are built to solve business problems. The field in which your business operates is your domain. In any business, there are both general aspects that are common for any field, and specialized ones which are unique to that domain. This is also true for information systems, which also have generic subdomains, such as user identification, notification sending, and payments.

It should be emphasized that in any system there is always a part that is key to the business. This subdomain is the core of the domain. The main efforts should be concentrated on building a quality system core. Defining the subdomains allows you to split up the domain logically based on business processes. This makes it easy to build a context map that defines the boundaries of the physical partition at the code level and describes the types of interaction between the contexts.

The part of the system that encapsulates the business processes from one or more subdomains but does not violate the integrity of the ubiquitous language is called the bounded context. With a properly decomposed system, it is not necessary at all to implement all parts of the system using the DDD approach. For individual parts, a simpler option with a transaction script can be used. Another advantage of this decomposition is the possibility to use ready-made solutions for generic subdomains.

After identifying the most important bounded context, it is necessary to delve into the features to study the model. Model design is the process of displaying the business processes and terms of a bounded context in the form of a set of classes and methods that allow you to effectively resolve business problems. For this purpose, DDD offers a number of tactical patterns that help in building a domain model: aggregate, entity, value object, domain events, and others.

The process of cognition is iterative since it is impossible to immediately cover everything. In the first iteration, the data model is just a starting point, after which a long process of knowledge processing and displaying it on a new model is required. Eric Evans, the author of the DDD concept, introduced the concept of “distillation” to describe the process of improving the model.

The merging of words with the same name but different meanings from various constrained contexts is one of the most typical instances of a lack of communication between experts and coders. This creates a class with low cohesion since some methods and fields are used in some user scenarios without dependence on the others, resulting in excessive coupling between bounded contexts. It also lowers the likelihood that they might develop independently.

Let’s apply the aforementioned ideas to an example from the field of software development services. The examples were created to illustrate the concepts in action. This would take a long time in practice for the domain model to form in a real system, and that is reasonable.

A website must be designed that enables potential customers to submit requests for the deployment of software solutions, after which allows them to join up. The system should automatically assign a manager to each request as soon as it is submitted. Requests can either be accepted or rejected. A cover letter should be sent if a request is denied.

If a proposal is accepted, the client will receive an offer letter. The client will have access to their own account after the contract has ended, allowing them to establish and manage their team. They should also be able to create projects, add activities, and assign team members to projects.

After analyzing the domain, the following contexts have been distinguished:

  • onboarding
  • collaboration
  • development management
  • notifications
  • user identification
Bounded context map

In the contexts of onboarding and notifications, there is an Event entity for both that has the same name but completely different semantics. In the first case, it is a planned meeting of a representative and a potential client, but in the second, this entity describes business events in the system that the user has the opportunity to subscribe to. In contrast to the previous example, sometimes the same physical object in the real world can be described in different terms in different contexts.

It is easy to see in the example of the person submitting a request on the site. In the context of onboarding, they play the role of a potential client, but after signing up on the platform, they have different responsibilities depending on the context in which they are interacting with the system.

In the context of collaboration, they will be responsible for the organization and they will be able to create teams and add participants and determine their roles, but in the context of notifications, they are a subscriber and should be able to manage their subscriptions, but they can also be assigned to tasks in the context of development management. If we insisted on merging these terms into a single User entity, it would be harder to understand and develop the system, as well as blur the boundaries of class responsibility in various contexts.

This is a very common occurrence in large systems. There are typically one or two “god objects” which accumulate a huge amount of functionality which should fit in various different contexts, but it’s always easier to just add one more method or one more database column. When analyzing requirements and defining contexts, you should be guided by the roles and responsibilities that are assigned to the entity. This will help you avoid a big ball of mud architecture.

Off by a meter, can we just move it?

The Leaning Tower of Pisa is a beautiful and iconic landmark that attracts millions of tourists each year. But, the constant repairs it needs are very costly for Italy’s government. The tower was designed to lean because there were not enough people on site when construction began in 1173 who had any architectural experience or knowledge — so they just built what looked good at the time without making sure things would work with gravity. It took almost 100 years before anyone realized a mistake had been made; but instead of fixing it right away, we let them continue building other buildings around this new design flaw as well which lead to all sorts more problems over time including cracks from earthquake damage and sinking foundations due to heavier construction materials now being used than back then.

And this is the very example that we encounter every time in practice when developing software systems, when requirements change over time and with the growth of the system and it becomes necessary to add new functionality. Often when developing complex software systems or startups, there are no exact deadlines, and not all of the requirements are known in advance, so when taking that into account, we need an approach that will reduce the risks of choosing a particular technology or framework.

DDD is not linked to any technology, language, or paradigm. It can be used equally well for both object-oriented and functional languages​. There are many different architectural styles for implementing applications. One of the key benefits of DDD is that this approach focuses on building the business core. This allows you to postpone or revise some technical solutions based on new factors, without significantly increasing the cost of changes, which provides the best conditions for the system to develop naturally.

Traditionally, most programs start being developed as monolithic applications. This is the right path at the initial stage when there is not enough knowledge of the domain to clearly define bounded context boundaries. Over time, the systems grow to enormous sizes, and then the development of monolithic systems becomes difficult. A recently popular way to further develop systems is microservice architecture. If you need to move from a monolith to a microservice architecture, the decomposition of monolithic systems that are built taking into account DDD principles will naturally occur along context boundaries.

Populating

I find that the Colosseum is a wonderful example of architecture as it doesn’t require complex explanations to be understood. The simplicity and conciseness also show in modern-day systems such as those created by developers, which aim at building structures you can admire for their beauty without having extensive knowledge about them.

The design of this system is a sophisticated process that relies on intention, communication, and thoughtful consideration. When looking for the best architecture it’s important to look at the systems from different angles in order to gauge what patterns exist across all levels. Developers who are guided by these rules will not lose sight of an overall picture while improving single parts within their model.

The main factors contributing to this are decomposition of the system into bounded contexts, maintaining a ubiquitous language for every team member involved in development — ensuring that everyone is on the same page and all knowledge can be disseminated quickly without confusion as well as continuous refactoring based on new knowledge about the domain. This approach to system decomposition also makes it possible to effectively organize development teams with distinct responsibilities per project (i.e., design-development pairs) along with being able to scale individual parts of them when needed or desired from one production environment at any point in time.

By Michael Lazko, Senior Backend Developer at QuantumSoft

Glossary

Ubiquitous language is the language structured around the domain model and used by all the team members to describe the business processes and rules in the software development process.

Domain is the field of knowledge, influence, or activity.

Core of the domain is the most important part of the domain model, central to the user’s goals and what makes it valuable to the business.

Bounded context is the context in which the single language and corresponding model are valid. We can include one or more subdomains.

--

--