skills/lee-to/ai-factory/ai-factory.architecture

ai-factory.architecture

SKILL.md

Architecture Patterns Guide

Practical guidelines for software architecture decisions.

Quick Reference

  • /ai-factory.architecture — Overview and decision guide
  • /ai-factory.architecture clean — Clean Architecture
  • /ai-factory.architecture ddd — Domain-Driven Design
  • /ai-factory.architecture microservices — Microservices patterns
  • /ai-factory.architecture monolith — Modular monolith
  • /ai-factory.architecture layers — Layered architecture

Choosing an Architecture

Decision Matrix

Factor Monolith Modular Monolith Microservices
Team size 1-10 5-30 20+
Domain complexity Low-Medium Medium-High High
Scale requirements Moderate Moderate-High Very High
Deploy independence Partial
Initial velocity ✅ Fast ✅ Fast ❌ Slow
Operational complexity ✅ Low ✅ Low ❌ High

Start Here

New project? → Start with Modular Monolith
Growing team + clear domain boundaries? → Extract to Microservices
Single team + unclear boundaries? → Stay Monolith, refine modules

Clean Architecture

Core Principle

Dependencies point inward. Inner layers know nothing about outer layers.

┌─────────────────────────────────────────────────────────┐
│                    Frameworks & Drivers                  │
│  ┌─────────────────────────────────────────────────┐    │
│  │              Interface Adapters                  │    │
│  │  ┌─────────────────────────────────────────┐    │    │
│  │  │           Application Layer              │    │    │
│  │  │  ┌─────────────────────────────────┐    │    │    │
│  │  │  │         Domain Layer            │    │    │    │
│  │  │  │    (Entities & Business Rules)  │    │    │    │
│  │  │  └─────────────────────────────────┘    │    │    │
│  │  └─────────────────────────────────────────┘    │    │
│  └─────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────┘

Folder Structure

src/
├── domain/                 # Core business logic (no dependencies)
│   ├── entities/
│   │   └── User.ts
│   ├── value-objects/
│   │   └── Email.ts
│   └── repositories/       # Interfaces only
│       └── IUserRepository.ts
├── application/            # Use cases (depends on domain)
│   ├── use-cases/
│   │   ├── CreateUser.ts
│   │   └── GetUserById.ts
│   └── services/
│       └── AuthService.ts
├── infrastructure/         # External concerns (implements interfaces)
│   ├── database/
│   │   └── PrismaUserRepository.ts
│   ├── external/
│   │   └── StripePaymentGateway.ts
│   └── config/
└── presentation/           # UI/API layer
    ├── api/
    │   └── routes/
    ├── controllers/
    └── dto/

Dependency Rule Example

// ✅ domain/repositories/IUserRepository.ts (interface)
interface IUserRepository {
  findById(id: string): Promise<User | null>;
  save(user: User): Promise<void>;
}

// ✅ infrastructure/database/PrismaUserRepository.ts (implementation)
class PrismaUserRepository implements IUserRepository {
  constructor(private prisma: PrismaClient) {}

  async findById(id: string): Promise<User | null> {
    const data = await this.prisma.user.findUnique({ where: { id } });
    return data ? User.fromPersistence(data) : null;
  }
}

// ✅ application/use-cases/GetUserById.ts (depends on interface)
class GetUserById {
  constructor(private userRepo: IUserRepository) {}

  async execute(id: string): Promise<User> {
    const user = await this.userRepo.findById(id);
    if (!user) throw new UserNotFoundError(id);
    return user;
  }
}

Domain-Driven Design (DDD)

Strategic Patterns

Bounded Contexts: Explicit boundaries around domain models

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│    Ordering     │     │    Inventory    │     │    Shipping     │
│    Context      │────▶│    Context      │────▶│    Context      │
│                 │     │                 │     │                 │
│  Order          │     │  Product        │     │  Shipment       │
│  OrderLine      │     │  Stock          │     │  Carrier        │
│  Customer       │     │  Warehouse      │     │  TrackingInfo   │
└─────────────────┘     └─────────────────┘     └─────────────────┘

Context Mapping: How contexts communicate

  • Shared Kernel: Common code between contexts
  • Customer/Supplier: Upstream/downstream relationship
  • Anti-Corruption Layer: Translation between contexts

Tactical Patterns

Entities: Identity-based objects

class Order {
  constructor(
    public readonly id: OrderId,
    private items: OrderItem[],
    private status: OrderStatus
  ) {}

  addItem(product: Product, quantity: number): void {
    if (this.status !== 'draft') {
      throw new Error('Cannot modify confirmed order');
    }
    this.items.push(new OrderItem(product, quantity));
  }
}

