DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Domain-Driven-Design With JPA: A Practical Guide
  • When Events Move Faster Than Your Database: A Resilient Design Pattern
  • Architectural Evidence in Enterprise Java: Making Domain-Driven Design Visible
  • Strategic Domain-Driven Design: The Forgotten Foundation of Great Software

Trending

  • Stop Running Two Data Systems for One Agent Query
  • Run Gemma 4 on Your Laptop: A Hands-On Guide to Google's Latest Open Multimodal LLM
  • Data Contracts as the "Circuit Breaker" for Model Reliability
  • Why Your DLP Policies Fall Short the Moment AI Agents Enter the Picture
  1. DZone
  2. Data Engineering
  3. Databases
  4. Tactical Domain-Driven Design: Bringing Strategy to Code

Tactical Domain-Driven Design: Bringing Strategy to Code

Tactical DDD transforms business understanding into code through seven core patterns — from entities to domain events — building software that truly reflects the domain.

By 
Otavio Santana user avatar
Otavio Santana
DZone Core CORE ·
Nov. 06, 25 · Analysis
Likes (1)
Comment
Save
Tweet
Share
3.5K Views

Join the DZone community and get the full member experience.

Join For Free

In the previous article, I discussed the most often overlooked aspect of Domain-Driven Design: the strategic side. When it comes to software development, teams tend to rush toward code, believing that implementation will clarify the domain. History shows the opposite — building without understanding the underlying reason or direction often leads to systems that are technically correct but conceptually wrong. As the old Latin root of strategy (strategos, “the art of the general”) suggests, the plan must precede the movement.

Now that we’ve explored the “why” and “what,” it’s time to turn to the “how.” Tactical DDD represents this next step — the process of transforming a well-understood domain into expressive, maintainable code. While strategic design defines boundaries and fosters a shared understanding, tactical design brings those ideas to life within each bounded context.

Tactical DDD focuses on implementing the domain model. It provides a rich vocabulary of design patterns — entities, value objects, aggregates, repositories, and domain services — each serving a precise purpose in expressing business logic. These patterns were not invented from scratch by Eric Evans; instead, they emerged from decades of object-oriented design thinking, later refined to fit complex business domains. The term “entity,” for instance, descends from the Latin “entitas” — “being” — emphasizing identity and continuity through change, while “value objects” recall the algebraic notion of equality by content rather than identity.

What makes tactical DDD essential is its ability to create models that are not only accurate but also resilient to change in an era of a vast amount of tools and architecture patterns, such as distributed systems, microservices, and cloud-native architectures. Without a good direction, we can mislead and generate unnecessary complexity. This layer bridges the conceptual clarity of the strategic model with the practical demands of implementation. Tactical design ensures that business rules are captured in code rather than scattered across services, controllers, or database scripts. It’s about writing software that behaves like the business, not merely one that stores its data.

As the strategic part defines direction, the tactical part defines execution. It consists of seven essential patterns that turn concepts into code.

  • Entities – Objects with identity that persist and evolve.
  • Value Objects – Immutable objects defined only by their attributes.
  • Aggregates – Groups of related entities ensuring consistent boundaries.
  • Repositories – Interfaces that abstract persistence of aggregates.
  • Factories – Responsible for creating complex domain objects.
  • Domain Services – Hold domain logic that doesn’t fit an entity or value object.
  • Domain Events – Capture and communicate significant occurrences in the domain.

Each plays a specific role in expressing business logic faithfully within a bounded context. Together, they bring the domain model to life, ensuring that design decisions remain aligned with the business, even as they are implemented deep within the code.

Entities

Entities represent domain objects with a unique identity, or ID,  that persists over time, even as their attributes might change. They model continuity — something that remains the same even when its data evolves.

They capture real-world concepts like Order, Customer, or Invoice, where identity defines existence. In e-commerce, an Order remains the same object whether it’s created, updated, or completed.

Java
 
public class Order {

    private final UUID orderId;

    private List<OrderItem> items = new ArrayList<>();

    private OrderStatus status = OrderStatus.NEW;

   public void addItem(OrderItem item) {
        items.add(item);
    }

}


Value Objects

Value Objects describe elements of the domain that are defined entirely by their values, not by their identity. They are immutable, replaceable, and ensure equality through content.

In practice, value objects like Money, Address, or DateRange make models safer and more precise. For example, a Money object adds two amounts of the same currency, ensuring correctness and immutability.

Java
 
public record Money(BigDecimal amount, String currency) {

    public Money add(Money other) {

        if (!currency.equals(other.currency())){
            throw new IllegalArgumentException("Currencies must match");
         }
        return new Money(amount.add(other.amount()), currency);
    }
}


Aggregates

