One of the most common axioms of software design is that there should be separation of the user interface, business logic, and database. There are principles like Dependency Injection, design patterns like MVC, and a variety of frameworks that claim to encourage or enforce this separation. All these focus on the technical details of how to separate these concerns, but they rarely discuss why this is important.
There is a fundamental mind shift that occurs when properly extracting business logic that people are otherwise unaware of or misunderstand. This mind shift is as profound as the difference between assembly and object-orientated code. The impact is not just for designers and programmers, but for business analysts and customers too.
Also note, that I’m not against CRUD designs. They have their place, and in fact most software projects out there use this successfully. Also, I’m not implying that CRUD is easier than DDD, or vice versa. These are two valid approaches to systems development. There isn’t one best approach for all situations.
Each can be used successfully, independently. Even within one system part of it may be a CRUD application and other parts may follow a DDD approach. Sometimes there is a need to transition from CRUD to DDD, or vice versa. My point is that software teams need to be aware that these two approaches exist, have respect for each of them, and be able to transition between the two when necessary.
Before I talk about DDD or transitioning to it, let me first describe a CRUD-style design.
CRUD stands for Create, Read, Update, and Delete. These are the four operations users routinely perform on a database table. This is also known as a data-centric design.
To start designing a CRUD system, the business analyst must identify the entities is the customer’s domain. There will be a page in the application for each entity and a database table to back it. There will be list (probably searchable) to Read the rows in the database, there will be a Create or Add button allow them to create a new row. There will be an Edit page to view all the properties of the row. And finally, there will be a Delete or Deactivate button. The Edit page is the most complicated to design. The business analyst must work with the customer to define all the properties of that entity.
Here are some things to notice when designing a CRUD system:
- It is typically the customer that is defining the database design and user interface. They decide what the tables are in the database and the columns (properties).
- There is a very tight coupling of User Interface and the Database. It doesn’t really matter if the customer describes their desired system in terms of the user interface or the database structure, because there is a simple one-to-one mapping between the two.
- There is little in the system for business logic. There maybe a few validation rules here and there, but in general the developers working on the project are not aware of how the data is used to help the customer’s business. The users are experts in the domain, and they will know what to enter in all the fields or interpret the values entered by others.
Domain Driven Design takes a different approach than CRUD. With DDD the focus is on the business goals and rules, not on the data. DDD focuses on actions and events that are taken to meet those business goals.
To illustrate this, let’s take a hypothetical example of a shipping company. Here is a business workflow used to fill new orders.
- First, we will need select from a list of new orders that haven’t been filled.
- Then we print a shipping label the user can stick to a new empty box. We also need to provide them with the order details so that they know what to put in the box.
- Finally, we verify the box if full by weighing it and comparing the weight with a know weights of the products that should have been loaded. The order is considered Shipped once this verification is done.
The user interface that would be developed to support this design flow from the business process. There are 3 screens/pages in this part of the system, each tailored to the task at hand.
- The new orders list with a select button.
- The order details either on a tablet or printed.
- The verification screen that captures a weight.
Each screen is small. There is limited scope to each. This allows for a lot of flexibility in the design and the technologies used. The orders list can be on a PC. Perhaps a UI for this task isn't needed at all and the order can be automatically selected. The order details may be on a tablet or a printed page. The verification might be done on a PC desktop application or an IoT solutions with a bar code reader and scale interface. Each task in the system has its own user interface, which is easy to change and decoupled from other tasks.
The data needed to support these tasks is separated as well.
- There must be a database (or service) with new orders. An order must be able to be selected and removed from this list.
- The user’s tablet doesn’t need much of a database. It only needs to show one record at a time. It can throw the data away once the task is complete.
- The verification captures a weight, but it doesn’t necessarily need to store it. Once the verification is complete an event can be broadcast to the system that the order has been shipped.
The event that an order has been shipped might be subscribed to by various processes. One process might record in a database that the order has been shipped, which may or may not be the same database where new orders are stored. Another process might inform the customer that their package is on the way. Another process might verify that the shipment has been filled in a timely manner and verify that the order doesn’t need to be resubmitted to the new orders list.
There are lots of possibilities on what the system could do when a package is verified as shipped. But those actions have no impact on the verification process or the person doing that task.
Let’s think about how this same business from the DDD example would have been approached as a CRUD application. In the CRUD application there would be an Orders list, which could be filtered to New Orders. This would open to an Order edit page. The edit page shows the details. There would be a Status drop down list to indicate if the order is New, Being Filled, or Shipped. Users would tell the system of the two required state transitions by selecting the new state and clicking the Save button. There would be a weight field that would be required to be filled if the Status is Shipped. The save would fail if the incorrect weight were entered.
The CRUD system is much easier to describe what technical requirements are. It can be done in a single paragraph. We don’t need to describe the user interface and the database because one exactly matches the other. There is a single business rule, which is easy to implement.
Furthermore, the CRUD design may be able to be used by not just the people filling the orders, but also by people entering new orders and those looking for shipped orders. However, this can be a double-edged sword. Although the code is reused, the layout of the screen may not be suited to all users. There also may be scalability problems. This can also cause maintenance problems; a user needing a new field for order creation will impact the same screen used by people filling orders.
The DDD application may seem more complicated to describe. There are a lot more pieces. There are separate options for user interface and databases, and these can change for each task. Although this might seem more complicated on the surface, it yields a more flexible system. The components are smaller, easier to change, more scalable, and easier to tailor for each user.
DDD also can be more difficult to describe because the business process and goals need to be very well defined. This information needs to be available to developers, project managers, and users. Although difficult, this does allow for the software development team to see the business goals. This can make business opportunities that could be solved through software more visible and possible.
It can be more difficult to learn DDD because of the lack of examples. An experienced CRUD application developer has a golden hammer that clobbers off most of the work on a new project, before you even talk to the customer. With a DDD approach there is no “one solution fits all”. It relies more heavily on developers attacking one problem at a time, using a variety of tools at their disposal, and applying sound software design principles to move the project forward.
Another hurdle in transitioning is how much value is placed on the database. In a CRUD centric world there is a great deal of value placed on the database. A database with $100 million in orders is treated as if the data itself is worth $100 million. In DDD the value is not in the database, but the value provided to the business. A DDD system is worthy if it helps the business ship $100 million in orders. It must assist customers and employees in getting what the want, when they want it. They should feel happy to use the system; it should feel effortless to use, almost to the point where it can be taken for granted. In DDD, the database is not important. It is a minor, technical detail that can be thrown away once the business goals are achieved. Letting go of the database is a very big hurdle for those use to a data-centric system design.
DDD is not for everyone. Some customers feel burdened having to explain their business to developers. Some developers aren’t interested in the customer’s business and would prefer to focus on the technical details.
Most developer begin their careers doing CRUD applications. These types of applications are extremely popular. Once a developer has mastered this skill, they are often pressured to separate the UI, Database, and Business logic. There may be problems maintaining a large CRUD system, that some feel is easily solved by developers separating out these layers.
A sole developer trying to separate a CRUD monolith into different layers without an understanding DDD can be a rather fruitless endeavour. The analysis of what the business needs usually results in a “business” layer with the functions: Create, Read, Update, and Delete. It typically results in an overly complicated onion/lasagna code base, with fundamentally the same design, and that is not easier to maintain.
In this crude attempt, the user interface and database designs don’t change, only an attempt is made to create a business layer in between them. However, the UI and database fit together so well before that introducing a layer in between them feels burdensome. Customers still control the UI and database design. When they need a change they will request a change to both these layers, which inevitably requires a change to the new business layer too. The business layer doesn’t add much value. It just feels like latency in the development process that slows the team down.
To successfully transition from CRUD to DDD systems requires team buy-in. Everyone on the team needs to understand how the approach taken with DDD is fundamentally different that CRUD. The biggest difference in these approaches is not the technologies used, nor the structure of the code, but it is how problems get solved.
A successful DDD team needs buy-in from the customers too. The software team, including project managers and business analysts, needs to work with the customer on their business goals. The goals of the business will be something like cost-effective shipping of packages. The goal of the business is never merely to feed a database.