agentsclimarketplace

Ddd patterns

Skill kinhluan/skills/.agent-skills/ddd-patterns

πŸš€ Professional Multi-Agent Skills

Install
npx -y skills add kinhluan/skills --skill ddd-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Advanced Domain-Driven Design (DDD) Integration Patterns. Use this skill for implementing CQRS, Event Sourcing, the Outbox Pattern, and Anti-Corruption Layers (ACL) in distributed systems.

SKILL.md

23.6 KB, as published. Nobody here has run it

Domain-Driven Design (DDD): Advanced Patterns

Advanced integration patterns for handling complex distributed system interactions, data consistency, and cross-boundary communication. Use these patterns when a single Bounded Context's tactical patterns are insufficient.

"Distributed systems are different. If we don't know that, we are building them wrong." β€” Udi Dahan


🧭 When to Use Advanced Patterns

SituationPattern(s)Complexity
Read model needs different shape than write modelCQRSMedium
Need full audit trail of all state changesEvent SourcingHigh
Need atomic DB + message broker consistencyOutboxMedium
Long-running business process across contextsSagaHigh
External system has incompatible modelACLMedium
High read load, simple queriesCQRS + Read ModelMedium

Warning: These patterns add significant complexity. Don't use them unless you have a clear problem they solve.


1️⃣ CQRS (Command Query Responsibility Segregation)

Definition: Separate the read (Query) model from the write (Command) model. Each is optimized for its purpose.

Why CQRS?

AspectTraditional CRUDCQRS
Read modelSame as write modelOptimized for queries (denormalized)
Write modelSame as read modelOptimized for business rules (normalized)
SchemaSingle schemaSeparate schemas per model
ScalingScale togetherScale independently

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Client    │────▢│   Command   │────▢│  Command    β”‚
β”‚             β”‚     β”‚   API       β”‚     β”‚  Handler    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                                               β”‚
                                               β–Ό
                                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                        β”‚  Write DB   β”‚
                                        β”‚ (Normalized)β”‚
                                        β”‚ PostgreSQL  β”‚
                                        β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                                               β”‚
                                               β”‚ Events
                                               β–Ό
                                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                        β”‚  Event Bus  β”‚
                                        β”‚   (Kafka)   β”‚
                                        β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                                               β”‚
                                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Client    │◀────│   Query     │◀────│  Projector  β”‚
β”‚             β”‚     β”‚   API       β”‚     β”‚  (Consumer) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                                               β”‚
                                               β–Ό
                                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                        β”‚  Read DB    β”‚
                                        β”‚(Denormalized)β”‚
                                        β”‚Elasticsearchβ”‚
                                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Implementation Example (Go)

// ============ COMMAND SIDE ============

// Command
type PlaceOrderCommand struct {
    CustomerID uuid.UUID
    Items      []OrderItemDTO
    CouponCode string
}

// Command Handler
type PlaceOrderHandler struct {
    orderRepo   order.Repository
    pricingSvc  *PricingService
    eventBus    EventBus
}

func (h *PlaceOrderHandler) Handle(ctx context.Context, cmd PlaceOrderCommand) error {
    // 1. Load/build aggregate
    items := convertItems(cmd.Items)
    total := h.pricingSvc.CalculateTotal(items, cmd.CouponCode)

    order, err := order.New(cmd.CustomerID, items, total)
    if err != nil {
        return err
    }

    // 2. Save aggregate (generates events)
    if err := h.orderRepo.Save(ctx, order); err != nil {
        return err
    }

    // 3. Events published by repository
    return nil
}

// ============ QUERY SIDE ============

// Read Model (denormalized for specific query)
type OrderSummary struct {
    OrderID      uuid.UUID
    CustomerName string
    Total        string
    Status       string
    ItemCount    int
    CreatedAt    time.Time
}

// Query
type GetCustomerOrdersQuery struct {
    CustomerID uuid.UUID
    Page       int
    PageSize   int
}

// Query Handler
type OrderQueryHandler struct {
    readDB *sql.DB // separate read-optimized DB
}

