odavid / typeorm-transactional-cls-hooked

A Transactional Method Decorator for typeorm that uses cls-hooked to handle and propagate transactions between different repositories and service methods. Inpired by Spring Trasnactional Annotation and Sequelize CLS
MIT License
524 stars 86 forks source link

A way to support transactions started without this library #53

Closed Faithfinder closed 4 years ago

Faithfinder commented 4 years ago

To ease the migration to this library (which is great and should make my life much better, hauling the entity manager around is a terrible experience) it would be nice if it could support transactions started in a traditional way. i.e:

await getConnection().transaction(async transactionalEntityManager => {
   this.create()
});

 @Transactional({propagation: Propagation.MANDATORY})
  async create() {
    const newThing = this.repository.create()
    return this.repository.save(newThing)
  }

Right now this will fail, but it would be great if it could work. Unfortunately I don't have enough know-how to implement it myself. If you think it's possible and will nudge me in the right direction, I'd be willing to try, though.

odavid commented 4 years ago

Thank you @Faithfinder... The way the library works is that it starts a transaction and put the EntityManager within the cls-hook Namespace. I don't see a real way of doing it the way you want, without changing the code of the callers... I am sorry...

Faithfinder commented 4 years ago

No worries. What I wanted to enable, is refactoring the code from the end of the call chain, rather than beginning. I suppose this library could somehow patch TypeORM's methods that start transactions to put the entity manager where we want it. The use case is not really worth the additional complexity though, probably. The workaround is quite simple, if a bit ugly, really. I'll leave it here in case someone googles and stumbles upon this issue.

You can refactor the top level of transactional chain to use @Transactional() instead of getConnection().transaction(), and pass the manager further down the chain by getting the manager property of any repo you use. After that you can use both "styles" of transactions and refactor both the top and the bottom of the call chain at your leisure.

Pseudo code example. This:

  await getConnection().transaction(async transactionalEntityManager => {
     this.startTransactionalWork(transactionalEntityManager)
  });

startTransactionalWork(transactionalEntityManager) {
    const transactionalRepo = transactionalEntityManager.getRepository();
    continueTransactionalWork(transactionalEntityManager)
  }

becomes this:

@Transactional()
startTransactionalWork() {
    const transactionalEntityManager = this.anyInjectedRepository.manager;
    continueTransactionalWork(transactionalEntityManager)
  }