Skip to main content
DDD Without the Book Club

Strategic vs Tactical, Plainly

Ravinder··5 min read
DDDDomain-Driven DesignArchitectureStrategyBounded Context
Share:
Strategic vs Tactical, Plainly

Most teams that try DDD drown in vocabulary before they ship anything useful. They spend three sprints debating whether a Customer is an aggregate root or an entity while the business still can't tell the difference between their billing domain and their shipping domain. That's backwards.

DDD gives you two toolboxes. Strategic tools answer the question "what are we building and how does it fit together?" Tactical tools answer "how do we express the domain logic cleanly in code?" If you reach for tactical patterns before you've done the strategic work, you'll build a beautifully structured mess.

The Two Toolboxes

Strategic DDD operates at the team and system boundary level. The artefacts here are bounded contexts, context maps, and the ubiquitous language within each context. You use these when scoping a new service, onboarding a new team, or untangling a monolith.

Tactical DDD operates inside a single bounded context. Aggregates, entities, value objects, domain events, repositories, application services — these live here. You use these when writing the actual code that enforces business rules.

Strategic  →  What belongs where?
Tactical   →  How do we model it correctly?

Neither is more advanced. They solve different problems. The mistake is skipping strategic work and jumping to tactical patterns because they feel more concrete.

When to Do Which

The heuristic that works in practice:

  • Before writing a line of code in a new area: spend time on strategic work. Identify the bounded context, agree on the ubiquitous language with a domain expert, draw a rough context map.
  • When writing the code: apply tactical patterns. Start with the aggregate that owns the core invariant. Let entities and value objects fall out of that.
  • When integrating with another team or system: return to strategic tools. Draw the relationship (conformist, anti-corruption layer, partnership) before you write the adapter.
flowchart TD A[New Feature Request] --> B{Crosses team or system boundary?} B -- Yes --> C[Strategic Work First] B -- No --> D[Already inside a bounded context?] C --> E[Define/confirm bounded context] E --> F[Agree ubiquitous language] F --> G[Map context relationship] G --> D D -- Yes --> H[Tactical: find the aggregate] D -- No --> E H --> I[Entities & Value Objects] I --> J[Domain Events] J --> K[Ship it]

A Concrete Starting Point

Imagine your company sells software subscriptions. You have one big Rails app. A product manager asks for "subscription pausing."

Wrong approach: jump to PauseSubscription command, Subscription aggregate, SubscriptionPaused event.

Right approach first: ask where "subscription pausing" lives. Is it in the billing context (pause means stop charging)? The entitlement context (pause means stop access)? Both? The answer changes which team owns the feature and what the event even means.

// WRONG: tactical code before strategic clarity
class Subscription {
  pause(): void {
    this.status = 'paused';
    // But paused for billing? For access? Both?
    this.domainEvents.push(new SubscriptionPaused(this.id));
  }
}
 
// RIGHT: strategic clarity first reveals two separate operations
// In BillingContext:
class BillingSubscription {
  suspendBilling(reason: PauseReason): void {
    this.billingStatus = BillingStatus.Suspended;
    this.domainEvents.push(new BillingSuspended(this.id, reason));
  }
}
 
// In EntitlementContext:
class EntitlementGrant {
  revoke(until: Date): void {
    this.revokedUntil = until;
    this.domainEvents.push(new GrantRevoked(this.id, until));
  }
}

Same business request, two different domain models because they live in two different bounded contexts with different invariants and different owners.

The Weekly Rhythm

A practical cadence for a team actively applying DDD:

Sprint planning week: do strategic work. Review or update the context map. Confirm ubiquitous language for the upcoming stories with a domain expert. Decide which bounded context owns the new feature.

During the sprint: do tactical work. Write aggregates, domain events, application services. Keep the ubiquitous language in the code (method names, class names, variable names should match what the domain expert says).

After a significant delivery: revisit the context map. Has anything shifted? Did you discover a hidden sub-domain?

This isn't waterfall. You'll loop. But doing strategic work before tactical work saves you from building the wrong thing with the right patterns.

Why Teams Skip Strategic Work

The honest answer is that strategic work produces diagrams and conversations, not pull requests. It's hard to put on a sprint board. It feels like meetings.

The fix is to keep strategic artefacts small and close to the code. A CONTEXT.md in each service repo, a lightweight context map in a shared wiki that gets updated in retros, agreed language in a glossary linked from the README. These don't have to be formal documents — they have to be alive.

// A living ubiquitous language lives in the code itself
// Terms like "suspend" vs "cancel" vs "close" are NEVER interchangeable
 
type AccountStatus = 
  | 'active'      // paying, full access
  | 'suspended'   // not paying, access blocked, recoverable
  | 'closed';     // permanent, non-recoverable
 
// If a domain expert says "deactivated", that goes in a glossary note:
// "deactivated" → maps to "suspended" in billing, "revoked" in entitlements

Key Takeaways

  • Strategic DDD defines what belongs where; tactical DDD defines how to model it — do them in that order.
  • Starting with tactical patterns before strategic clarity produces beautifully structured code in the wrong place.
  • The signal to do strategic work is a boundary crossing: new team, new service, new integration, new subdomain.
  • Ubiquitous language is the output of strategic work and the input to tactical work — every class name and method name should reflect it.
  • Keep strategic artefacts small and colocated with the code; living documents beat formal diagrams that rot in Confluence.
  • Return to strategic tools whenever integrations change — team topology changes are context map changes.
Share: