# Module Design

# Module Design Document

> **Project:** {{PROJECT_NAME}}
> **Module:** {{MODULE_NAME}}
> **Service:** {{SERVICE_NAME}}
> **Version:** {{VERSION}}
> **Date:** {{DATE}}
> **Author:** {{AUTHOR}}
> **Status:** Draft | In Review | Approved
> **Reviewers:** {{REVIEWERS}}

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1     | {{DATE}} | {{AUTHOR}} | Initial draft |

---

## 1. Module Overview & Responsibility

<!-- GUIDANCE: Clearly define the module's single responsibility. A module should do one thing well. If you need "and" to describe it, consider splitting. State what it owns, what it doesn't own, and why it exists as a separate module. -->

**Module:** `{{MODULE_NAME}}`
**Layer:** Domain | Application | Infrastructure | Presentation
**Repository:** `{{REPO_OR_MONOREPO_PATH}}`
**Team Owner:** {{TEAM_NAME}}

**Single Responsibility Statement:**
> {{THE_MODULE_IS_RESPONSIBLE_FOR_ONE_THING}}

**This module owns:**
- {{OWNED_RESOURCE_1}} (data + business logic)
- {{OWNED_RESOURCE_2}}

**This module does NOT own:**
- {{NOT_OWNED_1}} — owned by `{{OTHER_MODULE}}`
- {{NOT_OWNED_2}} — owned by `{{OTHER_MODULE}}`

**Why this is a separate module:**
{{RATIONALE_FOR_SEPARATION}} — e.g., "Separate bounded context, different team ownership, different scaling requirements"

---

## 2. Interface Definition (Public API)

<!-- GUIDANCE: This is the contract that other modules/services depend on. Changes here require coordination. Be precise. -->

### 2.1 Exported Service Interface

```typescript
// Public interface exported by this module
export interface I{{ModuleName}}Service {
  /**
   * {{METHOD_1_DESCRIPTION}}
   * @throws {ValidationError} if dto is invalid
   * @throws {ConflictError} if {{UNIQUE_FIELD}} already exists
   */
  create(dto: Create{{Entity}}Dto, context: RequestContext): Promise<{{Entity}}>;

  /**
   * {{METHOD_2_DESCRIPTION}}
   * @throws {NotFoundError} if not found
   */
  findById(id: string, context: RequestContext): Promise<{{Entity}}>;

  /**
   * {{METHOD_3_DESCRIPTION}}
   */
  findAll(filter: {{Filter}}Dto, context: RequestContext): Promise<PaginatedResult<{{Entity}}>>;

  /**
   * {{METHOD_4_DESCRIPTION}}
   * @throws {NotFoundError} if not found
   * @throws {ForbiddenError} if user lacks permission
   */
  update(id: string, dto: Update{{Entity}}Dto, context: RequestContext): Promise<{{Entity}}>;

  /**
   * {{METHOD_5_DESCRIPTION}}
   * @throws {NotFoundError} if not found
   */
  delete(id: string, context: RequestContext): Promise<void>;
}

// DTOs exported for consumers
export type Create{{Entity}}Dto = { /* ... */ };
export type Update{{Entity}}Dto = { /* ... */ };
export type {{Filter}}Dto = { /* ... */ };
export type {{Entity}} = { /* ... */ };
```

### 2.2 HTTP Endpoints (if applicable)

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `POST` | `/api/v{{V}}/{{resource}}` | JWT | Create {{entity}} |
| `GET` | `/api/v{{V}}/{{resource}}` | JWT | List {{entities}} |
| `GET` | `/api/v{{V}}/{{resource}}/:id` | JWT | Get by ID |
| `PUT` | `/api/v{{V}}/{{resource}}/:id` | JWT | Full update |
| `PATCH` | `/api/v{{V}}/{{resource}}/:id` | JWT | Partial update |
| `DELETE` | `/api/v{{V}}/{{resource}}/:id` | JWT | Soft delete |

### 2.3 Events Published

<!-- GUIDANCE: Events are part of the public interface. Once published, they're consumed by others — treat event schema changes as breaking changes. -->