func (h *OrderQueryHandler) Handle(
    ctx context.Context,
    q GetCustomerOrdersQuery,
) ([]OrderSummary, error) {
    // Optimized query β€” no joins needed
    rows, err := h.readDB.QueryContext(ctx, `
        SELECT order_id, customer_name, total, status, item_count, created_at
        FROM order_summaries
        WHERE customer_id = $1
        ORDER BY created_at DESC
        LIMIT $2 OFFSET $3
    `, q.CustomerID, q.PageSize, (q.Page-1)*q.PageSize)
    // ... scan rows
}

// ============ PROJECTOR (syncs read model) ============

type OrderProjector struct {
    readDB *sql.DB
}

func (p *OrderProjector) OnOrderPlaced(event OrderPlacedEvent) error {
    _, err := p.readDB.Exec(`
        INSERT INTO order_summaries (order_id, customer_id, total, status, item_count, created_at)
        VALUES ($1, $2, $3, 'PENDING', $4, $5)
    `, event.OrderID, event.CustomerID, event.Total.String(),
        len(event.Items), event.CreatedAt)
    return err
}

func (p *OrderProjector) OnOrderPaid(event OrderPaidEvent) error {
    _, err := p.readDB.Exec(`
        UPDATE order_summaries SET status = 'PAID' WHERE order_id = $1
    `, event.OrderID)
    return err
}

CQRS Anti-Patterns

Anti-PatternSymptomFix
Premature CQRSSimple CRUD app with CQRSRemove CQRS. Use simple CRUD
Shared DatabaseCommand and Query use same DBSeparate schemas or physical DBs
Eventual Consistency ConfusionUsers expect immediate read-after-writeShow "processing" state, or use synchronous projection
Over-ProjectionProjecting every field to read modelOnly project fields needed for queries

2️⃣ Event Sourcing

Definition: Store the state of an application as a sequence of events. The current state is derived by replaying events.

Why Event Sourcing?

BenefitDescription
Full Audit TrailEvery change is recorded with timestamp and reason
Temporal Queries"What was the state at time T?"
Event ReplayRebuild read models, debug issues by replaying
Natural CQRSEvents feed read model projections

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Command   │────▢│  Aggregate  │────▢│  Event      β”‚
β”‚   Handler   β”‚     β”‚  (applies   β”‚     β”‚  Store      β”‚
β”‚             β”‚     β”‚   events)   β”‚     β”‚ (Append-only)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β”‚ generates
                           β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Domain     β”‚
                    β”‚  Events     β”‚
                    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β–Ό            β–Ό            β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ Read    β”‚  β”‚ Read    β”‚  β”‚ Read    β”‚
        β”‚ Model 1 β”‚  β”‚ Model 2 β”‚  β”‚ Model 3 β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Implementation Example (Go)

// Event Store interface
type EventStore interface {
    Append(ctx context.Context, streamID string, events []DomainEvent, expectedVersion int) error
    Read(ctx context.Context, streamID string) ([]StoredEvent, error)
    ReadAll(ctx context.Context, afterPosition int64) ([]StoredEvent, error)
}

// Stored event in DB
type StoredEvent struct {
    StreamID    string
    StreamType  string
    Position    int64
    EventType   string
    EventData   json.RawMessage
    OccurredAt  time.Time
}

// Aggregate with event sourcing
type Order struct {
    ID      uuid.UUID
    Version int // optimistic concurrency
    // state derived from events
    CustomerID uuid.UUID
    Items      []OrderItem
    Status     OrderStatus
    Total      Money
    // uncommitted events
    pendingEvents []DomainEvent
}

// Apply event to mutate state
func (o *Order) apply(event DomainEvent) {
    switch e := event.(type) {
    case OrderCreatedEvent:
        o.ID = e.OrderID
        o.CustomerID = e.CustomerID
        o.Status = OrderStatusPending
        o.Total = e.Total
    case OrderItemAddedEvent:
        o.Items = append(o.Items, OrderItem{
            ProductID: e.ProductID,
            Quantity:  e.Quantity,
            UnitPrice: e.UnitPrice,
        })
    case OrderPaidEvent:
        o.Status = OrderStatusPaid
    }
    o.Version++
}

// Reconstruct from events
func OrderFromEvents(events []DomainEvent) *Order {
    order := &Order{}
    for _, event := range events {
        order.apply(event)
    }
    return order
}

