SwadhyAI Notes

All System Design notes

System Design · Modeling Boundaries

Domain-Driven Design Concepts

System design interviews fail when every box is named “User Service” and every noun means three things. Domain-Driven Design gives you vocabulary for , , and consistency units ()—so your architecture matches how the business actually talks.

This is not a book report on repositories and factories. It is DDD applied to service borders, events, , and you can draw on a whiteboard in 10 minutes.

Modeling journey

1

Same words in talk, code, and APIs.

2

One model per meaning of “Product.”

3

Consistency boundaries, not god objects.

4

ACL, events, conformist—on purpose.

Why interviewers care about DDD ideas

When “Product” means sellable SKU in marketing, shelvable unit in warehouse, and invoice line in finance, a single products table shared by three services will fight you forever. Separate with explicit integration beats one fuzzy model.

Focus modeling energy on the —the differentiator. Use simpler patterns or vendors for generic email delivery. Saying that out loud in an interview shows judgment, not laziness.

Ubiquitous language

is a discipline: rename code when product renames concepts. If support says “ticket,” do not keep IssueEntity and CaseDTO for the same thing unless they truly live in different contexts.

Practical technique: write acceptance scenarios with domain words, then make type and API names match. Ambiguous terms become discovery sessions: “Account” might split into BillingAccount and LoginIdentity.

plaintext
Smell → Fix
  "processOrder()" does inventory + charge + email
    → PlaceOrder (Orders), ReserveStock (Inventory), CapturePayment (Billing)

  Shared enum ProductStatus used by Catalog and Warehouse
    → Catalog: Draft|Published
      Inventory: InStock|Quarantine|Damaged
      Integrate via events, not one enum

Bounded contexts as system borders

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
flowchart LR
    subgraph Catalog["Catalog BC"]
        P1["Product (sellable)"]
    end
    subgraph Inventory["Inventory BC"]
        P2["SKU stock"]
    end
    subgraph Fulfill["Fulfillment BC"]
        P3["Shipable item"]
    end
    subgraph Billing["Billing BC"]
        Inv["Invoice line"]
    end
    Catalog -->|"Published language: ProductId, SKU"| Inventory
    Catalog -->|"events ProductPublished"| Fulfill
    Inventory -->|"ACL from WMS legacy"| WMS[Legacy WMS]
    Fulfill -->|"OrderReadyToShip events"| Billing
    Billing -->|"Conformist to tax vendor API"| Tax[Tax SaaS]

Drawing contexts before drawing Kubernetes services prevents accidental distributed monoliths. Two contexts can live in one deployable (modular monolith) if teams and scale allow; one “service” that mixes three languages is still three contexts fighting.

Integration styles between contexts belong on a : upstream Catalog publishes ProductId; Inventory may be a to Catalog identifiers; Fulfillment uses an toward a legacy WMS; many consumers share a for events.

Aggregates: where transactions stop

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
flowchart TD
    Cmd["Command: AddItemToOrder"] --> Root["Order aggregate root"]
    Root --> Inv["Check invariants: currency, max lines, state=OPEN"]
    Inv --> Lines["OrderLine entities / VOs"]
    Lines --> Commit["Single TX commit"]
    Commit --> Evt["Domain event: OrderItemAdded"]
    Evt --> Out["Outbox → other contexts"]
    Bad["Anti-pattern: TX across Order + Inventory + Customer"] --> Split["Separate aggregates + saga/events"]

The aggregate root (e.g. Order) is the only entry point for changes. External objects hold orderId, not pointers into line-item rows. Invariants—“all lines same currency,” “cannot add items when Cancelled”—are enforced in one place.

Size rule of thumb: if two pieces must be strongly consistent for a user-visible rule, they may share an aggregate. If they only need eventual agreement (stock counts vs marketing copy), separate them. Huge aggregates serialize all writes and become hotspots.

have identity; like Money do not. Prefer value objects for money and quantities so rules travel with the data. A holds logic that does not fit a single root without turning the root into a god object.

plaintext
Worked invariant — seat hold aggregate
  Hold aggregate root: HoldId
  Contains: SeatRefs (VOs), ExpiresAt, Status
  Invariant: seats unique; expire → Released in same TX as status flip
  Not in aggregate: Payment (separate); User profile (separate)
  Across boundary: HoldConfirmed event → Orders creates Order aggregate

Why not one TX with Payment?
  Payment provider latency + PCI boundary
  Saga: confirm hold → capture payment → compensate hold on failure

Domain events as integration

A is something that already happened in ubiquitous language: OrderPlaced, SeatHoldExpired. Other contexts subscribe and update their own models. That is how you avoid shared databases while keeping the system coherent.

Implementation companions for system design: transactional outbox (event commit with aggregate), idempotent consumers, versioned schemas, and clear causality (correlation / causation ids). Event-driven architecture notes go deeper on brokers; here the point is modeling—events are facts from a context, not a dump of row changes.

Prefer business events over raw CDC for inter-context contracts. CDC is a great feed into an ACL or analytics; it is a weak published language for product workflows.

Anti-corruption layers

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
sequenceDiagram
    autonumber
    participant Core as Your Fulfillment
    participant ACL as Anti-corruption layer
    participant Leg as Legacy WMS

    Core->>ACL: ReserveStock(sku, qty) in domain language
    ACL->>ACL: map SKU → LEGACY_ITEM_CODE
    ACL->>Leg: XML allocateItem
    Leg-->>ACL: opaque status codes
    ACL->>ACL: map to StockReserved / StockDenied
    ACL-->>Core: domain result
    Note over Core: Core never imports WMS XML types