| Event | Topic/Queue | Schema | Triggered By |
|-------|------------|--------|--------------|
| `{{entity}}.created` | `{{TOPIC}}` | See §5 | POST endpoint |
| `{{entity}}.updated` | `{{TOPIC}}` | See §5 | PUT/PATCH endpoint |
| `{{entity}}.deleted` | `{{TOPIC}}` | See §5 | DELETE endpoint |
| `{{entity}}.{{CUSTOM_EVENT}}` | `{{TOPIC}}` | See §5 | {{BUSINESS_TRIGGER}} |

---

## 3. Internal Structure

<!-- GUIDANCE: Show how the module is organized internally. Use the layer diagram to clarify separation of concerns. -->

```
{{MODULE_NAME}}/
├── controllers/
│   └── {{entity}}.controller.ts      # HTTP request handling, input parsing
├── services/
│   └── {{entity}}.service.ts         # Business logic
├── repositories/
│   ├── {{entity}}.repository.ts      # Data access interface
│   └── {{entity}}.repository.pg.ts   # PostgreSQL implementation
├── domain/
│   ├── {{entity}}.entity.ts          # Domain entity / value objects
│   ├── {{entity}}.events.ts          # Domain events
│   └── {{entity}}.errors.ts          # Domain-specific errors
├── dto/
│   ├── create-{{entity}}.dto.ts
│   ├── update-{{entity}}.dto.ts
│   └── {{entity}}-filter.dto.ts
├── mappers/
│   └── {{entity}}.mapper.ts          # DB record ↔ domain entity ↔ DTO
├── __tests__/
│   ├── unit/
│   └── integration/
└── {{entity}}.module.ts              # Module registration / DI wiring
```

**Layer rules (enforced by linting):**
- Controllers only call Services (never Repositories directly)
- Services only call Repositories and publish Events
- Domain entities have no framework dependencies
- Mappers live at service layer — not in controllers

---

## 4. Database Schema

<!-- GUIDANCE: Tables owned by this module only. Cross-module tables should be avoided — use events for cross-module data sync. -->

### Primary Table: `{{table_name}}`

| Column | Type | Nullable | Default | Constraints | Description |
|--------|------|----------|---------|-------------|-------------|
| `id` | `UUID` | NO | `gen_random_uuid()` | PK | Surrogate key |
| `created_at` | `TIMESTAMPTZ` | NO | `NOW()` | | Immutable creation time |
| `updated_at` | `TIMESTAMPTZ` | NO | `NOW()` | | Auto-updated on write |
| `deleted_at` | `TIMESTAMPTZ` | YES | `NULL` | | Soft delete marker |
| `version` | `INTEGER` | NO | `1` | | Optimistic lock version |
| `{{FIELD_1}}` | `{{TYPE}}` | {{YES/NO}} | `{{DEFAULT}}` | {{CHECK/UNIQUE/FK}} | {{DESCRIPTION}} |
| `{{FIELD_2}}` | `{{TYPE}}` | {{YES/NO}} | `{{DEFAULT}}` | {{CHECK/UNIQUE/FK}} | {{DESCRIPTION}} |
| `{{FK_ID}}` | `UUID` | NO | | FK → `{{other_table}}(id)` | {{RELATIONSHIP}} |

**Indexes:**
```sql
CREATE INDEX CONCURRENTLY idx_{{table_name}}_{{column}} ON {{table_name}}({{column}})
    WHERE deleted_at IS NULL;
-- Rationale: {{WHY_THIS_INDEX}}

CREATE UNIQUE INDEX idx_{{table_name}}_{{unique_column}} ON {{table_name}}({{unique_column}})
    WHERE deleted_at IS NULL;
-- Rationale: Enforce uniqueness for active records only
```

**RLS (Row-Level Security):**
<!-- GUIDANCE: Enable RLS if multi-tenant or if different users should see different rows at DB level. -->
```sql
-- TODO: Enable if multi-tenant
-- ALTER TABLE {{table_name}} ENABLE ROW LEVEL SECURITY;
-- CREATE POLICY {{table_name}}_tenant_isolation ON {{table_name}}
--     USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
```

---

## 5. API Endpoints (Detailed)

<!-- GUIDANCE: For each endpoint, document the complete request and response contract. -->

### `POST /api/v{{V}}/{{resource}}`

**Request:**
```json
{
  "{{field1}}": "string (required, max 255 chars)",
  "{{field2}}": "number (required, > 0)",
  "{{field3}}": "string (optional, enum: [A, B, C])"
}
```

