Loading...
Loading...
[Pragmatic DDD Architecture] Guide for creating DDD Value Objects (VOs). Use when defining a new Value Object in a domain layer. Covers immutability patterns, private constructors, random/from builders, Railway-oriented error handling via neverthrow Result, TypeScript branded error types, and co-locating errors within the VO file.
npx skill4agent add leif-sync/pragmatic-ddd value-objectsprivate readonlyprivatestatic from(value: T)static random()from()Result<MyVO, InvalidMyVOError>neverthrowstatic checkValidity(value: T): booleanisGreaterThan(other: ValueObject)add(other: ValueObject)equals(other: ValueObject).toBranded()uuidzodcheckValidity()z.uuid()z.email()z.url()Resultinstanceofdeclare const uniqueBrand: unique symbol;
export type BrandedDomainConcept = string & { readonly [uniqueBrand]: never };
declare const uniqueError: unique symbol;
export class InvalidDomainConceptError extends Error {
declare private readonly [uniqueError]: never;
private readonly invalidValue: string;
// ...
}import { Result, ok, err } from "neverthrow";
import { z } from "zod";
// 1. Primitive Branded Type
declare const brandedTopicNameSymbol: unique symbol;
export type BrandedTopicName = string & { readonly [brandedTopicNameSymbol]: never };
// 2. Co-located Error Class
declare const unique: unique symbol;
export class InvalidTopicNameError extends Error {
declare private readonly [unique]: never;
private readonly invalidValue: string;
constructor(invalidValue: string) {
super(`The topic name "${invalidValue}" is invalid.`);
this.invalidValue = invalidValue;
}
getInvalidValue(): string {
return this.invalidValue;
}
}
// 3. The Value Object
export class TopicName {
static readonly MIN_LENGTH = 3;
static readonly MAX_LENGTH = 50;
// Immutability
private readonly value: string;
// Private constructor
private constructor(value: string) {
this.value = value.trim();
}
// Pure validation check
static checkValidity(value: string): boolean {
const trimmed = value.trim();
// Zod can be used for things like z.email().safeParse(value).success
return trimmed.length >= TopicName.MIN_LENGTH && trimmed.length <= TopicName.MAX_LENGTH;
}
// Instantiation via Result pattern
static from(value: string): Result<TopicName, InvalidTopicNameError> {
const trimmed = value.trim();
if (!TopicName.checkValidity(trimmed)) {
return err(new InvalidTopicNameError(trimmed));
}
return ok(new TopicName(trimmed));
}
// Test / System generation
static random(): TopicName {
return new TopicName(`topic-${Math.floor(Math.random() * 10000)}`);
}
// Safe extraction for cross-boundary serialization and queries
toBranded(): BrandedTopicName {
return this.value as BrandedTopicName;
}
}.agents/skills/value-objects/references/isGreaterThanaddsubtractminmaxzodz.email()