From ffb1238800334d07f2c9f2066fe613b87122823d Mon Sep 17 00:00:00 2001 From: Gabriele Toselli Date: Sat, 30 Mar 2024 19:30:48 +0100 Subject: [PATCH] feat(core): add getByIdOrThrow on AggregateRepo --- .changeset/spotty-moles-cover.md | 5 +++++ packages/ddd-toolkit/src/errors.ts | 6 ++++++ .../src/repo/mongo-aggregate-repo.ts | 18 +++++++++++++++--- .../mongo-aggregate-repo.integration-spec.ts | 19 ++++++++++++++++++- 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 .changeset/spotty-moles-cover.md diff --git a/.changeset/spotty-moles-cover.md b/.changeset/spotty-moles-cover.md new file mode 100644 index 0000000..aaa8f1b --- /dev/null +++ b/.changeset/spotty-moles-cover.md @@ -0,0 +1,5 @@ +--- +"@fizzbuds/ddd-toolkit": patch +--- + +add getByIdOrThrow to IAggregateRepo diff --git a/packages/ddd-toolkit/src/errors.ts b/packages/ddd-toolkit/src/errors.ts index 7ce92eb..173f340 100644 --- a/packages/ddd-toolkit/src/errors.ts +++ b/packages/ddd-toolkit/src/errors.ts @@ -15,3 +15,9 @@ export class RepoHookError extends Error { super(message); } } + +export class AggregateNotFoundError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/packages/ddd-toolkit/src/repo/mongo-aggregate-repo.ts b/packages/ddd-toolkit/src/repo/mongo-aggregate-repo.ts index 1f0561e..6c46112 100644 --- a/packages/ddd-toolkit/src/repo/mongo-aggregate-repo.ts +++ b/packages/ddd-toolkit/src/repo/mongo-aggregate-repo.ts @@ -1,14 +1,15 @@ import { IRepoHooks } from './repo-hooks'; -import { Collection, Document, MongoClient } from 'mongodb'; +import { Collection, Document, MongoClient, WithId } from 'mongodb'; import { ISerializer } from './serializer.interface'; import { merge } from 'lodash'; -import { DuplicatedIdError, OptimisticLockError, RepoHookError } from '../errors'; +import { AggregateNotFoundError, DuplicatedIdError, OptimisticLockError, RepoHookError } from '../errors'; import { ILogger } from '../logger'; import { IInit } from '../init.interface'; export interface IAggregateRepo { // TODO add id as a generic type getById: (id: string) => Promise | null>; + getByIdOrThrow: (id: string) => Promise>; save: (aggregate: A) => Promise; } @@ -23,6 +24,7 @@ const MONGODB_UNIQUE_INDEX_CONSTRAINT_ERROR = 11000; export class MongoAggregateRepo implements IAggregateRepo, IInit { protected readonly collection: Collection; + constructor( protected readonly serializer: ISerializer, protected readonly mongoClient: MongoClient, @@ -94,11 +96,21 @@ export class MongoAggregateRepo implements IAggreg } } - // TODO evaluate to implement getOrThrow async getById(id: string): Promise | null> { const aggregateModel = await this.collection.findOne({ id: id } as any); this.logger.debug(`Retrieving aggregate ${id}. Found: ${JSON.stringify(aggregateModel)}`); if (!aggregateModel) return null; + + return this.modelToAggregateWithVersion(aggregateModel); + } + + async getByIdOrThrow(id: string): Promise> { + const aggregate = await this.getById(id); + if (!aggregate) throw new AggregateNotFoundError(`Aggregate ${id} not found.`); + return aggregate; + } + + private modelToAggregateWithVersion(aggregateModel: WithId): WithVersion { const aggregate = this.serializer.modelToAggregate(aggregateModel as AM); return merge(aggregate, { __version: aggregateModel.__version }); } diff --git a/packages/ddd-toolkit/src/repo/tests/mongo-aggregate-repo.integration-spec.ts b/packages/ddd-toolkit/src/repo/tests/mongo-aggregate-repo.integration-spec.ts index 6e2a440..22d60a2 100644 --- a/packages/ddd-toolkit/src/repo/tests/mongo-aggregate-repo.integration-spec.ts +++ b/packages/ddd-toolkit/src/repo/tests/mongo-aggregate-repo.integration-spec.ts @@ -2,6 +2,7 @@ import { MongoAggregateRepo } from '../mongo-aggregate-repo'; import { MongoMemoryReplSet } from 'mongodb-memory-server'; import { MongoClient } from 'mongodb'; import { TestAggregate, TestModel, TestSerializer } from './example.serializer'; +import { AggregateNotFoundError } from '../../errors'; describe('MongoAggregateRepo MongoDB Integration', () => { let mongodb: MongoMemoryReplSet; @@ -39,7 +40,7 @@ describe('MongoAggregateRepo MongoDB Integration', () => { }); describe('Save and Get', () => { - describe('Given an aggregate', () => { + describe('Given an existing aggregate', () => { describe('When saving', () => { const id1 = 'id1'; beforeEach(async () => { @@ -54,6 +55,22 @@ describe('MongoAggregateRepo MongoDB Integration', () => { }); }); }); + + describe('Given an un-existing aggregate', () => { + describe('When getById', () => { + it('should return null', async () => { + expect(await aggregateRepo.getById('not-existing-id')).toBeNull(); + }); + }); + + describe('When getByIdOrThrow', () => { + it('should throw AggregateNotFoundError', async () => { + await expect(() => aggregateRepo.getByIdOrThrow('not-existing-id')).rejects.toThrowError( + AggregateNotFoundError, + ); + }); + }); + }); }); describe('Optimistic Lock', () => {