Files
gridpilot.gg/docs/architecture/core/ENUMS.md
2026-01-11 13:04:33 +01:00

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.

  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

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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

  1. 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);

  1. 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

  1. 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

  1. 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.