135 lines
3.4 KiB
Markdown
135 lines
3.4 KiB
Markdown
# Phase 1: Shared Domain — Building Blocks
|
|
|
|
## Objetivo
|
|
Crear las clases base que TODOS los módulos usarán. Esto es el cimiento.
|
|
|
|
## Result.ts
|
|
```typescript
|
|
// Discriminated union, no classes
|
|
type ResultOk<T> = { readonly ok: true; readonly value: T };
|
|
type ResultErr<E> = { readonly ok: false; readonly error: E };
|
|
export type Result<T, E = Error> = ResultOk<T> | ResultErr<E>;
|
|
|
|
export const Ok = <T>(value: T): Result<T, never> => ({ ok: true, value });
|
|
export const Err = <E>(error: E): Result<never, E> => ({ ok: false, error });
|
|
export function isOk<T, E>(r: Result<T, E>): r is ResultOk<T> { return r.ok; }
|
|
export function isErr<T, E>(r: Result<T, E>): r is ResultErr<E> { return !r.ok; }
|
|
```
|
|
|
|
## UniqueId.ts
|
|
```typescript
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
export class UniqueId {
|
|
private constructor(private readonly value: string) {}
|
|
static create(): UniqueId { return new UniqueId(uuidv4()); }
|
|
static from(value: string): UniqueId { return new UniqueId(value); }
|
|
toString(): string { return this.value; }
|
|
equals(other?: UniqueId): boolean {
|
|
if (!other) return false;
|
|
return this.value === other.value;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Entity.ts
|
|
```typescript
|
|
export abstract class Entity<T> {
|
|
protected readonly _id: UniqueId;
|
|
protected props: T;
|
|
|
|
constructor(props: T, id?: UniqueId) {
|
|
this._id = id ?? UniqueId.create();
|
|
this.props = props;
|
|
}
|
|
|
|
get id(): UniqueId { return this._id; }
|
|
|
|
equals(other?: Entity<T>): boolean {
|
|
if (!other) return false;
|
|
return this._id.equals(other._id);
|
|
}
|
|
}
|
|
```
|
|
|
|
## AggregateRoot.ts
|
|
```typescript
|
|
export abstract class AggregateRoot<T> extends Entity<T> {
|
|
private _domainEvents: DomainEvent[] = [];
|
|
|
|
get domainEvents(): ReadonlyArray<DomainEvent> {
|
|
return this._domainEvents;
|
|
}
|
|
|
|
protected addDomainEvent(event: DomainEvent): void {
|
|
this._domainEvents.push(event);
|
|
}
|
|
|
|
clearEvents(): DomainEvent[] {
|
|
const events = [...this._domainEvents];
|
|
this._domainEvents = [];
|
|
return events;
|
|
}
|
|
}
|
|
```
|
|
|
|
## ValueObject.ts
|
|
```typescript
|
|
export abstract class ValueObject<T> {
|
|
protected readonly props: T;
|
|
|
|
constructor(props: T) {
|
|
this.props = Object.freeze(props);
|
|
}
|
|
|
|
equals(other?: ValueObject<T>): boolean {
|
|
if (!other) return false;
|
|
return JSON.stringify(this.props) === JSON.stringify(other.props);
|
|
}
|
|
}
|
|
```
|
|
|
|
## DomainEvent.ts
|
|
```typescript
|
|
export interface DomainEvent {
|
|
readonly eventId: string;
|
|
readonly eventName: string;
|
|
readonly aggregateId: string;
|
|
readonly occurredOn: Date;
|
|
readonly payload: Record<string, unknown>;
|
|
}
|
|
```
|
|
|
|
## UseCase.ts
|
|
```typescript
|
|
export interface UseCase<TRequest, TResponse, TError = Error> {
|
|
execute(request: TRequest): Promise<Result<TResponse, TError>>;
|
|
}
|
|
```
|
|
|
|
## EventBus.ts + EventHandler.ts
|
|
```typescript
|
|
// EventBus.ts
|
|
export interface EventBus {
|
|
publish(event: DomainEvent): Promise<void>;
|
|
subscribe(eventName: string, handler: EventHandler): void;
|
|
}
|
|
|
|
// EventHandler.ts
|
|
export interface EventHandler {
|
|
handle(event: DomainEvent): Promise<void>;
|
|
}
|
|
```
|
|
|
|
## Tests requeridos (mínimo)
|
|
1. Result: Ok crea value accesible, Err crea error accesible, isOk/isErr discriminan
|
|
2. UniqueId: create genera string válido, equals funciona, from preserva valor
|
|
3. Entity: equals compara por id (no por props)
|
|
4. ValueObject: equals compara por props, props son inmutables
|
|
|
|
## IMPORTANTE
|
|
- Estos archivos NO importan NADA externo excepto 'uuid'
|
|
- NO usar decorators
|
|
- NO usar classes abstractas complicadas — mantener simple
|
|
- Cada archivo exporta UNA cosa principal
|