Domain-driven design (DDD) is a critical approach in software development, yet its essence often gets buried under layers of complexity and misunderstanding. At its core, DDD aims to align software design with the needs of the business or stakeholders, ensuring that what we build addresses real problems. Even that sounds like a cliche in software development, and obviously, it is still a challenge to most organizations.
One standout example is Forbes’ 16 Obstacles to a Successful Software Project, which emphasizes challenges such as hyper-focused planning, unclear expectations, and poor collaboration. These issues often lead to scenarios where software is more about managing complexity than solving problems.
This disconnect can be likened to going to a restaurant, ordering a pizza, waiting for hours, and then receiving a Caesar salad with an expensive bill. You pay for something you didn’t want and didn’t need. Unfortunately, this is still the reality in many software projects. DDD aims to change that. However, before diving into how it helps, it’s essential to clarify some common misconceptions and explain what DDD is not.
DDD is not a framework, programming language, or specific paradigm. It’s a methodology that can be applied across languages and frameworks. DDD is not inherently tied to microservices but can help define bounded contexts. Most importantly, DDD is not solely about code—it’s about fostering a positive communication process between developers and domain experts to translate business knowledge accurately into software.
The essence of DDD lies in its ability to extract and encapsulate domain knowledge. It focuses on transferring this knowledge, often held by domain experts, into the software design process. In practice, DDD is divided into two primary parts: strategic and tactical. While tactical patterns often steal the spotlight, strategic DDD is the foundation that ensures those patterns make sense in the larger context.
Strategic Domain-Driven Design starts with determining the Domain and its Subdomains. This step also includes creating a common vocabulary, which removes any vagueness by embedding the meaning of words in their context. For example, “Ajax” can mean a soccer team, a cleaning agent, or front-end technology, depending on the context. This enables teams to mutually understand four aspects of bridging business and IT.
After the strategies are formulated, tactical DDD starts. It involves incorporating the patterns known as entities, value objects, repositories, services, aggregates and factories. Each of these patterns has an intended use in the design:
- Entity: An object with a distinct identity retained through time. For example, a Vehicle (Car) with a unique VIN Number.
- Value Object: A collection of attributes considered to have no existence, such as the name of the Manufacturer and the model number, which are used as the basic building blocks and never change.
- Repository: A mechanism for persistent store objects and a type of service where business logic is defined, enabling us to interact with and manipulate entities.
- Service: Stateless operations do not belong to or are associated with a particular entity or value object.
- Aggregate: A collection of related entities and value objects treated as a single unit regarding data changes within a specified limit.
- Factory: Complex obj creation and management.
Let’s explore a simple example using Jakarta Data and Eclipse JNoSQL to see how these concepts translate into practice. Imagine we are building a car management system. Our first step is to define the core components: the Car entity and the Manufacturer value object.
@Entity
public class Car {
@Id
private String vin;
@Column
private String transmission;
@Column
private Manufacturer manufacturer;
@Column
private String color;
}
@Embeddable(Embeddable.EmbeddableType.GROUPING)
public record Manufacturer(@Column String name, @Column String model) {
}
The car represents an Entity with VIN. We also have the Manufacturer as a value object, which is immutable, represented by a record, and contains the car’s maker details.
Next, we define a repository interface using Jakarta Data. This repository captures the domain language, making it intuitive and closely aligned with business terminology:
@Repository
public interface Garage {
@Save
Car park(Car car);
@Delete
void unpark(Car car);
@Find
Car checkRegistration(@By("vin") String vin);
}
The availability of methods like park, unpark, and checkRegistration, which may easily be understood in layman’s English as about that particular domain, makes the code sensible to programmers and the business community.
When developed to its fullest potential, Domain-Driven Design aids gains and objectives throughout as it helps teams deliver software that can respond effectively and efficiently to the interests of all those who sponsor the development of the software. It facilitates teamwork, reduces the chances of conflicts, and ensures that all activities, including planning and implementation, are centered on the business. DDD and frameworks such as Jakarta Data are instrumental in producing functional software and can address the correct issues. To explore and see the source code, go to GitHub: soujava/ddd-zero-hero.