Skip to content

prefer-nominal-id-types

Python boundaries should distinguish swappable identifier roles with nominal types.

Why

Two identifiers with the same primitive or container carrier can be exchanged without a type-checking error. Nominal types make those role mistakes visible while leaving unlike carriers alone.

Fix

Use typing.NewType as the low-runtime-cost default and propagate it from the raw edge through the domain boundary. A nominal value object is also valid when runtime validation or behavior is required.

Examples

Before — flagged Two domain ID roles share the same primitive carrier
app/services/files.py
def move(file_id: str, parent_folder_id: str) -> None: ...
After — preferred NewType makes the two domain roles distinct
app/services/files.py
from typing import NewType
FileId = NewType("FileId", str)
FolderId = NewType("FolderId", str)
def move(file_id: FileId, parent_folder_id: FolderId) -> None: ...