Enums in Clean Architecture (Strict & Final) This document defines how enums are modeled, placed, and used in a strict Clean Architecture setup. Enums are one of the most common sources of architectural leakage. This guide removes all ambiguity. ⸻ 1. Core Principle Enums represent knowledge. Knowledge must live where it is true. Therefore: • Not every enum is a domain enum • Enums must never cross architectural boundaries blindly • Ports must remain neutral ⸻ 2. Enum Categories (Authoritative) There are four and only four valid enum categories: 1. Domain Enums 2. Application (Workflow) Enums 3. Transport Enums (API) 4. UI Enums (Frontend) Each category has strict placement and usage rules. ⸻ 3. Domain Enums Definition A Domain Enum represents a business concept that: • has meaning in the domain • affects rules or invariants • is part of the ubiquitous language Examples: • LeagueVisibility • MembershipRole • RaceStatus • SponsorshipTier • PenaltyType ⸻ Placement core//domain/ ├── value-objects/ │ └── LeagueVisibility.ts └── entities/ Preferred: model domain enums as Value Objects instead of enum keywords. ⸻ Example (Value Object) export class LeagueVisibility { private constructor(private readonly value: 'public' | 'private') {} static from(value: string): LeagueVisibility { if (value !== 'public' && value !== 'private') { throw new DomainError('Invalid LeagueVisibility'); } return new LeagueVisibility(value); } isPublic(): boolean { return this.value === 'public'; } } ⸻ Usage Rules Allowed: • Domain • Use Cases Forbidden: • Ports • Adapters • API DTOs • Frontend Domain enums must never cross a Port boundary. ⸻ 4. Application Enums (Workflow Enums) Definition Application Enums represent internal workflow or state coordination. They are not business truth and must not leak. Examples: • LeagueSetupStep • ImportPhase • ProcessingState ⸻ Placement core//application/internal/ └── LeagueSetupStep.ts ⸻ Example export enum LeagueSetupStep { CreateLeague, CreateSeason, AssignOwner, Notify } ⸻ Usage Rules Allowed: • Application Services • Use Cases Forbidden: • Domain • Ports • Adapters • Frontend These enums must remain strictly internal. ⸻ 5. Transport Enums (API DTOs) Definition Transport Enums describe allowed values in HTTP contracts. They exist purely to constrain transport data, not to encode business rules. Naming rule: Transport enums MUST end with Enum. This makes enums immediately recognizable in code reviews and prevents silent leakage. Examples: • LeagueVisibilityEnum • SponsorshipStatusEnum • PenaltyTypeEnum ⸻ Placement apps/api//dto/ └── LeagueVisibilityEnum.ts Website mirrors the same naming: apps/website/lib/dtos/ └── LeagueVisibilityEnum.ts ⸻ Example export enum LeagueVisibilityEnum { Public = 'public', Private = 'private' } ⸻ Usage Rules Allowed: • API Controllers • API Presenters • Website API DTOs Forbidden: • Core Domain • Use Cases Transport enums are copies, never reexports of domain enums. ⸻ Placement apps/api//dto/ └── LeagueVisibilityDto.ts or inline as union types in DTOs. ⸻ Example export type LeagueVisibilityDto = 'public' | 'private'; ⸻ Usage Rules Allowed: • API Controllers • API Presenters • Website API DTOs Forbidden: • Core Domain • Use Cases Transport enums are copies, never reexports of domain enums. ⸻ 6. UI Enums (Frontend) Definition UI Enums describe presentation or interaction state. They have no business meaning. Examples: • WizardStep • SortOrder • ViewMode • TabKey ⸻ Placement apps/website/lib/ui/ └── LeagueWizardStep.ts ⸻ Example export enum LeagueWizardStep { Basics, Structure, Scoring, Review } ⸻ Usage Rules Allowed: • Frontend only Forbidden: • Core • API ⸻ 7. Absolute Prohibitions ❌ Enums in Ports // ❌ forbidden export interface CreateLeagueInputPort { visibility: LeagueVisibility; } ✅ Correct export interface CreateLeagueInputPort { visibility: 'public' | 'private'; } Mapping happens inside the Use Case: const visibility = LeagueVisibility.from(input.visibility); ⸻ 8. Decision Checklist Ask these questions: 1. Does changing this enum change business rules? • Yes → Domain Enum • No → continue 2. Is it only needed for internal workflow coordination? • Yes → Application Enum • No → continue 3. Is it part of an HTTP contract? • Yes → Transport Enum • No → continue 4. Is it purely for UI state? • Yes → UI Enum ⸻ 9. Summary Table Enum Type Location May Cross Ports Scope Domain Enum core/domain ❌ No Business rules Application Enum core/application ❌ No Workflow only Transport Enum apps/api + website ❌ No HTTP contracts UI Enum apps/website ❌ No Presentation only ⸻ 10. Final Rule (Non-Negotiable) If an enum crosses a boundary, it is in the wrong place. This rule alone prevents most long-term architectural decay.