playbook/outfitter-agents/plugins/outfitter/skills/architecture/references/design-patterns.md

2.5 KiB

Design Patterns

Service Decomposition

Monolith first, then extract:

  1. Start with well-organized monolith
  2. Identify bounded contexts as you learn domain
  3. Extract when hitting specific pain:
    • Different scaling needs (one service needs 10x instances)
    • Different deployment cadences (ML model updates vs API)
    • Team boundaries (separate teams, separate services)
    • Technology constraints (need Rust for one component)

When to use microservices:

Signal Microservices
Large team (10+ engineers) Yes
Clear domain boundaries Yes
Independent scaling needs Yes
Polyglot requirements Yes
Small team (<5 engineers) No
Unclear domain No
Premature optimization No

Communication Patterns

Synchronous (REST, GraphQL, gRPC)

  • Use when: Immediate response needed, simple request-response
  • Tradeoffs: Tight coupling, cascading failures, latency compounds
  • Mitigation: Circuit breakers, timeouts, retries with backoff

Asynchronous (message queues, event streams)

  • Use when: Eventual consistency acceptable, high volume, decoupling needed
  • Tradeoffs: Complexity, harder debugging, ordering challenges
  • Patterns: Message queues (RabbitMQ, SQS), event streams (Kafka, Kinesis)

Event-driven architecture

Core: services publish events, others subscribe

Benefits: Loose coupling, easy to add consumers, audit trail

Challenges: Eventual consistency, event versioning, ordering

Best practices:

  • Schema registry for event contracts
  • Include correlation IDs for tracing
  • Design idempotent consumers
  • Plan for out-of-order delivery

Data Management

Database per service

  • Each service owns its data
  • No direct database access across services
  • Communication via APIs or events
  • Tradeoff: data consistency challenges, no joins across services

Shared database (anti-pattern for microservices)

  • Multiple services access same database
  • Only acceptable: transitioning from monolith
  • Migration path: add service layer, restrict direct access

CQRS (Command Query Responsibility Segregation)

  • Separate write model from read model
  • Use when: read/write patterns very different, complex queries needed
  • Implementation: write to normalized DB, project to read-optimized views

Event Sourcing

  • Store events, not current state
  • Rebuild state by replaying events
  • Use when: audit trail critical, temporal queries needed
  • Challenges: migration complexity, eventual consistency