Strategic DDD vs Tactical DDD Explained

Strategic DDD vs Tactical DDD Explained

By

6 min read··

dddsoftware-architecture

Strategic DDD identifies business priorities, bounded contexts, and relationships between models and teams. Tactical DDD implements behavior inside a context using patterns such as entities, value objects, aggregates, and domain events. Start with the strategic questions, then choose only the tactical patterns that the context's business rules and complexity justify.

What Is the Difference Between Strategic and Tactical DDD?

Domain-Driven Design has two distinct parts:

  • Strategic DDD - the big picture. Where are the boundaries? How do teams collaborate? Which parts of the domain are most important?
  • Tactical DDD - the implementation details. Aggregates, entities, value objects, domain events, repositories.

Most developers jump straight to tactical patterns. They learn about aggregates and value objects and apply them everywhere. But without strategic thinking, you end up with perfectly modeled code inside the wrong boundaries.

ConcernStrategic DDDTactical DDD
FocusBusiness priorities and model boundariesBehavior and consistency within a model
ToolsSubdomains, bounded contexts, context mapsEntities, value objects, aggregates, domain events
Key questionWhich model belongs to which context?Where should this rule be enforced?
ParticipantsDomain experts, developers, and responsible teamsDomain experts and developers implementing behavior
FeedbackIntegration reveals boundary problemsWorking code reveals missing concepts and invariants

Strategic DDD

Strategic DDD answers the question: how should we divide and organize the system?

Bounded Contexts

A bounded context defines a boundary where a domain model applies. The same real-world concept has different meanings in different contexts.

"Customer" in the Sales context has a name, company, and purchase history. "Customer" in the Billing context has a payment method and billing address. Same word, completely different models.

Three bounded contexts - Sales, Billing, and Support - each with its own Customer model carrying different attributes, showing the same word means different things per context

Context Maps

A context map shows the relationships between bounded contexts:

  • Partnership - two teams cooperate, both sides adapt
  • Customer-Supplier - upstream provides, downstream consumes
  • Conformist - downstream adopts the upstream model as-is
  • Anti-Corruption Layer - downstream translates the upstream model into its own language
  • Open Host Service - upstream provides a published API
  • Shared Kernel - two contexts share a small piece of the model
A context map showing Sales in a partnership with Marketing, a customer-supplier relationship from Sales to Billing, and Billing integrating with an external payment gateway through an anti-corruption layer

Subdomains

Not all parts of the system are equally important:

  • Core subdomain - what differentiates your business, such as an order-matching algorithm or a specialized pricing engine. Prioritize its modeling and development effort.
  • Supporting subdomain - important but not differentiating (inventory management). Use simpler models and moderate investment.
  • Generic subdomain - a capability with established solutions that does not differentiate this business. Email delivery or authentication often qualifies, but an identity provider would classify its authentication product differently.

This classification guides investment, while actual rule complexity guides the implementation. A core capability does not need every tactical pattern, and a supporting capability can still have complex rules that benefit from a rich domain model.

Ubiquitous Language

Ubiquitous language is a shared vocabulary between developers and domain experts within a bounded context. The same terms appear in conversations, documentation, and code:

// The code reads like the domain experts talk
public interface IOrderActions
{
    void Place();
    void Confirm();
    void Ship();
    void Cancel(string reason);
}

This interface illustrates names rather than a complete lifecycle. Choose names domain experts use instead of hiding distinct operations behind UpdateStatus().

Tactical DDD

Tactical DDD answers the question: how do we model within a bounded context?

Building Blocks

  • Entity - has identity and a lifecycle (Order, Customer)
  • Value Object - defined by its attributes (Money, Address, Email)
  • Aggregate - a consistency boundary (Order with its LineItems)
  • Domain Event - a record of something that happened (OrderPlacedEvent)
  • Domain Service - domain behavior that does not naturally belong to an entity or value object (PricingService)
  • Repository - a persistence abstraction (IOrderRepository)
  • Factory - complex object creation (Order.Create(...))
  • Specification - encapsulated query rules (PremiumCustomerSpec)

Aggregate Example

This standalone domain example uses USD prices and keeps the order lifecycle explicit. Put these types in Order.cs; an application handler supplies catalog prices, saves the aggregate, and dispatches its recorded events after a successful commit:

public enum OrderStatus { Draft, Placed }
public abstract record OrderEvent(Guid OrderId);
public sealed record OrderCreatedEvent(Guid OrderId) : OrderEvent(OrderId);
public sealed record OrderPlacedEvent(Guid OrderId, decimal TotalUsd)
    : OrderEvent(OrderId);
public sealed record LineItem(Guid ProductId, int Quantity, decimal UnitPrice);

public sealed class Order
{
    private readonly List<LineItem> _lineItems = [];
    private readonly List<OrderEvent> _events = [];

    private Order(Guid customerId)
    {
        Id = Guid.NewGuid();
        CustomerId = customerId;
        _events.Add(new OrderCreatedEvent(Id));
    }

