refactor: UnitOfWork is now a global module

This commit is contained in:
user
2021-09-21 20:39:19 +02:00
parent f822ab3408
commit 0edc57b4f4
11 changed files with 61 additions and 34 deletions

View File

@@ -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,

View File

@@ -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 {}

View File

@@ -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);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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>;
}

View File

@@ -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();

View File

@@ -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';

View File

@@ -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');

View File

@@ -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 {

View File

@@ -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],
};

View File

@@ -5,8 +5,8 @@
config:
target: http://localhost:3000/v1
phases:
- duration: 1
arrivalRate: 50
- duration: 2
arrivalRate: 150
plugins:
faker:
locale: en