// Command creates event
func (o *Order) Pay(payment Money) error {
    if o.Status != OrderStatusPending {
        return errors.New("order not pending")
    }
    if !o.Total.Equals(payment) {
        return errors.New("payment mismatch")
    }
    o.pendingEvents = append(o.pendingEvents, OrderPaidEvent{
        OrderID: o.ID,
        Amount:  payment,
        PaidAt:  time.Now(),
    })
    o.apply(o.pendingEvents[len(o.pendingEvents)-1])
    return nil
}

// Repository loads and saves
func (r *EventSourcedOrderRepository) FindByID(
    ctx context.Context,
    id uuid.UUID,
) (*Order, error) {
    events, err := r.eventStore.Read(ctx, "order-"+id.String())
    if err != nil {
        return nil, err
    }
    domainEvents := make([]DomainEvent, len(events))
    for i, e := range events {
        domainEvents[i] = deserialize(e.EventType, e.EventData)
    }
    return OrderFromEvents(domainEvents), nil
}

func (r *EventSourcedOrderRepository) Save(
    ctx context.Context,
    order *Order,
) error {
    if len(order.pendingEvents) == 0 {
        return nil
    }
    err := r.eventStore.Append(
        ctx,
        "order-"+order.ID.String(),
        order.pendingEvents,
        order.Version-len(order.pendingEvents), // expected version
    )
    if err != nil {
        return err // optimistic concurrency conflict
    }
    order.pendingEvents = nil
    return nil
}

Event Sourcing Anti-Patterns

Anti-PatternSymptomFix
Giant EventOne event captures entire aggregate stateSplit into granular business facts
No Schema VersioningCan't evolve event schemaAdd version field, support upcasting
Missing SnapshotReplay 10,000 events to load aggregateSnapshot every N events
Event as APIExternal systems consume raw eventsUse published language / integration events

3️⃣ Outbox Pattern

Definition: Ensure atomic consistency between database state and message publication by writing messages to an "outbox" table in the same transaction.

The Problem

Without Outbox:
  1. Save order to DB βœ…
  2. Publish OrderPlaced event ❌ (network failure)
  β†’ DB has order, but no event. Inconsistent.

With Outbox:
  1. Save order to DB βœ…
  2. Save event to outbox table (same transaction) βœ…
  3. Background process reads outbox and publishes βœ…
  β†’ Atomic. Either both succeed or both fail.

Implementation (Go + PostgreSQL)

// Outbox table schema:
// CREATE TABLE outbox (
//     id SERIAL PRIMARY KEY,
//     aggregate_type VARCHAR(255) NOT NULL,
//     aggregate_id VARCHAR(255) NOT NULL,
//     event_type VARCHAR(255) NOT NULL,
//     payload JSONB NOT NULL,
//     created_at TIMESTAMP NOT NULL DEFAULT NOW(),
//     published_at TIMESTAMP,
//     UNIQUE(aggregate_type, aggregate_id, event_type, payload)
// );

// Repository saves aggregate + outbox in one transaction
func (r *OrderRepository) Save(ctx context.Context, order *Order) error {
    tx, err := r.db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    defer tx.Rollback()

    // 1. Save aggregate state
    if err := r.saveOrder(tx, order); err != nil {
        return err
    }

    // 2. Save events to outbox (same transaction!)
    events := order.PullEvents()
    for _, event := range events {
        payload, _ := json.Marshal(event)
        _, err := tx.ExecContext(ctx, `
            INSERT INTO outbox (aggregate_type, aggregate_id, event_type, payload)
            VALUES ($1, $2, $3, $4)
            ON CONFLICT DO NOTHING
        `, "order", order.ID.String(), event.EventName(), payload)
        if err != nil {
            return err
        }
    }

    return tx.Commit()
}

// Background relay process
type OutboxRelay struct {
    db       *sql.DB
    eventBus EventBus
    pollInterval time.Duration
}

func (r *OutboxRelay) Start(ctx context.Context) {
    ticker := time.NewTicker(r.pollInterval)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            r.processOutbox(ctx)
        }
    }
}