**Success `201`:**
```json
{
  "id": "uuid",
  "{{field1}}": "value",
  "{{field2}}": 0,
  "createdAt": "ISO8601"
}
```

**Event published:** `{{entity}}.created`
```json
{
  "specversion": "1.0",
  "type": "{{entity}}.created",
  "source": "/{{resource}}",
  "id": "{{EVENT_UUID}}",
  "time": "ISO8601",
  "data": {
    "entityId": "uuid",
    "tenantId": "uuid",
    "createdBy": "uuid",
    "{{field1}}": "value"
  }
}
```

---

## 6. Business Logic Specifications

<!-- GUIDANCE: Document the business rules that live in the service layer. These should be independent of HTTP or database concerns. -->

### 6.1 Business Rules

| Rule ID | Rule | Enforced In | Error |
|---------|------|-------------|-------|
| BR-001 | {{RULE_1_DESCRIPTION}} | Service | `{{ERROR_CODE}}` |
| BR-002 | {{RULE_2_DESCRIPTION}} | Domain Entity | `{{ERROR_CODE}}` |
| BR-003 | {{RULE_3_DESCRIPTION}} | Service + DB constraint | `ConflictError` |

### 6.2 Validation Rules

| Field | Type | Required | Validation | Error Message |
|-------|------|----------|-----------|---------------|
| `{{field1}}` | `string` | Yes | Min 1, Max 255 chars | "{{field1}} must be 1-255 characters" |
| `{{field2}}` | `number` | Yes | Positive integer | "{{field2}} must be a positive integer" |
| `{{field3}}` | `enum` | No | One of: [A, B, C] | "{{field3}} must be A, B, or C" |

### 6.3 Authorization Rules

| Operation | Required Role | Additional Conditions |
|-----------|--------------|----------------------|
| Create | `{{ROLE}}` | — |
| Read own | Any authenticated user | `userId === resource.createdBy` |
| Read any | `{{ADMIN_ROLE}}` | — |
| Update | `{{ROLE}}` | `userId === resource.createdBy OR isAdmin` |
| Delete | `{{ADMIN_ROLE}}` | Soft delete only |
| Hard delete | `SUPER_ADMIN` | Requires 2FA confirmation |

---

## 7. Event Publishing / Consuming

<!-- GUIDANCE: Events are the module's async API. Document both published and consumed events. -->

### 7.1 Events Published

| Event | When | Payload Schema | Idempotency Key |
|-------|------|----------------|-----------------|
| `{{entity}}.created` | After successful create | `{entityId, tenantId, ...}` | `entityId` |
| `{{entity}}.updated` | After successful update | `{entityId, changes: {...}}` | `entityId + version` |
| `{{entity}}.deleted` | After soft delete | `{entityId, deletedAt}` | `entityId` |

### 7.2 Events Consumed

| Event | Source Module | Handler | Processing Guarantee |
|-------|-------------|---------|---------------------|
| `{{other_entity}}.deleted` | `{{OTHER_MODULE}}` | Cascade soft-delete related records | At-least-once |
| `{{other_entity}}.updated` | `{{OTHER_MODULE}}` | Update denormalized cache | At-least-once |

**Idempotency strategy:** All consumers check `processed_events` table before processing. Duplicate events are logged and skipped.

---

## 8. Dependencies

<!-- GUIDANCE: Upstream = this module depends on them. Downstream = they depend on this module. Circular dependencies are forbidden. -->

### 8.1 Upstream (what this module depends on)

| Dependency | Type | Coupling | Reason |
|-----------|------|---------|--------|
| `AuthModule` | Internal module | Loose (interface) | JWT validation, user context |
| `{{OTHER_MODULE}}` | Internal module | Loose (events) | {{REASON}} |
| PostgreSQL | Infrastructure | Required | Primary storage |
| Redis | Infrastructure | Optional | Caching (degrades gracefully) |
| `{{EXTERNAL_SDK}}` | External library | Hard | {{REASON}} |

### 8.2 Downstream (what depends on this module)

| Consumer | What they use | Notes |
|---------|--------------|-------|
| `{{OTHER_MODULE}}` | `{{entity}}.created` events | Read-only consumer |
| `{{FRONTEND}}` | HTTP API | Via API gateway |

