Awesome-omni-skill architecture-patterns

Software architecture patterns and best practices

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/architecture-patterns-miles990" ~/.claude/skills/diegosouzapw-awesome-omni-skill-architecture-patterns-602869 && rm -rf "$T"
manifest: skills/development/architecture-patterns-miles990/SKILL.md
source content

Architecture Patterns

Overview

Architecture patterns provide proven solutions for structuring software systems. Choosing the right architecture is crucial for scalability, maintainability, and team productivity.

Patterns

Monolithic Architecture

Description: Single deployable unit containing all application functionality.

Key Features:

  • Simple deployment and development
  • Shared database and memory
  • Straightforward debugging

Use Cases:

  • MVPs and startups
  • Small teams (< 10 developers)
  • Simple domain logic

Best Practices:

src/
├── modules/          # Feature-based organization
│   ├── users/
│   ├── orders/
│   └── products/
├── shared/           # Cross-cutting concerns
└── infrastructure/   # External services

Microservices Architecture

Description: Distributed system of independently deployable services.

Key Features:

  • Independent deployment and scaling
  • Technology diversity per service
  • Fault isolation

Use Cases:

  • Large teams needing autonomy
  • Complex domains with clear boundaries
  • High scalability requirements

Key Components:

ComponentPurposeTools
API GatewayEntry point, routingKong, AWS API Gateway
Service DiscoveryService registrationConsul, Kubernetes DNS
Config ManagementCentralized configSpring Cloud Config, Consul
Circuit BreakerFault toleranceResilience4j, Hystrix

Best Practices:

  1. Design around business capabilities
  2. Decentralize data management
  3. Design for failure
  4. Automate deployment

Event-Driven Architecture

Description: Systems communicating through events.

Key Patterns:

PatternDescriptionUse Case
Event SourcingStore state as eventsAudit trails, temporal queries
CQRSSeparate read/write modelsHigh-read workloads
SagaDistributed transactionsCross-service workflows

Event Sourcing Example:

// Events are the source of truth
interface OrderEvent {
  id: string;
  type: 'OrderCreated' | 'ItemAdded' | 'OrderShipped';
  timestamp: Date;
  payload: unknown;
}

// Rebuild state from events
function rebuildOrder(events: OrderEvent[]): Order {
  return events.reduce((order, event) => {
    switch (event.type) {
      case 'OrderCreated': return { ...event.payload };
      case 'ItemAdded': return { ...order, items: [...order.items, event.payload] };
      case 'OrderShipped': return { ...order, status: 'shipped' };
    }
  }, {} as Order);
}

Serverless Architecture

Description: Cloud-managed execution without server management.

Key Features:

  • Pay-per-execution pricing
  • Auto-scaling to zero
  • Reduced operational overhead

Considerations:

AspectImpact
Cold Start100ms-2s latency on first invocation
TimeoutUsually 15-30 min max execution
StateMust use external storage
Vendor Lock-inPlatform-specific features

Best Practices:

  1. Keep functions small and focused
  2. Minimize dependencies
  3. Use connection pooling for databases
  4. Implement proper error handling

Clean Architecture

Description: Dependency-inverted architecture with domain at center.

Layer Structure:

┌──────────────────────────────────────┐
│           Frameworks & Drivers       │  ← External (DB, Web, UI)
├──────────────────────────────────────┤
│           Interface Adapters         │  ← Controllers, Gateways
├──────────────────────────────────────┤
│           Application Business       │  ← Use Cases
├──────────────────────────────────────┤
│           Enterprise Business        │  ← Entities, Domain Rules
└──────────────────────────────────────┘

Dependency Rule: Dependencies point inward. Inner layers know nothing about outer layers.


Domain-Driven Design (DDD)

Description: Architecture aligned with business domain.

Strategic Patterns:

PatternPurpose
Bounded ContextClear domain boundaries
Context MapRelationships between contexts
Ubiquitous LanguageShared vocabulary

Tactical Patterns:

PatternPurpose
EntityObjects with identity
Value ObjectImmutable descriptors
AggregateConsistency boundary
RepositoryCollection-like persistence
Domain EventSomething that happened

Decision Guide

START
  │
  ├─ Team size < 10? ──────────────────→ Monolith
  │
  ├─ Need independent deployments? ────→ Microservices
  │
  ├─ Audit trail required? ────────────→ Event Sourcing
  │
  ├─ Variable/unpredictable load? ─────→ Serverless
  │
  ├─ Complex business logic? ──────────→ Clean Architecture + DDD
  │
  └─ Default ──────────────────────────→ Modular Monolith

Common Pitfalls

1. Premature Microservices

Problem: Starting with microservices for a simple application Solution: Start monolithic, extract services when boundaries are clear

