From 7671fefb7f57ff4f16a3c445e51cd0de530b9c5f Mon Sep 17 00:00:00 2001 From: Jose Carlos Date: Sun, 22 Feb 2026 12:48:11 +0100 Subject: [PATCH] refactor: inject IRepository in PostgresProjector (DIP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D2.1 from Phase 2 roadmap. Violation: PostgresProjector injected the concrete PostgresRepository class instead of the IRepository interface, violating the Dependency Inversion Principle (Evans, Blue Book — interfaces in Domain, impls in Infrastructure; Martin, Clean Architecture ch.11). Consequence: The projector was untestable without a real database connection, and a compile-time dependency on a concrete infrastructure class crept into application wiring. Fix: - Add save(dto: ITransactionReadDTO): Promise to IRepository (keeps TypeORM types out of the Domain interface) - PostgresRepository.save() now maps ITransactionReadDTO → Transactions entity internally; infrastructure detail stays in Infrastructure - PostgresProjector now depends only on IRepository and builds the DTO inline from the domain event - Remove dead Transactions.fromCreated() factory (no longer called) - Update InMemoryProjector test double to build DTO inline instead --- src/Billing/Transaction/Domain/Repository.ts | 1 + .../ReadModel/Mapping/Transactions.ts | 14 -------------- .../ReadModel/Projections/PostgresProjector.ts | 13 +++++++++---- .../ReadModel/Repository/PostgresRepository.ts | 10 ++++++++-- .../Infrastructure/InMemoryProjector.ts | 11 +++++++---- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Billing/Transaction/Domain/Repository.ts b/src/Billing/Transaction/Domain/Repository.ts index 2ffcf71..ee30e97 100644 --- a/src/Billing/Transaction/Domain/Repository.ts +++ b/src/Billing/Transaction/Domain/Repository.ts @@ -16,4 +16,5 @@ export interface ITransactionReadDTO { export default interface IRepository { get(id: TransactionId): Promise; + save(dto: ITransactionReadDTO): Promise; } diff --git a/src/Billing/Transaction/Infrastructure/ReadModel/Mapping/Transactions.ts b/src/Billing/Transaction/Infrastructure/ReadModel/Mapping/Transactions.ts index d9612e4..73c6a3e 100644 --- a/src/Billing/Transaction/Infrastructure/ReadModel/Mapping/Transactions.ts +++ b/src/Billing/Transaction/Infrastructure/ReadModel/Mapping/Transactions.ts @@ -1,22 +1,8 @@ -import type TransactionWasCreated from "@Transaction/Domain/Events/TransactionWasCreated"; import { Column, Entity, PrimaryColumn } from "typeorm"; @Entity() export class Transactions { - public static fromCreated(event: TransactionWasCreated): Transactions { - - const instance = new Transactions(); - - instance.uuid = event.aggregateId; - instance.product = event.product; - instance.priceAmount = Number(event.amount); - instance.priceCurrency = event.currency; - instance.createdAt = new Date(); - - return instance; - } - @PrimaryColumn("uuid") public uuid: string; diff --git a/src/Billing/Transaction/Infrastructure/ReadModel/Projections/PostgresProjector.ts b/src/Billing/Transaction/Infrastructure/ReadModel/Projections/PostgresProjector.ts index a5f7d0a..b4cdced 100644 --- a/src/Billing/Transaction/Infrastructure/ReadModel/Projections/PostgresProjector.ts +++ b/src/Billing/Transaction/Infrastructure/ReadModel/Projections/PostgresProjector.ts @@ -1,20 +1,25 @@ import TransactionWasCreated from "@Transaction/Domain/Events/TransactionWasCreated"; +import IRepository from "@Transaction/Domain/Repository"; import { EventSourcing } from "hollywood-js"; import { inject, injectable } from "inversify"; -import { Transactions } from "../Mapping/Transactions"; -import PostgresRepository from "../Repository/PostgresRepository"; @injectable() export default class PostgresProjector extends EventSourcing.EventSubscriber { constructor( - @inject("infrastructure.transaction.readModel.repository") private readonly readModel: PostgresRepository, + @inject("infrastructure.transaction.readModel.repository") private readonly readModel: IRepository, ) { super(); } protected async onTransactionWasCreated(event: TransactionWasCreated): Promise { try { - await this.readModel.save(Transactions.fromCreated(event)); + await this.readModel.save({ + createdAt: new Date(), + priceAmount: Number(event.amount), + priceCurrency: event.currency, + product: event.product, + uuid: event.aggregateId, + }); } catch (error) { throw new Error("Error in Transaction Projector save: " + error.message); } diff --git a/src/Billing/Transaction/Infrastructure/ReadModel/Repository/PostgresRepository.ts b/src/Billing/Transaction/Infrastructure/ReadModel/Repository/PostgresRepository.ts index febbc94..40f1384 100644 --- a/src/Billing/Transaction/Infrastructure/ReadModel/Repository/PostgresRepository.ts +++ b/src/Billing/Transaction/Infrastructure/ReadModel/Repository/PostgresRepository.ts @@ -12,8 +12,14 @@ export default class PostgresRepository implements IRepository { @inject("infrastructure.transaction.readModel.dbal") private readonly connection: Repository, ) {} - public async save(transaction: Transactions): Promise { - await this.connection.save(transaction); + public async save(dto: ITransactionReadDTO): Promise { + const entity = new Transactions(); + entity.uuid = dto.uuid; + entity.product = dto.product; + entity.priceAmount = dto.priceAmount; + entity.priceCurrency = dto.priceCurrency; + entity.createdAt = dto.createdAt; + await this.connection.save(entity); } public async get(id: TransactionId): Promise { diff --git a/tests/Billing/Transaction/Infrastructure/InMemoryProjector.ts b/tests/Billing/Transaction/Infrastructure/InMemoryProjector.ts index d7c0bde..6778120 100644 --- a/tests/Billing/Transaction/Infrastructure/InMemoryProjector.ts +++ b/tests/Billing/Transaction/Infrastructure/InMemoryProjector.ts @@ -1,7 +1,6 @@ import { EventSourcing, ReadModel } from "hollywood-js"; import { inject, injectable } from "inversify"; import TransactionWasCreated from "@Transaction/Domain/Events/TransactionWasCreated"; -import {Transactions} from "@Transaction/Infrastructure/ReadModel/Mapping/Transactions"; @injectable() export class TransactionInMemoryProjector extends EventSourcing.EventSubscriber { @@ -13,8 +12,12 @@ export class TransactionInMemoryProjector extends EventSourcing.EventSubscriber } protected onTransactionWasCreated(event: TransactionWasCreated): void { - this.readModel - .save(event.aggregateId, Transactions.fromCreated(event)) - ; + this.readModel.save(event.aggregateId, { + createdAt: new Date(), + priceAmount: Number(event.amount), + priceCurrency: event.currency, + product: event.product, + uuid: event.aggregateId, + }); } }