The owns mapping tables, protocol quirks, and weird status codes. Your core domain imports only your types. Cost: another component to test. Benefit: you can replace the legacy system without rewriting the core.

Choose ACL over when upstream is unstable, poorly named, or spans concepts you must split. Choose conformist when upstream is a clean published language you are happy to adopt (e.g. a central Identity context’s user id).

Context maps on the whiteboard

A is the DDD artifact that maps cleanly to system design: boxes are contexts; arrows are relationship types, not just “HTTP.” Label upstream/downstream so everyone knows who adapts to whom.

RelationshipMeaningSD implication
Published languageShared integration dialectVersioned events / API schemas
ACLTranslate foreign modelAdapter service / module
ConformistAdopt upstream modelFewer translations; coupled shape
Customer–SupplierDownstream needs negotiatedContract tests, SLAs
Shared kernelSmall shared model both editHigh coordination cost—use sparingly
Separate waysNo integrationHonest: duplicate rather than couple

Interview pattern: from story to boundaries

plaintext
Prompt: Design a ticket marketplace

1. Language pass
   Hold, Listing, Order, Payout, Transfer — define each

2. Context candidates
   Listings (sellers), Checkout (buyers), Payments,
   Delivery/Transfer of tickets, Risk/Fraud, Search

3. Aggregates
   Listing: tickets available count + price rules
   Hold: seats + expiry (short TTL consistency)
   Order: payment intent reference + line items
   Do NOT one-TX Listing+Payment+Email

4. Context map
   Listings upstream of Search (events)
   Checkout customer of Payments (ACL if using Stripe shapes)
   Risk conformist to device fingerprint vendor? or ACL?

5. Say out loud
   "Search can lag; Holds cannot."
   That single sentence is DDD applied to SLOs.

What to skip in an SD interview

You rarely need to lecture on factories, repositories, or layered architecture diagrams from the blue book. Interviewers want: clear contexts, sensible aggregates, event vs sync edges, and how you protect the core from legacy. Mention repositories only if asked about persistence boundaries.

Likewise, “we use DDD” is empty. Show a context map and one invariant. That is the signal.

Trade-offs to weigh

Small aggregates vs fewer distributed workflows

Tiny aggregates maximize parallelism and clear invariants but force more sagas and eventual consistency. Larger aggregates simplify transactions and create write hotspots. Size from real invariants, not from dogma.

ACL purity vs shipping speed

A perfect ACL slows the first integration. Leaking vendor types speeds day one and taxes every future change. For core domains, pay for the ACL; for throwaway prototypes, conforming may be fine if you schedule a rewrite.

Shared kernel vs duplication

A tiny shared kernel (Money, common IDs) reduces drift. A fat shared kernel becomes a distributed monolith of libraries. Prefer published language at the boundary over sharing domain modules across contexts.

One enterprise model vs many contexts

A single canonical model sounds efficient and collapses under conflicting meanings. Multiple contexts cost translation and buy clarity. Enterprises that insist on one Product table usually invent boolean flags until nobody understands them.

Domain events vs sync APIs

Sync APIs give immediate answers and temporal coupling. Events give decoupling and lag. Critical user journeys often sync within a context and emit events outward—hybrid by default.

Choosing and explaining domain-driven design concepts

A complete explanation connects the mechanism to a requirement. Describe what improves when you introduce it, which new failure cases become possible, and how you would know that the trade-off is worthwhile.

Explain the core idea

Use ubiquitous language and bounded contexts to place system borders where meanings and invariants naturally split.

Aggregates define consistency units; cross-aggregate work uses domain events and explicit workflows.

Context maps name integration styles—ACL, conformist, published language—so legacy and vendors do not corrupt the core model.

When it belongs

Designing multi-service systems where the same word means different things across teams.

Explaining why two domains cannot share one writable schema.

Integrating with legacy ERPs or third-party APIs without leaking their models inward.

How it fails

God aggregates that serialize all writes and hide unrelated invariants.

Shared “enterprise” tables that merge Catalog, Inventory, and Billing meanings.

Event storms of CDC row dumps mistaken for a published language.

Trade-offs to weigh

Aggregate size versus saga complexity.

ACL isolation versus integration speed.

Model clarity in many contexts versus translation overhead.

Common misunderstandings

Reciting repository/factory patterns instead of drawing boundaries.

Assuming one microservice equals one bounded context automatically (or vice versa).

Using DDD jargon without stating a single invariant or context relationship.

Wrap-up

Exercises

  1. Split “Product.” List attributes for Catalog vs Inventory vs Billing. Which become separate models, and what ids cross the boundaries?
  2. Size an aggregate. Shopping cart with coupons and inventory checks: what is inside the Cart aggregate, and what becomes events/sagas?
  3. Draw a context map. Your system plus Stripe plus a legacy warehouse—label ACL, conformist, and published language edges.

Topic-specific references

Key takeaway

DDD for system design is language, boundaries, consistency units, and deliberate translation. Draw the context map, name the invariants, and keep legacy models behind an ACL—leave the blue-book taxonomy for when someone asks.