2. Distributed Monolith

Problem: Microservices that must deploy together Solution: Ensure services are truly independent with clear API contracts

3. Ignoring Data Boundaries

Problem: Shared database across services Solution: Each service owns its data, use events for synchronization


Hexagonal Architecture (Ports & Adapters)

Description: Application core isolated from external concerns through ports (interfaces) and adapters (implementations).

Structure:

┌─────────────────────────────────────────────────────────────┐
│                      Driving Adapters                       │
│    (REST API, CLI, GraphQL, Message Consumer)               │
└──────────────────────────┬──────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────┐
│                    Input Ports                              │
│              (Use Case Interfaces)                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                   APPLICATION CORE                          │
│              (Domain Logic, Entities)                       │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                   Output Ports                              │
│           (Repository, Gateway Interfaces)                  │
└──────────────────────────┬──────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────┐
│                     Driven Adapters                         │
│    (Database, External APIs, Message Publisher)             │
└─────────────────────────────────────────────────────────────┘

TypeScript Example:

// Port (Interface)
interface OrderRepository {
  save(order: Order): Promise<void>;
  findById(id: string): Promise<Order | null>;
}

// Adapter (Implementation)
class PostgresOrderRepository implements OrderRepository {
  constructor(private db: Database) {}

  async save(order: Order): Promise<void> {
    await this.db.query('INSERT INTO orders...', [order]);
  }

  async findById(id: string): Promise<Order | null> {
    const row = await this.db.query('SELECT * FROM orders WHERE id = $1', [id]);
    return row ? this.toDomain(row) : null;
  }
}

// Use Case (Application Core)
class CreateOrderUseCase {
  constructor(private orderRepo: OrderRepository) {} // Depends on Port, not Adapter

  async execute(input: CreateOrderInput): Promise<Order> {
    const order = new Order(input);
    await this.orderRepo.save(order);
    return order;
  }
}

Benefits:

  • Easy to swap implementations (DB, external services)
  • Highly testable (mock ports)
  • Framework-agnostic domain logic

Modular Monolith

Description: Monolith with strict module boundaries, preparing for potential microservices extraction.

Key Features:

  • Modules communicate via defined interfaces
  • Each module owns its data
  • Can be deployed as single unit or extracted

Structure:

src/
├── modules/
│   ├── users/
│   │   ├── api/           # Public API of module
│   │   │   └── UserService.ts
│   │   ├── internal/      # Private implementation
│   │   │   ├── UserRepository.ts
│   │   │   └── UserEntity.ts
│   │   └── index.ts       # Only exports public API
│   ├── orders/
│   │   ├── api/
│   │   │   └── OrderService.ts
│   │   ├── internal/
│   │   └── index.ts
│   └── shared/            # Cross-cutting utilities
├── infrastructure/
│   ├── database/
│   ├── messaging/
│   └── http/
└── main.ts

Module Communication Rules:

// ✅ Good: Use public API
import { UserService } from '@modules/users';
const user = await userService.getById(id);

// ❌ Bad: Direct access to internal
import { UserRepository } from '@modules/users/internal/UserRepository';

Enforcement:

// eslint rules or ts-paths to prevent internal imports
{
  "rules": {
    "no-restricted-imports": ["error", {
      "patterns": ["@modules/*/internal/*"]
    }]
  }
}

Strangler Fig Pattern

Description: Gradually replace legacy system by routing traffic to new implementation.

Migration Process:

Phase 1: Facade
┌─────────┐     ┌─────────┐     ┌─────────────┐
│ Client  │────→│ Facade  │────→│ Legacy      │
└─────────┘     └─────────┘     │ System      │
                                └─────────────┘

Phase 2: Partial Migration
┌─────────┐     ┌─────────┐     ┌─────────────┐
│ Client  │────→│ Facade  │──┬─→│ Legacy      │
└─────────┘     └─────────┘  │  └─────────────┘
                             │  ┌─────────────┐
                             └─→│ New System  │
                                └─────────────┘

Phase 3: Complete Migration
┌─────────┐     ┌─────────┐     ┌─────────────┐
│ Client  │────→│ Facade  │────→│ New System  │
└─────────┘     └─────────┘     └─────────────┘

Implementation:

class PaymentFacade {
  constructor(
    private legacyPayment: LegacyPaymentService,
    private newPayment: NewPaymentService,
    private featureFlags: FeatureFlags
  ) {}

  async processPayment(payment: Payment): Promise<Result> {
    // Gradually migrate traffic
    if (this.featureFlags.isEnabled('new-payment-system', payment.userId)) {
      return this.newPayment.process(payment);
    }
    return this.legacyPayment.process(payment);
  }
}

Backend for Frontend (BFF)