Aggregates organize related entities and value objects under a single consistency boundary, ensuring that business rules remain valid. The aggregate root acts as the guardian of its internal state.

A typical example is an Order controlling its OrderItems. All modifications are routed through the root, preserving invariants such as total price and item limits.

Java
 
public class Order {

    private final UUID orderId;

    private final List<OrderItem> items = new ArrayList<>();

    public void addItem(Product product, int quantity) {

        items.add(new OrderItem(product, quantity));

    }

    public BigDecimal total() {

        return items.stream() .map(OrderItem::subtotal).reduce(BigDecimal.ZERO, BigDecimal::add);

    }

}


Repositories

Repositories abstract the way aggregates are stored and retrieved, allowing the domain to stay independent of database concerns. They act as in-memory collections that handle persistence transparently.

A repository enables the domain to operate at a higher level, focusing on business logic rather than SQL or API calls. For example, an OrderRepository manages how Order objects are saved or found, without exposing infrastructure details.

Java
 
public interface OrderRepository {

    Optional<Order> findById(UUID id);

    void save(Order order);

    void delete(Order order);

}


Factories

Factories are responsible for creating complex domain objects while ensuring that all invariants are satisfied. They centralize creation logic, keeping entities free from construction complexity.

When creating an Order, for example, a factory ensures the object starts in a valid state and respects business rules — avoiding scattered creation logic throughout the code.

Java
 
public class OrderFactory {

    public Order create(Customer customer, List<Product> products) {

        Order order = new Order(UUID.randomUUID(), customer);

        products.forEach(p -> order.addItem(p, 1));

        return order;

    }

}


Domain Services

Domain Services hold domain logic that doesn’t naturally belong to an entity or value object. They express behaviors that involve multiple aggregates or cross-cutting business rules.

For instance, a PaymentService could coordinate payment processing for an order. It operates at the domain level, preserving the model’s purity while integrating with external systems when necessary.

Java
 
public class PaymentService {

    private final PaymentGateway gateway;

    public PaymentService(PaymentGateway gateway) {
        this.gateway = gateway;
    }


    public PaymentReceipt processPayment(Order order, Money amount) {
        return gateway.charge(order.getOrderId(), amount);
    }

}


Domain Events

Domain Events capture meaningful occurrences within the business domain. They represent something that happened — not an external trigger, but a fact that the domain itself wants to share. This makes the model more expressive, reactive, and aligned with real business language.

For example, when an Order is placed, it can publish an OrderPlacedEvent. Other parts of the system, such as billing, shipping, or notification services, can then react independently, promoting decoupling and scalability.

Java
 
public record OrderPlacedEvent(UUID orderId, Instant occurredAt) {

    public static OrderPlacedEvent from(Order order) {
        return new OrderPlacedEvent(order.getOrderId(), Instant.now());
    }
}


Application Services — Orchestrating Use Cases

Although Application Services are not part of the original seven tactical DDD patterns, they deserve mention for their role in modern architectures. They act as use-case orchestrators, coordinating domain operations without containing business logic themselves. Application services sit above the domain layer, ensuring that controllers, APIs, or message handlers remain thin and focused on their primary purpose: communication.

For example, when placing an order, an application service coordinates the creation of the Order, its persistence, and the payment process. The domain remains responsible for what happens, while the application service decides when and in which sequence those actions occur.

Java
 
public class OrderApplicationService {

    private final OrderRepository repository;

    private final PaymentService paymentService;

    private final OrderFactory factory;

    public OrderApplicationService(OrderRepository repository,
                                   PaymentService paymentService,
                                   OrderFactory factory) {

        this.repository = repository;
        this.paymentService = paymentService;
        this.factory = factory;
    }


    @Transactional
    public void placeOrder(Customer customer, List<Product> products) {
        Order order = factory.create(customer, products);
        repository.save(order);
        paymentService.processPayment(order, order.total());
    }

}


In practice, application services serve as the entry points for use cases, managing transactions, invoking domain logic, and triggering external integrations as needed. They maintain the model’s purity while enabling the system to execute coherent business flows from end to end.

Conclusion

Tactical Domain-Driven Design brings strategy to life. While the strategic side defines boundaries and shared understanding, the tactical patterns — entities, value objects, aggregates, repositories, factories, domain services, and domain events — translate that vision into expressive, maintainable code. Even the application service, although not part of the original seven, plays a vital role in orchestrating use cases and maintaining the model's purity.

Database Design Domain-driven design

Opinions expressed by DZone contributors are their own.

Related

  • Domain-Driven-Design With JPA: A Practical Guide
  • When Events Move Faster Than Your Database: A Resilient Design Pattern
  • Architectural Evidence in Enterprise Java: Making Domain-Driven Design Visible
  • Strategic Domain-Driven Design: The Forgotten Foundation of Great Software

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook