Assume that in my hypothetical server application, I use a SQL database for persistence, an ORM like Hibernate and a language like Java for the business logic. Requests come over HTTP.
If I want to implement the transaction script pattern, I would follow this template for each (e.g. HTTP POST) method:
1. Load required entities from the database
2. Execute business logic on them
3. Persist/flush the changed entities, add newly created entities, and remove deleted ones
So long as I don't compose such transaction script methods, everything is straightforward: I know which entities I have to load before applying the business logic. For example, I know which child entities to fetch and attach to parent entities, because I have the foresight of what will be used later on. Heck, I could even intertwine ORM operations with business logic.
The problems start as soon as I want to compose my transaction script methods. In principle, they could be nested to an arbitrary level. Given that I use an ORM, this leads to a problem:
- How do I know which entities have been marked as deleted in a previous method? They might still be connected and reachable via other entities. I certainly don't want to check whether they are deleted every time I access them!
How is this issue commonly resolved? I can only think of the following strategy (that happens all in one overarching DB transaction):
- Each transaction script method has its own ORM session/context, and flushes and disposes it before either calling another method or returning. This way, every method will have a fresh session and deleted entities will no longer be present. However, this leads to very cumbersome session handling and also to unnecessary repeated loading of the same data.
Ideally, I would like to separate the persistence logic from the (composable) business logic, but I can't see how this is possible with the transaction script pattern, since I would have to know which entities (and connected child entities) I need to load at the beginning. This is impossible without first inspecting the entire implementation of the business code that follows. The underlying root cause is the fact that the type system does not capture "how much data" is required. The only way out would be the introduction of DDD aggregates, but if I do this, I could as well just abandon the transaction script pattern altogether.
Any suggestions? Is the transaction script pattern even supposed to be composable?