Value Objects: Immutable, equality by value

class Money {
  constructor(
    public readonly amount: number,
    public readonly currency: string
  ) {
    if (amount < 0) throw new Error('Amount cannot be negative');
  }

  add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new Error('Currency mismatch');
    }
    return new Money(this.amount + other.amount, this.currency);
  }

  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency;
  }
}

Aggregates: Consistency boundaries

// Order is the Aggregate Root
// OrderItems can only be modified through Order
class Order {
  private items: OrderItem[] = [];

  // All invariants enforced here
  addItem(item: OrderItem): void {
    if (this.items.length >= 100) {
      throw new Error('Order cannot have more than 100 items');
    }
    this.items.push(item);
  }
}

Domain Events: Communicate state changes

class OrderPlaced implements DomainEvent {
  constructor(
    public readonly orderId: string,
    public readonly customerId: string,
    public readonly occurredAt: Date = new Date()
  ) {}
}

// Usage
order.place();
eventBus.publish(new OrderPlaced(order.id, order.customerId));

Microservices Patterns

When to Use

  • ✅ Large teams needing independent deployment
  • ✅ Different scaling requirements per service
  • ✅ Polyglot persistence needs
  • ❌ Small team (< 10 people)
  • ❌ Unclear domain boundaries
  • ❌ Startups exploring product-market fit

Service Boundaries

✅ Good boundaries:
  - User Service (authentication, profiles)
  - Order Service (order lifecycle)
  - Payment Service (transactions, refunds)
  - Notification Service (email, SMS, push)

❌ Bad boundaries:
  - Database Service (too technical)
  - Validation Service (too generic)
  - Utils Service (not a domain)

Communication Patterns

Synchronous (HTTP/gRPC)

Order Service ──HTTP──▶ Inventory Service
                       "Check stock for product X"

Use for: Queries, real-time validation

Asynchronous (Events/Messages)

Order Service ──Event──▶ Message Broker ──▶ Notification Service
                         "OrderPlaced"      (sends email)

Use for: Side effects, eventual consistency

Data Patterns

Database per Service

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Orders    │     │  Inventory  │     │  Payments   │
│   Service   │     │   Service   │     │   Service   │
└──────┬──────┘     └──────┬──────┘     └──────┬──────┘
       │                   │                   │
   ┌───▼───┐           ┌───▼───┐           ┌───▼───┐
   │ PostgreSQL       │ MongoDB │           │ PostgreSQL
   └───────┘           └───────┘           └───────┘

Saga Pattern for distributed transactions

Order Saga:
  1. Create Order (Orders Service)
  2. Reserve Inventory (Inventory Service)
  3. Process Payment (Payment Service)
  4. Confirm Order (Orders Service)

  If step fails → Compensate previous steps

Modular Monolith

Best of Both Worlds

  • Single deployment unit (simple ops)
  • Strong module boundaries (future extraction ready)
  • Shared database with logical separation

Structure

src/
├── modules/
│   ├── users/
│   │   ├── api/           # HTTP handlers
│   │   ├── domain/        # Business logic
│   │   ├── infra/         # Database, external
│   │   └── index.ts       # Public API only
│   │
│   ├── orders/
│   │   ├── api/
│   │   ├── domain/
│   │   ├── infra/
│   │   └── index.ts
│   │
│   └── payments/
│       └── ...
├── shared/                 # Truly shared code
│   ├── kernel/            # Base classes, interfaces
│   └── utils/             # Pure utilities
└── main.ts                # Composition root

Module Communication Rules

// ✅ Good: Module exposes explicit public API
// modules/users/index.ts
export { UserService } from './domain/UserService';
export { User } from './domain/User';
export type { CreateUserDTO } from './api/dto';

// ✅ Good: Other modules use public API
import { UserService } from '@/modules/users';

// ❌ Bad: Reaching into module internals
import { UserRepository } from '@/modules/users/infra/UserRepository';

Quick Decision Guide

Q: New greenfield project?
A: Start with Modular Monolith

Q: Existing messy codebase?
A: Apply Clean Architecture gradually

Q: Team > 50 engineers?
A: Consider Microservices with clear domain boundaries

Q: Need to scale one component independently?
A: Extract that component as a service

Q: Unclear requirements?
A: Keep it simple, refactor when patterns emerge
Weekly Installs
1
GitHub Stars
381
First Seen
Feb 18, 2026
Installed on
windsurf1
opencode1
cursor1
codex1
claude-code1
antigravity1