Files
Autonomous-Bug-Explorer/.ralph/specs/phase-01-shared-domain.md

3.4 KiB

Phase 1: Shared Domain — Building Blocks

Objetivo

Crear las clases base que TODOS los módulos usarán. Esto es el cimiento.

Result.ts

// 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

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

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

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

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

export interface DomainEvent {
  readonly eventId: string;
  readonly eventName: string;
  readonly aggregateId: string;
  readonly occurredOn: Date;
  readonly payload: Record<string, unknown>;
}

UseCase.ts

export interface UseCase<TRequest, TResponse, TError = Error> {
  execute(request: TRequest): Promise<Result<TResponse, TError>>;
}

EventBus.ts + EventHandler.ts

// 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