Awesome-omni-skill architecture-patterns
Software architecture patterns and best practices
git clone https://github.com/diegosouzapw/awesome-omni-skill
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"
skills/development/architecture-patterns-miles990/SKILL.mdArchitecture 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:
| Component | Purpose | Tools |
|---|---|---|
| API Gateway | Entry point, routing | Kong, AWS API Gateway |
| Service Discovery | Service registration | Consul, Kubernetes DNS |
| Config Management | Centralized config | Spring Cloud Config, Consul |
| Circuit Breaker | Fault tolerance | Resilience4j, Hystrix |
Best Practices:
- Design around business capabilities
- Decentralize data management
- Design for failure
- Automate deployment
Event-Driven Architecture
Description: Systems communicating through events.
Key Patterns:
| Pattern | Description | Use Case |
|---|---|---|
| Event Sourcing | Store state as events | Audit trails, temporal queries |
| CQRS | Separate read/write models | High-read workloads |
| Saga | Distributed transactions | Cross-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:
| Aspect | Impact |
|---|---|
| Cold Start | 100ms-2s latency on first invocation |
| Timeout | Usually 15-30 min max execution |
| State | Must use external storage |
| Vendor Lock-in | Platform-specific features |
Best Practices:
- Keep functions small and focused
- Minimize dependencies
- Use connection pooling for databases
- 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:
| Pattern | Purpose |
|---|---|
| Bounded Context | Clear domain boundaries |
| Context Map | Relationships between contexts |
| Ubiquitous Language | Shared vocabulary |
Tactical Patterns:
| Pattern | Purpose |
|---|---|
| Entity | Objects with identity |
| Value Object | Immutable descriptors |
| Aggregate | Consistency boundary |
| Repository | Collection-like persistence |
| Domain Event | Something 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:
| Scenario | Recommendation |
|---|---|
| Single client type | Skip BFF |
| Web + Mobile with same needs | Single API Gateway |
| Different UX per platform | Separate BFFs |
| Multiple teams per frontend | Dedicated BFFs |
Architecture Patterns Comparison
| Pattern | Complexity | Scalability | Team Size | Best For |
|---|---|---|---|---|
| Monolith | Low | Vertical | Small (2-10) | MVPs, Simple apps |
| Modular Monolith | Medium | Vertical | Medium (5-20) | Growing apps |
| Microservices | High | Horizontal | Large (20+) | Complex domains |
| Serverless | Medium | Auto | Any | Event-driven, Variable load |
| Event-Driven | High | Horizontal | Medium-Large | Async 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
| Pattern | Latency | Throughput | Cold Start |
|---|---|---|---|
| Monolith | Low | High | N/A |
| Microservices | Medium (network) | High (distributed) | N/A |
| Serverless | Variable | Auto-scale | 100ms-2s |
| Event-Driven | Higher (async) | Very High | Depends |
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