fase(1): shared domain building blocks
This commit is contained in:
7
src/shared/application/EventBus.ts
Normal file
7
src/shared/application/EventBus.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { DomainEvent } from '../domain/DomainEvent';
|
||||
import { EventHandler } from './EventHandler';
|
||||
|
||||
export interface EventBus {
|
||||
publish(event: DomainEvent): Promise<void>;
|
||||
subscribe(eventName: string, handler: EventHandler): void;
|
||||
}
|
||||
5
src/shared/application/EventHandler.ts
Normal file
5
src/shared/application/EventHandler.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { DomainEvent } from '../domain/DomainEvent';
|
||||
|
||||
export interface EventHandler {
|
||||
handle(event: DomainEvent): Promise<void>;
|
||||
}
|
||||
5
src/shared/application/UseCase.ts
Normal file
5
src/shared/application/UseCase.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Result } from '../domain/Result';
|
||||
|
||||
export interface UseCase<TRequest, TResponse, TError = Error> {
|
||||
execute(request: TRequest): Promise<Result<TResponse, TError>>;
|
||||
}
|
||||
3
src/shared/application/index.ts
Normal file
3
src/shared/application/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './UseCase';
|
||||
export * from './EventBus';
|
||||
export * from './EventHandler';
|
||||
25
src/shared/domain/AggregateRoot.ts
Normal file
25
src/shared/domain/AggregateRoot.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Entity } from './Entity';
|
||||
import { DomainEvent } from './DomainEvent';
|
||||
import { UniqueId } from './UniqueId';
|
||||
|
||||
export abstract class AggregateRoot<T> extends Entity<T> {
|
||||
private _domainEvents: DomainEvent[] = [];
|
||||
|
||||
constructor(props: T, id?: UniqueId) {
|
||||
super(props, id);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
7
src/shared/domain/DomainEvent.ts
Normal file
7
src/shared/domain/DomainEvent.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface DomainEvent {
|
||||
readonly eventId: string;
|
||||
readonly eventName: string;
|
||||
readonly aggregateId: string;
|
||||
readonly occurredOn: Date;
|
||||
readonly payload: Record<string, unknown>;
|
||||
}
|
||||
18
src/shared/domain/Entity.ts
Normal file
18
src/shared/domain/Entity.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { UniqueId } from './UniqueId';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
8
src/shared/domain/Result.ts
Normal file
8
src/shared/domain/Result.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
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; }
|
||||
15
src/shared/domain/UniqueId.ts
Normal file
15
src/shared/domain/UniqueId.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
export class UniqueId {
|
||||
private constructor(private readonly value: string) {}
|
||||
|
||||
static create(): UniqueId { return new UniqueId(randomUUID()); }
|
||||
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;
|
||||
}
|
||||
}
|
||||
12
src/shared/domain/ValueObject.ts
Normal file
12
src/shared/domain/ValueObject.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export abstract class ValueObject<T> {
|
||||
protected readonly props: T;
|
||||
|
||||
constructor(props: T) {
|
||||
this.props = Object.freeze(props) as T;
|
||||
}
|
||||
|
||||
equals(other?: ValueObject<T>): boolean {
|
||||
if (!other) return false;
|
||||
return JSON.stringify(this.props) === JSON.stringify(other.props);
|
||||
}
|
||||
}
|
||||
6
src/shared/domain/index.ts
Normal file
6
src/shared/domain/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './Result';
|
||||
export * from './UniqueId';
|
||||
export * from './Entity';
|
||||
export * from './AggregateRoot';
|
||||
export * from './ValueObject';
|
||||
export * from './DomainEvent';
|
||||
Reference in New Issue
Block a user