5.1 KiB
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.
⸻
- 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
⸻
- 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.
⸻
- 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.
⸻
- 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.
⸻
- 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.
⸻
- 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
⸻
- 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);
⸻
- 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
⸻
- 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
⸻
- 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.