ai-factory.architecture
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
More from lee-to/ai-factory
aif-skill-generator
Generate professional Agent Skills for AI agents. Creates complete skill packages with SKILL.md, references, scripts, and templates. Use when creating new skills, generating custom slash commands, or building reusable AI capabilities. Validates against Agent Skills specification.
38aif-implement
Execute implementation tasks from the current plan. Works through tasks sequentially, marks completion, and preserves progress for continuation across sessions. Use when user says "implement", "start coding", "execute plan", or "continue implementation".
37aif-security-checklist
Security audit checklist based on OWASP Top 10 and best practices. Covers authentication, injection, XSS, CSRF, secrets management, and more. Use when reviewing security, before deploy, asking "is this secure", "security check", "vulnerability".
35aif-plan
Plan implementation for a feature or task. Two modes — fast (single quick plan) or full (richer plan with optional git branch/worktree flow). Use when user says "plan", "new feature", "start feature", "create tasks".
34aif
Set up agent context for a project. Analyzes tech stack, installs relevant skills from skills.sh, generates custom skills, and configures MCP servers. Use when starting new project, setting up AI context, or asking "set up project", "configure AI", "what skills do I need".
33aif-commit
Create conventional commit messages by analyzing staged changes. Generates semantic commit messages following the Conventional Commits specification. Use when user says "commit", "save changes", or "create commit".
33