# Crafted Web Architecture

## Overview

This template implements **Hexagonal Architecture** (also known as Ports & Adapters) with Domain-Driven Design principles. It's designed for long-term sustainability (5-10+ years) with teams of 10-200+ developers.

## Architecture Layers

### 1. Domain Layer (Core Business Logic)

**Location**: `/packages/domain/`

**Characteristics**:
- ZERO external dependencies (except dev tools like TypeScript and Vitest)
- Pure business logic
- Framework-agnostic
- 100% test coverage required
- Result pattern for error handling (no exceptions)

**Structure**:
```
domain/
├── entities/           # Rich domain entities
│   ├── User.ts
│   ├── User.test.ts
│   ├── Product.ts
│   └── Product.test.ts
├── value-objects/      # Immutable value objects
│   ├── Email.ts
│   ├── Email.test.ts
│   ├── Money.ts
│   └── Money.test.ts
├── use-cases/          # Application use-cases
│   ├── CreateUser.ts
│   ├── CreateUser.test.ts
│   ├── CreateProduct.ts
│   └── CreateProduct.test.ts
├── repositories/       # Repository interfaces (ports)
│   ├── IUserRepository.ts
│   └── IProductRepository.ts
└── shared/
    └── Result.ts       # Result pattern implementation
```

**Key Concepts**:

#### Entities
Rich domain objects with behavior and identity:
```typescript
export class User {
  private constructor(private props: UserProps) {}

  static create(props: UserProps): Result<User, UserInvalidError> {
    // Validation logic
  }

  suspend(): Result<User, UserInvalidError> {
    // Business logic
  }
}
```

#### Value Objects
Immutable objects defined by their values:
```typescript
export class Email {
  private constructor(private value: string) {}

  static create(email: string): Result<Email, EmailInvalidError> {
    // Validation
  }

  equals(other: Email): boolean {
    return this.value === other.value;
  }
}
```

#### Use Cases
Application logic orchestrating domain operations:
```typescript
export class CreateUser {
  constructor(private userRepository: IUserRepository) {}

  async execute(input: CreateUserInput): Promise<Result<User, Error>> {
    // Orchestrate domain logic
  }
}
```

#### Repository Interfaces
Define contracts for data access (implementations in infrastructure):
```typescript
export interface IUserRepository {
  findById(id: string): Promise<Result<User, UserNotFoundError>>;
  save(user: User): Promise<Result<User, Error>>;
}
```

### 2. Application Layer (API Routers)

**Location**: `/packages/api/src/routers/`

**Characteristics**:
- Ultra-thin (< 10 lines per procedure)
- Input validation with Zod
- Use-case instantiation and execution
- No business logic

**Structure**:
```
api/
└── routers/
    ├── user.ts
    ├── product.ts
    ├── auth.ts
    └── payment.ts
```

**Example Router**:
```typescript
export const userRouter = router({
  create: publicProcedure
    .input(z.object({
      id: z.string(),
      email: z.string(),
      name: z.string()
    }))
    .mutation(async ({ input }) => {
      const useCase = new CreateUser(userRepository);
      return useCase.execute(input);
    }),
});
```

### 3. Infrastructure Layer (External Implementations)

**Location**: `/packages/infrastructure/`

**Characteristics**:
- Implements domain repository interfaces
- External service integrations
- 80% test coverage required
- Can throw exceptions (converted to Result at boundary)

**Structure**:
```
infrastructure/
├── database/
│   └── repositories/
│       ├── PrismaUserRepository.ts
│       └── PrismaProductRepository.ts
├── payments/
│   └── StripeClient.ts
└── emails/
    └── ResendEmailService.ts
```

**Example Repository**:
```typescript
export class PrismaUserRepository implements IUserRepository {
  constructor(private prisma: PrismaClient) {}

  async save(user: User): Promise<Result<User, Error>> {
    try {
      const dbUser = await this.prisma.user.upsert({
        // Prisma operations
      });
      return this.toDomain(dbUser);
    } catch (error) {
      return failure(error as Error);
    }
  }
}
```

## Dependency Rule

**Critical**: Dependencies point INWARD toward the domain, never outward.

```
┌─────────────────────────────────────┐
│     Infrastructure Layer            │
│  (Depends on Domain interfaces)     │
└────────────────┬────────────────────┘
                 │
                 ↓
┌─────────────────────────────────────┐
│         Domain Layer                │
│  (ZERO external dependencies)       │
└────────────────┬────────────────────┘
                 ↑
                 │
┌────────────────┴────────────────────┐
│      Application Layer              │
│  (Depends on Domain use-cases)      │
└─────────────────────────────────────┘
```

**Why?**
- Domain logic is stable and long-lived
- Infrastructure changes don't affect domain
- Can swap implementations without changing domain
- Domain can be tested in isolation

## Result Pattern

We use the Result pattern instead of exceptions in the domain layer.

### Definition
```typescript
export type Result<T, E = Error> = Success<T> | Failure<E>;

export class Success<T> {
  readonly isSuccess = true;
  readonly isFailure = false;
  constructor(readonly value: T) {}
}

export class Failure<E = Error> {
  readonly isSuccess = false;
  readonly isFailure = true;
  constructor(readonly error: E) {}
}
```

