Understanding Key Concepts in Domain-Driven Design

Ubiquitous Language

The term "ubiquitous" means existing or being everywhere simultaneously. When creating software for a specific business domain, we generally involve three key groups of people in the process.

  • Developers
  • Stakeholders
  • Domain Experts

A common practice is to use a shared language among developers, stakeholders, and domain experts. This language is domain-specific, derived from the domain itself, and should be used consistently in all discussions and documentation.

Let’s understand this with a small example,

Imagine we're working on software for the eCommerce domain. In this domain, there are several key areas or modules, such as Sales, Products, and Shipping.

Focusing on the Sales context, the language used might include terms like "Orders," "Discounts," and "Promotions" to refer to customer purchases. Through discussions and collaboration, we define our models, entities, and contexts with clarity, ensuring that everyone involved shares a common understanding.

Key Characteristics of Ubiquitous Language

  • Consistency: The same terms involved in all processes of software development like developers, stakeholders, and domain experts.
  • Refinement: It refines collaboration and communication between three main pillars.
  • Reflects in Code: The same terms will reflect in the code, which gives better clarity and a better understanding of what we are building.

Bounded Context

It’s a fundamental concept of DDD. The term “Boundary “ means defining the boundaries between different parts of the system to avoid conflict and maintain complexity.

Defining the boundaries helps to manage the large chunk of the system into more manageable pieces and lets them grow independently. It also allows the different teams to work in various contexts instead of stepping into other contexts and helps them to scale individually.

Key Characteristics of Bounded Context

  • Boundaries: Clear boundaries for the particular domain model where it is applicable.
  • Isolation of Model: Each model is separated from others.
  • Integration: Communication between contexts should be through exposed APIs, messaging, or shared events.
  • Autonomy: Each context is responsible for its domain. It can evolve independently, and have its database, and its team responsible for it.

Domain Services

The Domain Service is used when a certain behavior or operation doesn’t naturally fit within a single entity or value object but is important for the domain. It's a very practical way to encapsulate domain logic that cannot be assigned to a particular entity without violating the single responsibility principle.

Key Characteristics of Domain Services:

  1. Stateless: Domain services should not hold state. They are purely operational, containing business logic and acting on one or more entities.
  2. Domain-Specific: The operations encapsulated in a domain service are specific to the business logic of the domain and cannot exist outside of that domain.
  3. Encapsulation: When a behavior involves multiple entities or value objects and doesn’t naturally fit into one, a domain service is the best place to house that logic.
  4. Decoupling: Domain services allow the domain logic to be separated from infrastructure concerns, meaning they are pure business logic and don’t deal with things like database access or external services.

Example

public class DiscountService {
    public BigDecimal applyDiscount(Order order, Discount discount) {
        // Logic to calculate the price after discount
    }
}

Domain Events

A Domain Event represents something that has occurred in the domain that the system cares about. It signals that an important business event has taken place and typically notifies the other parts of the system.

Key Characteristics of Domain Events

  1. Occurrence: For instance, in the eCommerce context, events like "Order Placed", "Payment Completed", or "Shipment Dispatched" would be domain events.
  2. Immutable: Once a domain event is published, its state should not change. It represents a fact in the past that has already happened.
  3. Propagation: Domain events are often used for notifying other bounded contexts or external systems about something that has happened, enabling eventual consistency across the system.

Example

// Event
public class OrderPlacedEvent {
    private final OrderId orderId;
    private final CustomerId customerId;
    private final LocalDateTime occurred;

    public OrderPlacedEvent(OrderId orderId, CustomerId customerId) {
        this.orderId = orderId;
        this.customerId = customerId;
        this.occurred = LocalDateTime.now();
    }
}

// Handler
public class OrderPlacedEventHandler {
    public void handle(OrderPlacedEvent event) {
        // Access the information from the event
        // Send email to customer that the order is confirmed
    }
}

Here is how to use push events.

public class OrderService {

    private final EventPublisher eventPublisher; // Assume this is injected for publishing events

    public OrderService(EventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    // Domain service method for placing an order
    public void placeOrder(Order order) {
        // Business logic to process the order
        validateOrder(order);

        OrderPlacedEvent event = new OrderPlacedEvent(order.OrderId, order.CustomerId);

        eventPublisher.publish(event);  // Publishing the event to notify other systems
    }

    private void validateOrder(Order order) {
        // Validate logic here
    }
}

If you missed the first part of this DDD series, you can check the first part here.

Thank you so much for reading this.


Similar Articles