---

## 9. Error Handling & Recovery

<!-- GUIDANCE: Define how each error type is handled. Include database errors, external API failures, validation errors. -->

| Error Scenario | Handling | User Impact | Recovery |
|---------------|---------|------------|---------|
| DB connection lost | Retry 3x with backoff, then 503 | Request fails gracefully | Auto-recover when DB reconnects |
| External API timeout | Return cached data or 503 | Degraded feature | Async retry, alert on-call |
| Duplicate submission | Detect via unique constraint, return 409 | Clear error message | None needed |
| Invalid state transition | Return 422 with state machine error | Clear error message | User corrects input |
| Event publish failure | Log to retry queue, return 202 | Async delay | Background retry |

---

## 10. Configuration & Feature Flags

<!-- GUIDANCE: All configuration this module reads. Feature flags allow gradual rollout without deployments. -->

### Environment Variables

| Variable | Type | Default | Description |
|---------|------|---------|-------------|
| `{{MODULE_NAME}}_CACHE_TTL` | `number` | `300` | Cache TTL seconds |
| `{{MODULE_NAME}}_MAX_LIST_SIZE` | `number` | `100` | Max items per page |
| `{{MODULE_NAME}}_RATE_LIMIT_RPM` | `number` | `60` | Rate limit per minute |

### Feature Flags

| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `{{MODULE_NAME}}_ENABLE_CACHE` | `boolean` | `true` | Toggle Redis caching |
| `{{MODULE_NAME}}_ENABLE_{{FEATURE}}` | `boolean` | `false` | Gradual rollout of {{FEATURE}} |

---

## 11. Monitoring & Health Checks

<!-- GUIDANCE: How do we know this module is healthy? What metrics indicate problems? -->

### Health Check Endpoint

`GET /health/{{module-name}}`

```json
{
  "status": "healthy | degraded | unhealthy",
  "checks": {
    "database": "healthy",
    "cache": "healthy | degraded",
    "externalApi": "healthy | degraded | unhealthy"
  },
  "latency": {
    "database_ms": 5,
    "cache_ms": 1
  }
}
```

### Key Metrics

| Metric | Type | Alert Threshold | Dashboard |
|--------|------|-----------------|-----------|
| `{{module}}_requests_total` | Counter | — | {{DASHBOARD_LINK}} |
| `{{module}}_request_duration_ms` | Histogram | p99 > {{THRESHOLD}}ms | {{DASHBOARD_LINK}} |
| `{{module}}_errors_total` | Counter | Error rate > {{THRESHOLD}}% | {{DASHBOARD_LINK}} |
| `{{module}}_cache_hit_rate` | Gauge | < {{THRESHOLD}}% for 5min | {{DASHBOARD_LINK}} |
| `{{module}}_db_pool_exhausted` | Counter | Any occurrence | {{DASHBOARD_LINK}} |

---

## 12. Primary Flow — Sequence Diagram

<!-- GUIDANCE: Show the create flow end-to-end through this module's layers. -->

```mermaid
sequenceDiagram
    autonumber
    participant C as Controller
    participant S as Service
    participant V as Validator
    participant R as Repository
    participant DB as PostgreSQL
    participant EB as Event Bus
    participant Cache as Redis

    C->>V: validate(dto)
    alt Invalid input
        V-->>C: ValidationError
        C-->>Client: 400 Bad Request
    end
    V-->>C: Validated DTO

    C->>S: create(dto, context)
    S->>S: checkBusinessRules(dto)
    alt Business rule violation
        S-->>C: BusinessRuleError
        C-->>Client: 422 Unprocessable
    end

    S->>DB: BEGIN TRANSACTION
    S->>R: create(entityData)
    R->>DB: INSERT INTO {{table_name}}
    DB-->>R: Inserted record
    R-->>S: {{Entity}} domain object

    S->>DB: COMMIT

    S->>EB: publish("{{entity}}.created", event)
    Note over EB: Async — does not block response

    S->>Cache: INVALIDATE related keys
    S-->>C: {{Entity}} DTO
    C-->>Client: 201 Created
```

---

## Approval
| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | | | |
| Module Owner | | | |
| Tech Lead | | | |
| Reviewer | | | |