diff --git a/src/modules/feeds/application/eventHandlers/CardAddedToLibraryEventHandler.ts b/src/modules/feeds/application/eventHandlers/CardAddedToLibraryEventHandler.ts index 6cc3c534..fc042f06 100644 --- a/src/modules/feeds/application/eventHandlers/CardAddedToLibraryEventHandler.ts +++ b/src/modules/feeds/application/eventHandlers/CardAddedToLibraryEventHandler.ts @@ -1,10 +1,12 @@ import { CardAddedToLibraryEvent } from '../../../cards/domain/events/CardAddedToLibraryEvent'; import { IFeedService } from '../ports/IFeedService'; +import { IEventHandler } from '../../../../shared/application/events/IEventSubscriber'; +import { Result, ok, err } from '../../../../shared/core/Result'; -export class CardAddedToLibraryEventHandler { +export class CardAddedToLibraryEventHandler implements IEventHandler { constructor(private feedService: IFeedService) {} - async handle(event: CardAddedToLibraryEvent): Promise { + async handle(event: CardAddedToLibraryEvent): Promise> { try { const result = await this.feedService.processCardAddedToLibrary(event); @@ -13,12 +15,16 @@ export class CardAddedToLibraryEventHandler { 'Error processing CardAddedToLibraryEvent in feeds:', result.error, ); + return err(result.error); } + + return ok(undefined); } catch (error) { console.error( 'Unexpected error handling CardAddedToLibraryEvent in feeds:', error, ); + return err(error as Error); } } } diff --git a/src/modules/notifications/application/eventHandlers/CardAddedToLibraryEventHandler.ts b/src/modules/notifications/application/eventHandlers/CardAddedToLibraryEventHandler.ts index 67be703c..acf3a57e 100644 --- a/src/modules/notifications/application/eventHandlers/CardAddedToLibraryEventHandler.ts +++ b/src/modules/notifications/application/eventHandlers/CardAddedToLibraryEventHandler.ts @@ -1,10 +1,12 @@ import { CardAddedToLibraryEvent } from '../../../cards/domain/events/CardAddedToLibraryEvent'; import { INotificationService } from '../ports/INotificationService'; +import { IEventHandler } from '../../../../shared/application/events/IEventSubscriber'; +import { Result, ok, err } from '../../../../shared/core/Result'; -export class CardAddedToLibraryEventHandler { +export class CardAddedToLibraryEventHandler implements IEventHandler { constructor(private notificationService: INotificationService) {} - async handle(event: CardAddedToLibraryEvent): Promise { + async handle(event: CardAddedToLibraryEvent): Promise> { try { const result = await this.notificationService.processCardAddedToLibrary( event, @@ -15,12 +17,16 @@ export class CardAddedToLibraryEventHandler { 'Error processing CardAddedToLibraryEvent in notifications:', result.error, ); + return err(result.error); } + + return ok(undefined); } catch (error) { console.error( 'Unexpected error handling CardAddedToLibraryEvent in notifications:', error, ); + return err(error as Error); } } } diff --git a/src/shared/application/events/IEventPublisher.ts b/src/shared/application/events/IEventPublisher.ts new file mode 100644 index 00000000..229862c2 --- /dev/null +++ b/src/shared/application/events/IEventPublisher.ts @@ -0,0 +1,6 @@ +import { IDomainEvent } from '../../domain/events/IDomainEvent'; +import { Result } from '../../core/Result'; + +export interface IEventPublisher { + publishEvents(events: IDomainEvent[]): Promise>; +} diff --git a/src/shared/application/events/IEventSubscriber.ts b/src/shared/application/events/IEventSubscriber.ts new file mode 100644 index 00000000..50b882ba --- /dev/null +++ b/src/shared/application/events/IEventSubscriber.ts @@ -0,0 +1,16 @@ +import { IDomainEvent } from '../../domain/events/IDomainEvent'; +import { Result } from '../../core/Result'; + +export interface IEventHandler { + handle(event: T): Promise>; +} + +export interface IEventSubscriber { + subscribe( + eventType: string, + handler: IEventHandler + ): Promise; + + start(): Promise; + stop(): Promise; +} diff --git a/src/shared/core/UseCase.ts b/src/shared/core/UseCase.ts index f39b328b..a2fe407e 100644 --- a/src/shared/core/UseCase.ts +++ b/src/shared/core/UseCase.ts @@ -1,3 +1,32 @@ +import { IEventPublisher } from '../application/events/IEventPublisher'; +import { DomainEvents } from '../domain/events/DomainEvents'; +import { AggregateRoot } from '../domain/AggregateRoot'; +import { Result, ok } from './Result'; + export interface UseCase { execute(request?: IRequest): Promise | IResponse; } + +export abstract class BaseUseCase implements UseCase { + constructor(protected eventPublisher: IEventPublisher) {} + + abstract execute(request?: IRequest): Promise | IResponse; + + protected async publishEventsForAggregate( + aggregate: AggregateRoot + ): Promise> { + const events = DomainEvents.getEventsForAggregate(aggregate.id); + + if (events.length === 0) { + return ok(undefined); + } + + const publishResult = await this.eventPublisher.publishEvents(events); + + if (publishResult.isOk()) { + DomainEvents.clearEventsForAggregate(aggregate.id); + } + + return publishResult; + } +} diff --git a/src/shared/domain/events/DomainEvents.ts b/src/shared/domain/events/DomainEvents.ts index 16a6384d..0e07db38 100644 --- a/src/shared/domain/events/DomainEvents.ts +++ b/src/shared/domain/events/DomainEvents.ts @@ -66,6 +66,19 @@ export class DomainEvents { } } + public static getEventsForAggregate(id: UniqueEntityID): IDomainEvent[] { + const aggregate = this.findMarkedAggregateByID(id); + return aggregate ? [...aggregate.domainEvents] : []; + } + + public static clearEventsForAggregate(id: UniqueEntityID): void { + const aggregate = this.findMarkedAggregateByID(id); + if (aggregate) { + aggregate.clearEvents(); + this.removeAggregateFromMarkedDispatchList(aggregate); + } + } + public static register( callback: (event: IDomainEvent) => void, eventClassName: string,