Urgent.News

What's breaking now, across thousands of outlets.

Tech

Generalizing Transactions in NestJS: A Domain Port over TypeORM and MongoDB

Transaction control usually starts out tied to whatever implements it. An executor — a concrete class that opens a transaction over the project's ORM — gets injected into the controller and wraps the body of the handler, so that everything a request writes either commits or is discarded as a block: @ Post () create (@ Body () body : CreateUserRequest ) { return this . transactionExecutor .…

Transaction control commonly starts with whatever implements it. An executor, a concrete class opening a transaction over the project's ORM, gets injected into the controller and wraps the handler's body. This ensures all request writes either commit or are discarded as a block. This approach works well for most operations, as an HTTP request corresponds to a single unit of work, and repositories enlist themselves in the active transaction through AsyncLocalStorage.

However, this method cannot handle operations that require waiting on an external service before writing.

The solution involves extracting the executor's contract into a general interface within the domain. This allows the use case to decide its own scope without depending on the ORM. The interface is tested with implementations for PostgreSQL and MongoDB, displaying the extent of the generalization. The problem arises when waiting on a third-party service inside the transaction, as this can hold a pooled connection and locks for the duration of the block, even when the block only interacts with the database.

This wait becomes noticeable when calling external services, such as payment gateways, file stores, or AI models.

To address this issue, the controller should only carry out database operations within the transaction. This fix is easy to implement, but it breaks the original approach since the controller cannot perform this task. The solution lies in generalizing the executor contract rather than injecting it as is. The resulting UnitOfWork port enables the application layer to run a block as a single unit, without relying on a specific implementation of transactions over TypeORM.

This approach keeps the logic clean and testable in the domain and allows for flexibility in choosing the underlying persistence engine.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

RIP, iPhone Ultra

For many months, tech blogs like ours used the name iPhone Ultra to refer to Apple's rumored foldable iPhone, but the device ended up being named iPhone Duo .

More from Thursday 10 September →