Transactions sit at the use-case boundary, not inside a repository call
Transactions start and end at the use-case service — not in repositories or controllers.
nestjs-data-transactions-at-use-case
Why it matters
| Stake | If ignored |
|---|---|
| Layer mixing |
|
| Hidden coupling |
|
| Data corruption |
|
How to fix
@Transactional or unit-of-work wraps a service-level call, not a single repository call. If an operation needs two repositories — the transaction lives in the service.
Examples
ts
async createSiteWithAgent(input: CreateSiteWithAgentDto) {
const site = await this.sitesRepo.create(input.site); // commit 1
const agent = await this.agentsRepo.create({ // commit 2
...input.agent,
siteId: site.id,
});
// if the second fails, an orphaned site is left in the DB
return { site, agent };
}ts
@Injectable()
export class SiteProvisioningService {
constructor(
private dataSource: DataSource,
private sites: SitesService,
private agents: AgentsService,
) {}
async provisionSiteWithAgent(input: ProvisionInput) {
return this.dataSource.transaction(async (tx) => {
const site = await this.sites.create(input.site, { tx });
const agent = await this.agents.create({ ...input.agent, siteId: site.id }, { tx });
return { site, agent };
});
}
}