func (r *OutboxRelay) processOutbox(ctx context.Context) error {
    rows, err := r.db.QueryContext(ctx, `
        SELECT id, aggregate_type, aggregate_id, event_type, payload
        FROM outbox
        WHERE published_at IS NULL
        ORDER BY id
        LIMIT 100
        FOR UPDATE SKIP LOCKED
    `)
    if err != nil {
        return err
    }
    defer rows.Close()

    for rows.Next() {
        var id int
        var aggType, aggID, eventType string
        var payload []byte
        rows.Scan(&id, &aggType, &aggID, &eventType, &payload)

        // Publish to event bus
        event := deserialize(eventType, payload)
        if err := r.eventBus.Publish(ctx, event); err != nil {
            log.Printf("failed to publish outbox event %d: %v", id, err)
            continue // will retry on next poll
        }

        // Mark as published
        _, err = r.db.ExecContext(ctx, `
            UPDATE outbox SET published_at = NOW() WHERE id = $1
        `, id)
        if err != nil {
            log.Printf("failed to mark outbox event %d as published: %v", id, err)
        }
    }
    return rows.Err()
}

4️⃣ Saga Pattern

Definition: A sequence of local transactions where each transaction updates data and publishes an event/message to trigger the next transaction. If a step fails, compensating transactions undo previous steps.

Types

TypeCoordinationWhen to Use
OrchestrationCentral coordinator sagaComplex flows, need visibility
ChoreographyEvent-driven, no central coordinatorSimple flows, loose coupling

Orchestration Saga Example: Order Processing

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Saga       β”‚
β”‚ Coordinator β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚ 1. ReserveInventory
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     2. InventoryReserved
β”‚  Inventory  │────────────────▢
β”‚  Service    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β–²
       β”‚ 3. ProcessPayment
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     4. PaymentConfirmed
β”‚  Payment    │────────────────▢
β”‚  Service    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β–²
       β”‚ 5. CreateShipment
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     6. ShipmentCreated
β”‚  Shipping   │────────────────▢
β”‚  Service    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚ (if any step fails)
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Compensate: β”‚
β”‚ - ReleaseInventory
β”‚ - RefundPayment
β”‚ - CancelShipment
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Go Implementation (Orchestration)

type OrderSaga struct {
    inventorySvc InventoryClient
    paymentSvc   PaymentClient
    shippingSvc  ShippingClient
    sagaRepo     SagaRepository
}

func (s *OrderSaga) Start(ctx context.Context, order Order) error {
    saga := SagaState{
        ID:      uuid.New(),
        OrderID: order.ID,
        Status:  SagaStatusStarted,
        Steps: []SagaStep{
            {Name: "reserve_inventory", Status: StepStatusPending},
            {Name: "process_payment", Status: StepStatusPending},
            {Name: "create_shipment", Status: StepStatusPending},
        },
    }

    if err := s.sagaRepo.Save(ctx, saga); err != nil {
        return err
    }

    // Step 1: Reserve Inventory
    reservation, err := s.inventorySvc.Reserve(ctx, order.Items)
    if err != nil {
        return s.compensate(ctx, saga) // nothing to undo yet
    }
    saga.Steps[0].Status = StepStatusCompleted
    saga.Steps[0].Result = reservation.ID
    s.sagaRepo.Save(ctx, saga)

    // Step 2: Process Payment
    payment, err := s.paymentSvc.Charge(ctx, order.Total, order.CustomerID)
    if err != nil {
        return s.compensate(ctx, saga) // undo inventory reservation
    }
    saga.Steps[1].Status = StepStatusCompleted
    saga.Steps[1].Result = payment.ID
    s.sagaRepo.Save(ctx, saga)

    // Step 3: Create Shipment
    shipment, err := s.shippingSvc.Create(ctx, order.ID, order.ShippingAddress)
    if err != nil {
        return s.compensate(ctx, saga) // undo payment + inventory
    }
    saga.Steps[2].Status = StepStatusCompleted
    saga.Steps[2].Result = shipment.ID
    saga.Status = SagaStatusCompleted
    s.sagaRepo.Save(ctx, saga)

    return nil
}

