Data shapes Recommended
Wrap domain values in their own types
Give each domain value with its own rules a small type, such as EmailAddress, OrderId or Money. Never pass it around as a bare string or number.
How to do it
Validate the value once, in the type's constructor or factory, and trust it everywhere after. Two IDs then cannot be swapped by mistake, because their types differ. Use branded types in TypeScript, NewType or a frozen dataclass in Python, and a readonly class in PHP. Store money as the money rule says, never as a float.
Example
type OrderId = string & { readonly brand: 'OrderId' };
type CustomerId = string & { readonly brand: 'CustomerId' };
function refund(order: OrderId, customer: CustomerId) { /* ... */ }
refund(customerId, orderId); // Fails to compile, because the arguments are swapped
OrderId = NewType("OrderId", str)
@dataclass(frozen=True)
class EmailAddress:
value: str
def __post_init__(self):
if "@" not in self.value:
raise InvalidEmail(self.value)
How to check
A type test passes a swapped ID to a typed function and marks the line as an expected error. Examples are // @ts-expect-error in TypeScript and # type: ignore[arg-type] with mypy --warn-unused-ignores. The checker fails if the error ever disappears. A unit test gives each factory an invalid value, such as "not-an-email", and expects an error.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers architecture playbook: https://agentwranglers.se/playbook/architecture/data-shapes/value-objects.