339 lines
5.1 KiB
Markdown
339 lines
5.1 KiB
Markdown
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/<context>/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/<context>/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/<feature>/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/<feature>/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. |