Scaffold Entity
Generate a DDD Entity class with proper identity, equality, and encapsulation.
Triggers
- •"scaffold entity"
- •"create entity"
- •"new entity"
Instructions
Step 1: Gather Information
Ask the student for:
- •Entity name (e.g.,
Order,Customer,Product) - •Properties with types (e.g.,
name: string,email: string,status: OrderStatus) - •Invariants — business rules that must always be true (e.g., "email must be valid", "name cannot be empty")
If the student provides a name only, suggest reasonable properties based on the domain.
Step 2: Determine Output Location
Check the current working directory:
- •If in a
before/directory, create files there - •If in a lesson directory, create in
before/ - •Otherwise, create in the current directory
Step 3: Generate Entity File
Create {entity-name}.ts (kebab-case filename):
typescript
import { z } from 'zod';
// === Branded ID Type ===
declare const __brand: unique symbol;
type Brand<T, B extends string> = T & { [__brand]: B };
export type {EntityName}Id = Brand<string, '{EntityName}Id'>;
export function create{EntityName}Id(value: string): {EntityName}Id {
return value as {EntityName}Id;
}
// === Validation Schema ===
const {EntityName}PropsSchema = z.object({
// Properties with Zod validation
});
type {EntityName}Props = z.infer<typeof {EntityName}PropsSchema>;
// === Result Type ===
type Result<T, E = Error> =
| { success: true; value: T }
| { success: false; error: E };
// === Entity ===
export class {EntityName} {
private constructor(
private readonly _id: {EntityName}Id,
private readonly props: {EntityName}Props
) {}
// Factory method
static create(
id: {EntityName}Id,
props: {EntityName}Props
): Result<{EntityName}> {
const validation = {EntityName}PropsSchema.safeParse(props);
if (!validation.success) {
return {
success: false,
error: new Error(validation.error.errors.map(e => e.message).join(', ')),
};
}
// Enforce invariants here
return { success: true, value: new {EntityName}(id, validation.data) };
}
// Identity
get id(): {EntityName}Id {
return this._id;
}
// Property accessors (read-only)
// Identity-based equality
equals(other: {EntityName}): boolean {
return this._id === other._id;
}
}
Step 4: Generate Test File
Create {entity-name}.test.ts:
typescript
import { describe, it, expect } from 'vitest';
import { {EntityName}, create{EntityName}Id } from './{entity-name}';
describe('{EntityName}', () => {
const validId = create{EntityName}Id('test-id-1');
describe('create', () => {
it('should create with valid properties', () => {
const result = {EntityName}.create(validId, {
// valid props
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.id).toBe(validId);
}
});
it('should fail with invalid properties', () => {
const result = {EntityName}.create(validId, {
// invalid props
});
expect(result.success).toBe(false);
});
});
describe('equality', () => {
it('should be equal when IDs match', () => {
const entity1 = {EntityName}.create(validId, { /* props */ });
const entity2 = {EntityName}.create(validId, { /* props */ });
if (entity1.success && entity2.success) {
expect(entity1.value.equals(entity2.value)).toBe(true);
}
});
it('should not be equal when IDs differ', () => {
const otherId = create{EntityName}Id('test-id-2');
const entity1 = {EntityName}.create(validId, { /* props */ });
const entity2 = {EntityName}.create(otherId, { /* props */ });
if (entity1.success && entity2.success) {
expect(entity1.value.equals(entity2.value)).toBe(false);
}
});
});
});
Step 5: Explain the Pattern
After generating, briefly explain:
- •Branded ID — prevents mixing up IDs from different entities
- •Private constructor — forces creation through the factory method
- •Factory method with Result — validates invariants before creating
- •Identity-based equality — two entities are the same if their IDs match, regardless of property values
- •Zod validation — enforces property constraints at the boundary
Customization Notes
- •Replace placeholder properties with the student's actual properties
- •Add specific invariant checks in the
createmethod - •Add domain-specific methods as needed
- •Keep the entity focused — if it's getting large, consider extracting Value Objects