refactor: UnitOfWork is now a global module
This commit is contained in:
@@ -7,6 +7,7 @@ import { WalletModule } from '@modules/wallet/wallet.module';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
import { join } from 'path';
|
||||
import { typeormConfig } from './infrastructure/configs/ormconfig';
|
||||
import { UnitOfWorkModule } from './infrastructure/database/unit-of-work/unit-of-work.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -15,6 +16,7 @@ import { typeormConfig } from './infrastructure/configs/ormconfig';
|
||||
GraphQLModule.forRoot({
|
||||
autoSchemaFile: join(process.cwd(), 'src/infrastructure/schema.gql'),
|
||||
}),
|
||||
UnitOfWorkModule,
|
||||
NestEventModule,
|
||||
ConsoleModule,
|
||||
UserModule,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { UnitOfWork } from './unit-of-work';
|
||||
|
||||
const unitOfWorkSingleton = new UnitOfWork();
|
||||
|
||||
const unitOfWorkSingletonProvider = {
|
||||
provide: UnitOfWork,
|
||||
useFactory: () => unitOfWorkSingleton,
|
||||
};
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [],
|
||||
providers: [unitOfWorkSingletonProvider],
|
||||
exports: [UnitOfWork],
|
||||
})
|
||||
export class UnitOfWorkModule {}
|
||||
@@ -3,20 +3,22 @@ import { UserOrmEntity } from '@modules/user/database/user.orm-entity';
|
||||
import { UserRepository } from '@modules/user/database/user.repository';
|
||||
import { WalletOrmEntity } from '@modules/wallet/database/wallet.orm-entity';
|
||||
import { WalletRepository } from '@modules/wallet/database/wallet.repository';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class UnitOfWork extends TypeormUnitOfWork {
|
||||
// Add new repositories below to use this generic UnitOfWork
|
||||
|
||||
// Convert TypeOrm Repository to a Domain Repository
|
||||
getUserRepository(correlationId: string): UserRepository {
|
||||
return new UserRepository(
|
||||
UnitOfWork.getOrmRepository(UserOrmEntity, correlationId),
|
||||
this.getOrmRepository(UserOrmEntity, correlationId),
|
||||
).setCorrelationId(correlationId);
|
||||
}
|
||||
|
||||
getWalletRepository(correlationId: string): WalletRepository {
|
||||
return new WalletRepository(
|
||||
UnitOfWork.getOrmRepository(WalletOrmEntity, correlationId),
|
||||
this.getOrmRepository(WalletOrmEntity, correlationId),
|
||||
).setCorrelationId(correlationId);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IsolationLevel } from 'typeorm/driver/types/IsolationLevel';
|
||||
import { UnitOfWorkPort } from '../ports/unit-of-work.port';
|
||||
import { ID } from '../value-objects/id.value-object';
|
||||
import { Command } from './command.base';
|
||||
@@ -7,10 +8,15 @@ export abstract class CommandHandler<UnitOfWork extends UnitOfWorkPort> {
|
||||
|
||||
protected abstract execute(command: Command): Promise<ID>;
|
||||
|
||||
async executeUnitOfWork(command: Command): Promise<ID> {
|
||||
async executeUnitOfWork(
|
||||
command: Command,
|
||||
options?: { isolationLevel: IsolationLevel },
|
||||
): Promise<ID> {
|
||||
this.unitOfWork.init(command.correlationId);
|
||||
return this.unitOfWork.execute(command.correlationId, async () =>
|
||||
this.execute(command),
|
||||
return this.unitOfWork.execute(
|
||||
command.correlationId,
|
||||
async () => this.execute(command),
|
||||
options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export interface UnitOfWorkPort {
|
||||
init(correlationId: string): void;
|
||||
execute<T>(correlationId: string, callback: () => Promise<T>): Promise<T>;
|
||||
execute<T>(
|
||||
correlationId: string,
|
||||
callback: () => Promise<T>,
|
||||
options?: unknown,
|
||||
): Promise<T>;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { UnitOfWorkPort } from '@src/libs/ddd/domain/ports/unit-of-work.port';
|
||||
import { EntityTarget, getConnection, QueryRunner, Repository } from 'typeorm';
|
||||
import { IsolationLevel } from 'typeorm/driver/types/IsolationLevel';
|
||||
|
||||
export class TypeormUnitOfWork implements UnitOfWorkPort {
|
||||
init(correlationId: string): void {
|
||||
return TypeormUnitOfWork.init(correlationId);
|
||||
}
|
||||
|
||||
execute<T>(correlationId: string, callback: () => Promise<T>): Promise<T> {
|
||||
return TypeormUnitOfWork.execute(correlationId, callback);
|
||||
}
|
||||
|
||||
private static queryRunners: Map<string, QueryRunner> = new Map();
|
||||
private queryRunners: Map<string, QueryRunner> = new Map();
|
||||
|
||||
/**
|
||||
* Creates a connection pool with a specified ID.
|
||||
*/
|
||||
static init(correlationId: string): void {
|
||||
init(correlationId: string): void {
|
||||
if (!correlationId) {
|
||||
throw new Error('Correlation ID must be provided');
|
||||
}
|
||||
@@ -24,7 +17,7 @@ export class TypeormUnitOfWork implements UnitOfWorkPort {
|
||||
this.queryRunners.set(correlationId, queryRunner);
|
||||
}
|
||||
|
||||
static getQueryRunner(correlationId: string): QueryRunner {
|
||||
getQueryRunner(correlationId: string): QueryRunner {
|
||||
const queryRunner = this.queryRunners.get(correlationId);
|
||||
if (!queryRunner) {
|
||||
throw new Error(
|
||||
@@ -34,7 +27,7 @@ export class TypeormUnitOfWork implements UnitOfWorkPort {
|
||||
return queryRunner;
|
||||
}
|
||||
|
||||
static getOrmRepository<Entity>(
|
||||
getOrmRepository<Entity>(
|
||||
entity: EntityTarget<Entity>,
|
||||
correlationId: string,
|
||||
): Repository<Entity> {
|
||||
@@ -46,16 +39,16 @@ export class TypeormUnitOfWork implements UnitOfWorkPort {
|
||||
* Execute a UnitOfWork.
|
||||
* Database operations wrapped in a UnitOfWork will execute in a single
|
||||
* transactional operation, so everything gets saved or nothing.
|
||||
* Make sure to generate and inject correct repositories for this to work.
|
||||
*/
|
||||
static async execute<T>(
|
||||
async execute<T>(
|
||||
correlationId: string,
|
||||
callback: () => Promise<T>,
|
||||
options?: { isolationLevel: IsolationLevel },
|
||||
): Promise<T> {
|
||||
const logger = new Logger(`${this.name}:${correlationId}`);
|
||||
const logger = new Logger(`${this.constructor.name}:${correlationId}`);
|
||||
logger.debug(`[Starting transaction]`);
|
||||
const queryRunner = this.getQueryRunner(correlationId);
|
||||
await queryRunner.startTransaction('SERIALIZABLE');
|
||||
await queryRunner.startTransaction(options?.isolationLevel);
|
||||
let result: T;
|
||||
try {
|
||||
result = await callback();
|
||||
@@ -79,7 +72,7 @@ export class TypeormUnitOfWork implements UnitOfWorkPort {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static async finish(correlationId: string): Promise<void> {
|
||||
private async finish(correlationId: string): Promise<void> {
|
||||
const queryRunner = this.getQueryRunner(correlationId);
|
||||
try {
|
||||
await queryRunner.release();
|
||||
|
||||
@@ -3,7 +3,7 @@ import { UserRepositoryPort } from '@modules/user/database/user.repository.port'
|
||||
import { ConflictException } from '@libs/exceptions';
|
||||
import { Address } from '@modules/user/domain/value-objects/address.value-object';
|
||||
import { Email } from '@modules/user/domain/value-objects/email.value-object';
|
||||
import { UnitOfWork } from '@src/infrastructure/database/unit-of-work';
|
||||
import { UnitOfWork } from '@src/infrastructure/database/unit-of-work/unit-of-work';
|
||||
import { CommandHandler } from '@src/libs/ddd/domain/base-classes/command-handler.base';
|
||||
import { CreateUserCommand } from './create-user.command';
|
||||
import { UserEntity } from '../../domain/entities/user.entity';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Logger, Provider } from '@nestjs/common';
|
||||
import { UnitOfWork } from '@src/infrastructure/database/unit-of-work';
|
||||
import { UnitOfWork } from '@src/infrastructure/database/unit-of-work/unit-of-work';
|
||||
import { UserRepository } from './database/user.repository';
|
||||
import { CreateUserService } from './commands/create-user/create-user.service';
|
||||
import { DeleteUserService } from './commands/delete-user/delete-user.service';
|
||||
@@ -16,10 +16,10 @@ export const createUserSymbol = Symbol('createUser');
|
||||
|
||||
export const createUserProvider: Provider = {
|
||||
provide: createUserSymbol,
|
||||
useFactory: (): CreateUserService => {
|
||||
return new CreateUserService(new UnitOfWork());
|
||||
useFactory: (unitOfWork: UnitOfWork): CreateUserService => {
|
||||
return new CreateUserService(unitOfWork);
|
||||
},
|
||||
inject: [UserRepository],
|
||||
inject: [UnitOfWork],
|
||||
};
|
||||
|
||||
export const removeUserSymbol = Symbol('removeUser');
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UserCreatedDomainEvent } from '@modules/user/domain/events/user-created
|
||||
import { WalletRepositoryPort } from '@modules/wallet/database/wallet.repository.port';
|
||||
import { DomainEventHandler } from '@libs/ddd/domain/domain-events';
|
||||
import { UUID } from '@libs/ddd/domain/value-objects/uuid.value-object';
|
||||
import { UnitOfWork } from '@src/infrastructure/database/unit-of-work';
|
||||
import { UnitOfWork } from '@src/infrastructure/database/unit-of-work/unit-of-work';
|
||||
import { WalletEntity } from '../../domain/entities/wallet.entity';
|
||||
|
||||
export class CreateWalletWhenUserIsCreatedDomainEventHandler extends DomainEventHandler {
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
import { UnitOfWork } from '@src/infrastructure/database/unit-of-work';
|
||||
import { UnitOfWork } from '@src/infrastructure/database/unit-of-work/unit-of-work';
|
||||
import { CreateWalletWhenUserIsCreatedDomainEventHandler } from './application/event-handlers/create-wallet-when-user-is-created.domain-event-handler';
|
||||
|
||||
export const createWalletWhenUserIsCreatedProvider: Provider = {
|
||||
provide: CreateWalletWhenUserIsCreatedDomainEventHandler,
|
||||
useFactory: (): CreateWalletWhenUserIsCreatedDomainEventHandler => {
|
||||
useFactory: (
|
||||
unitOfWork: UnitOfWork,
|
||||
): CreateWalletWhenUserIsCreatedDomainEventHandler => {
|
||||
const eventHandler = new CreateWalletWhenUserIsCreatedDomainEventHandler(
|
||||
new UnitOfWork(),
|
||||
unitOfWork,
|
||||
);
|
||||
eventHandler.listen();
|
||||
return eventHandler;
|
||||
},
|
||||
inject: [UnitOfWork],
|
||||
};
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
config:
|
||||
target: http://localhost:3000/v1
|
||||
phases:
|
||||
- duration: 1
|
||||
arrivalRate: 50
|
||||
- duration: 2
|
||||
arrivalRate: 150
|
||||
plugins:
|
||||
faker:
|
||||
locale: en
|
||||
|
||||
Reference in New Issue
Block a user