### Usage
```typescript
// Creating results
const emailResult = Email.create('test@example.com');

// Consuming results
if (emailResult.isSuccess) {
  console.log(emailResult.value.getValue());
} else {
  console.error(emailResult.error.message);
}

// Chaining operations
async execute(input: CreateUserInput): Promise<Result<User, Error>> {
  const emailResult = Email.create(input.email);
  if (emailResult.isFailure) return failure(emailResult.error);

  const userResult = User.create({ ...input, email: emailResult.value });
  if (userResult.isFailure) return failure(userResult.error);

  return this.userRepository.save(userResult.value);
}
```

### Benefits
1. **Explicit**: Errors are in the type signature
2. **Type-safe**: Compiler enforces error handling
3. **Pure**: No side effects (exceptions are side effects)
4. **Composable**: Easy to chain operations

See [ADR 002](./docs/adr/002-result-pattern-over-exceptions.md) for full rationale.

## Testing Strategy

### Domain Layer: Spec-First (100% Coverage)

**Approach**:
1. UNDERSTAND business rules first
2. Generate tests FIRST (they specify behavior)
3. Then generate implementation that satisfies tests

**Example**:
```typescript
// 1. Write test FIRST
describe('Email', () => {
  it('should create valid email', () => {
    const result = Email.create('test@example.com');
    expect(result.isSuccess).toBe(true);
  });

  it('should reject invalid email', () => {
    const result = Email.create('invalid');
    expect(result.isFailure).toBe(true);
  });
});

// 2. Then implement to satisfy tests
export class Email {
  static create(email: string): Result<Email, EmailInvalidError> {
    // Implementation
  }
}
```

**Coverage**: 100% required for:
- All entities
- All value objects
- All use-cases

### Infrastructure Layer (80% Coverage)

**Approach**: Test-After Generation

Repository implementations are tested with:
- Happy path scenarios
- Error scenarios
- Edge cases

### Integration Tests

E2E tests verify the entire stack:
- Authentication flows
- Payment flows
- Critical user journeys

## Code Quality Rules

| Aspect | Crafted Level |
|--------|---------------|
| Max lines per function | 20 lines |
| Max parameters | 2 |
| Duplication | 0 (CI fails) |
| Cyclomatic complexity | < 5 |
| SOLID | Full + auditable |
| Comments | WHY + ADR ref only |
| Domain test coverage | 100% |
| Infrastructure test coverage | 80% |

## Package Structure

```
packages/
├── domain/             # ⭐ Core business logic (ZERO deps)
├── infrastructure/     # External implementations
├── api/                # tRPC routers (ultra-thin)
├── validators/         # Zod schemas (input validation)
├── database/           # Prisma schema + client
├── auth/               # Supabase Auth wrapper
├── payments/           # Stripe wrapper
├── emails/             # Resend + React Email
└── ui/                 # shadcn/ui components
```

## Data Flow

### Example: Creating a User

```
1. Client Request
   ↓
2. tRPC Router (Application Layer)
   - Validates input with Zod
   - Instantiates CreateUser use-case
   ↓
3. CreateUser Use-Case (Domain Layer)
   - Creates Email value object
   - Creates User entity
   - Calls repository interface
   ↓
4. PrismaUserRepository (Infrastructure Layer)
   - Saves to database via Prisma
   - Converts DB record to domain entity
   - Returns Result<User>
   ↓
5. Response flows back up
   - Use-case returns Result
   - Router serializes response
   - Client receives data
```

## Migration Path

### From Balanced to Crafted

1. **Create domain package**
   ```bash
   mkdir -p packages/domain/src/{entities,value-objects,use-cases,repositories}
   ```

2. **Extract business logic from services**
   - Move validation logic → Value Objects
   - Move business rules → Entities
   - Move orchestration → Use-Cases

3. **Define repository interfaces**
   - Create interfaces in domain
   - Implement in infrastructure

4. **Refactor routers**
   - Make them ultra-thin
   - Use use-cases instead of services

5. **Add comprehensive tests**
   - 100% domain coverage
   - 80% infrastructure coverage

## Best Practices

### DO ✅

- Keep domain layer pure (no external deps)
- Use Result pattern for domain errors
- Write tests FIRST for domain logic
- Keep routers ultra-thin (< 10 lines)
- Document decisions in ADRs
- Use value objects for business concepts
- Make entities rich with behavior

### DON'T ❌

- Import external libraries in domain
- Throw exceptions in domain layer
- Put business logic in routers
- Skip tests for domain logic
- Use anemic domain models
- Leak infrastructure concerns into domain
- Forget to update ADRs for big decisions

## Resources

- [ADR Index](./docs/adr/README.md)
- [Hexagonal Architecture](./docs/adr/001-hexagonal-architecture.md)
- [Result Pattern](./docs/adr/002-result-pattern-over-exceptions.md)
- [Zero Dependencies Rule](./docs/adr/003-zero-dependencies-domain-layer.md)

## Support

For questions about the architecture:
1. Check ADRs first
2. Review code examples in domain package
3. Run tests to understand expected behavior
4. Consult with senior engineers on the team