Description: Dedicated backend for each frontend type (web, mobile, etc.).

Structure:

                    ┌─────────────┐
                    │ Web Client  │
                    └──────┬──────┘
                           │
                    ┌──────▼──────┐
                    │  Web BFF    │
                    └──────┬──────┘
                           │
       ┌───────────────────┼───────────────────┐
       │                   │                   │
┌──────▼──────┐    ┌──────▼──────┐    ┌──────▼──────┐
│ User Service│    │Order Service│    │Product Svc  │
└─────────────┘    └─────────────┘    └─────────────┘
       │                   │                   │
       └───────────────────┼───────────────────┘
                           │
                    ┌──────▼──────┐
                    │ Mobile BFF  │
                    └──────┬──────┘
                           │
                    ┌──────▼──────┐
                    │Mobile Client│
                    └─────────────┘

Benefits:

  • Optimized payload for each client
  • Client-specific authentication
  • Independent deployment per frontend
  • Reduces over-fetching

When to Use:

ScenarioRecommendation
Single client typeSkip BFF
Web + Mobile with same needsSingle API Gateway
Different UX per platformSeparate BFFs
Multiple teams per frontendDedicated BFFs

Architecture Patterns Comparison

PatternComplexityScalabilityTeam SizeBest For
MonolithLowVerticalSmall (2-10)MVPs, Simple apps
Modular MonolithMediumVerticalMedium (5-20)Growing apps
MicroservicesHighHorizontalLarge (20+)Complex domains
ServerlessMediumAutoAnyEvent-driven, Variable load
Event-DrivenHighHorizontalMedium-LargeAsync workflows

Architecture Decision Record (ADR) Template

When choosing an architecture, document decisions:

# ADR-001: Choose Modular Monolith

## Status
Accepted

## Context
- Team of 8 developers
- MVP deadline in 3 months
- Uncertain about domain boundaries
- Limited DevOps resources

## Decision
Adopt Modular Monolith with strict boundaries

## Consequences
### Positive
- Faster initial development
- Simpler deployment
- Can extract services later

### Negative
- Single point of failure
- Scaling limited to vertical
- Need discipline for module boundaries

## Alternatives Considered
1. Microservices - Too complex for team size
2. Traditional Monolith - No path to scale

Evolution Path

┌─────────────────────────────────────────────────────────────────┐
│                    Architecture Evolution                        │
│                                                                 │
│   Monolith ──→ Modular Monolith ──→ Microservices              │
│      │              │                     │                     │
│      │              │                     ▼                     │
│      │              │            Event-Driven / CQRS            │
│      │              │                     │                     │
│      ▼              ▼                     ▼                     │
│  [Simple]     [Growing]            [Complex/Scale]              │
│                                                                 │
│   Tip: Don't skip steps. Each stage teaches domain boundaries. │
└─────────────────────────────────────────────────────────────────┘

Anti-Patterns to Avoid

1. Big Ball of Mud

Symptom: No clear structure, everything depends on everything Fix: Introduce module boundaries, apply Clean Architecture principles

2. Golden Hammer

Symptom: Using same architecture for every project Fix: Evaluate requirements, use decision guide

3. Accidental Complexity

Symptom: Architecture more complex than domain requires Fix: Start simple, add complexity only when needed

4. Resume-Driven Development

Symptom: Choosing tech for learning, not solving problems Fix: Align architecture with team skills and project needs

5. Vendor Lock-In

Symptom: Core logic tightly coupled to cloud provider Fix: Use Hexagonal Architecture, abstract vendor-specific code


Performance Considerations by Pattern

PatternLatencyThroughputCold Start
MonolithLowHighN/A
MicroservicesMedium (network)High (distributed)N/A
ServerlessVariableAuto-scale100ms-2s
Event-DrivenHigher (async)Very HighDepends

Testing Strategies by Pattern

Monolith

Unit Tests → Integration Tests → E2E Tests
    70%           20%              10%

Microservices

Unit Tests → Contract Tests → Integration → E2E
    60%           20%           15%         5%

// Contract Test Example (Pact)
const provider = new Pact({ consumer: 'OrderService', provider: 'UserService' });
await provider.addInteraction({
  state: 'user exists',
  uponReceiving: 'get user request',
  withRequest: { method: 'GET', path: '/users/123' },
  willRespondWith: { status: 200, body: { id: '123', name: 'John' } }
});

Event-Driven

  • Test event producers and consumers independently
  • Use event schema validation
  • Test saga/workflow orchestration

Related Skills

  • [[api-design]] - API design for service communication
  • [[system-design]] - Large-scale system considerations
  • [[devops-cicd]] - Deployment strategies for each pattern
  • [[data-design]] - Database patterns for each architecture