    public Guid Id { get; }
    public Guid CustomerId { get; }
    public OrderStatus Status { get; private set; } = OrderStatus.Draft;
    public decimal TotalUsd => _lineItems.Sum(i => i.Quantity * i.UnitPrice);
    public IReadOnlyList<LineItem> LineItems => _lineItems.AsReadOnly();
    public IReadOnlyList<OrderEvent> Events => _events.AsReadOnly();

    public static Order Create(Guid customerId)
    {
        if (customerId == Guid.Empty)
            throw new ArgumentException("A customer is required.");

        return new Order(customerId);
    }

    public void AddLineItem(Guid productId, int quantity, decimal unitPrice)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Only draft orders accept items.");
        if (productId == Guid.Empty || quantity <= 0 || unitPrice < 0)
            throw new ArgumentException("Invalid line item.");

        _lineItems.Add(new LineItem(productId, quantity, unitPrice));
    }

    public void Place()
    {
        if (Status != OrderStatus.Draft || _lineItems.Count == 0)
            throw new InvalidOperationException("A nonempty draft is required.");

        Status = OrderStatus.Placed;
        _events.Add(new OrderPlacedEvent(Id, TotalUsd));
    }
}

Common Mistakes

Mistake 1: Tactical Without Strategic

Applying aggregates, value objects, and domain events without first understanding bounded contexts. Result: a tangled monolith with rich domain objects that have the wrong boundaries.

Mistake 2: DDD Everywhere

Using tactical DDD patterns for every part of the system. The user settings page doesn't need aggregates and domain events. It needs simple CRUD.

Mistake 3: Ignoring Context Maps

Building modules in isolation without mapping their relationships. Result: inconsistent integration, duplicated concepts, and unclear ownership.

Mistake 4: One Model to Rule Them All

Creating a single Customer entity used across Sales, Billing, and Support. Each context needs its own model of the customer.

Decision Framework

A decision flow that starts with strategic DDD - subdomains, bounded contexts, relationships, and ubiquitous language - then branches by subdomain type: core gets full tactical DDD, supporting gets simpler patterns, generic gets CRUD or off-the-shelf

Summary

Strategic vs Tactical DDD:

  1. Strategic first - establish candidate boundaries, then refine them through implementation feedback
  2. Bounded contexts define where models apply - same word, different meaning
  3. Context maps define how contexts relate - partnership, ACL, shared kernel
  4. Subdomain classification guides investment, while rule complexity guides implementation
  5. Tactical patterns belong inside bounded contexts - aggregates, entities, value objects
  6. Choose patterns selectively - a context does not need every tactical pattern to use DDD

Revisit the boundaries when the code or integration work exposes a mismatch in language or ownership.

Thanks for reading.

And stay awesome!


Frequently Asked Questions

What is the difference between strategic and tactical DDD?

Strategic DDD is about dividing the system: identifying subdomains, defining bounded contexts, and mapping their relationships. Tactical DDD is about modeling inside a boundary with aggregates, entities, value objects, and domain events.

Which comes first, strategic or tactical DDD?

Begin with strategic questions and refine the boundaries as implementation teaches you more. Applying tactical patterns inside the wrong boundaries produces well-modeled code that still couples the wrong things together.

What are the three types of subdomains in DDD?

Core subdomains differentiate your business and deserve the most investment. Supporting subdomains are important but not differentiating. Generic subdomains have established solutions and are candidates for reuse or purchase; their classification depends on the business.

Do all parts of a system need tactical DDD patterns?

No. Reserve full tactical DDD for core subdomains with complex rules. Supporting subdomains can use simpler models, and generic subdomains are usually best served by CRUD or off-the-shelf solutions.

What is a context map in DDD?

A context map documents the relationships between bounded contexts: partnership, customer-supplier, conformist, anti-corruption layer, open host service, and shared kernel. It makes integration and team dependencies explicit.

  • Aggregate Design in DDD - Rules, Boundaries, and Consistency

    Aggregates are the most important tactical pattern in Domain-Driven Design. They define consistency boundaries, enforce invariants, and protect your domain model. Here are the rules and practical guidance for designing aggregates in .NET.

  • Aggregate Root in DDD: Rules and Implementation

    The Aggregate Root is the gatekeeper of consistency in Domain-Driven Design. Here are the rules for designing aggregate roots and implementing them in C#.

  • The Always-Valid Domain Model

    An always-valid domain model enforces business invariants at construction and on every state change. Private constructors, validated value objects, and guarded methods reduce repeated validation while keeping persistence and concurrency checks explicit.

Loading comments...

Whenever you're ready, there are 4 ways I can help you:

  1. Pragmatic Clean Architecture: Join 5,000+ students in this comprehensive course that will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture.
  2. Modular Monolith Architecture: Join 2,800+ engineers in this in-depth course that will transform the way you build modern systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario.
  3. Pragmatic REST APIs: Join 1,900+ students in this course that will teach you how to build production-ready REST APIs using the latest ASP.NET Core features and best practices. It includes a fully functional UI application that we'll integrate with the REST API.
  4. Patreon Community: Join a community of 5,000+ engineers and software architects. You will also unlock access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses.

The .NET Weekly

Become a Better .NET Software Engineer

Join 66,000+ engineers who are improving their skills every Saturday morning.