func (s *OrderSaga) compensate(ctx context.Context, saga SagaState) error {
    saga.Status = SagaStatusCompensating
    s.sagaRepo.Save(ctx, saga)

    // Compensate in reverse order
    for i := len(saga.Steps) - 1; i >= 0; i-- {
        step := saga.Steps[i]
        if step.Status != StepStatusCompleted {
            continue
        }

        switch step.Name {
        case "create_shipment":
            s.shippingSvc.Cancel(ctx, step.Result)
        case "process_payment":
            s.paymentSvc.Refund(ctx, step.Result)
        case "reserve_inventory":
            s.inventorySvc.Release(ctx, step.Result)
        }

        step.Status = StepStatusCompensated
        s.sagaRepo.Save(ctx, saga)
    }

    saga.Status = SagaStatusCompensated
    return s.sagaRepo.Save(ctx, saga)
}

5️⃣ Anti-Corruption Layer (ACL)

Definition: A translation layer that isolates your domain model from external systems' incompatible models.

Why ACL?

Without ACL:
  Your Domain ──direct uses──▢ External Model (leaks into your domain)

With ACL:
  Your Domain ──uses──▢ ACL ──translates──▢ External Model

Implementation (Go)

// Your domain model (clean, no external dependencies)
package domain

type Order struct {
    ID     uuid.UUID
    Total  Money
    Status OrderStatus
}

// External API model (messy, you don't control it)
package external

type StripeCharge struct {
    ID            string  `json:"id"`
    Amount        int     `json:"amount"` // in cents!
    Currency      string  `json:"currency"`
    Status        string  `json:"status"`
    ReceiptURL    string  `json:"receipt_url"`
    FailureCode   string  `json:"failure_code"`
    FailureMessage string `json:"failure_message"`
}

// ACL: translates between your domain and external model
package acl

type StripePaymentAdapter struct {
    client *stripe.Client
}

func (a *StripePaymentAdapter) Charge(
    ctx context.Context,
    amount domain.Money,
    customerID uuid.UUID,
) (domain.PaymentResult, error) {
    // Translate domain β†’ external
    stripeAmount := amount.Amount.Mul(decimal.NewFromInt(100)).IntPart() // cents

    charge, err := a.client.CreateCharge(ctx, external.StripeChargeRequest{
        Amount:   stripeAmount,
        Currency: strings.ToLower(amount.Currency),
        Customer: customerID.String(),
    })
    if err != nil {
        return domain.PaymentResult{}, fmt.Errorf("stripe charge failed: %w", err)
    }

    // Translate external β†’ domain
    return a.toDomainResult(charge), nil
}

func (a *StripePaymentAdapter) toDomainResult(
    charge external.StripeCharge,
) domain.PaymentResult {
    status := domain.PaymentStatusPending
    switch charge.Status {
    case "succeeded":
        status = domain.PaymentStatusSucceeded
    case "failed":
        status = domain.PaymentStatusFailed
    }

    amount := decimal.NewFromInt(int64(charge.Amount)).Div(decimal.NewFromInt(100))

    return domain.PaymentResult{
        TransactionID: charge.ID,
        Amount:        domain.MustNewMoney(amount, strings.ToUpper(charge.Currency)),
        Status:        status,
        ReceiptURL:    charge.ReceiptURL,
        FailureReason: charge.FailureMessage,
    }
}

πŸ“‹ Pattern Selection Flowchart

Do you need to separate read/write models?
β”œβ”€β”€ YES β†’ Do you need full audit trail?
β”‚         β”œβ”€β”€ YES β†’ Event Sourcing + CQRS
β”‚         └── NO  β†’ CQRS only
β”‚
β”œβ”€β”€ NO β†’ Do you need cross-service consistency?
β”‚        β”œβ”€β”€ YES β†’ Do you need to undo on failure?
β”‚        β”‚        β”œβ”€β”€ YES β†’ Saga Pattern
β”‚        β”‚        └── NO  β†’ Outbox Pattern
β”‚        β”‚
β”‚        └── NO β†’ Is there an external system with different model?
β”‚                 β”œβ”€β”€ YES β†’ Anti-Corruption Layer
β”‚                 └── NO  β†’ Standard DDD Tactical patterns

πŸ“š References

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.