3

Having a well designed domain, with aggregates that do not reference each other, well defined boundaries and aggregate objects with well defined object references, why is it a bad practice to have transaction logic inside repositories (with a repository made for each domain object)?

Before answering with UoW pattern, take in consideration this question UoW limitation.

Community
  • 1
  • 1
Geo C.
  • 755
  • 6
  • 18
  • Could you post an example of what you mean by "having transaction logic inside repositories" ? Would a given method in the repository encompass a whole business transaction, would you call separate methods on the repository to start and commit a transaction... ? – guillaume31 Dec 22 '13 at 23:01

3 Answers3

4

Because a typical transaction usually spans multiple repositories. When you sell a product you want, in the same transaction, to

  • decrement the number of elements in the stock (StockRepository)
  • create an order (OrderRepository)
  • create a shipment (ShipmentRepository)

And you really want all of that to either succeed or fail.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Can't a repository hold reference to the other repositories like the objects in the domain ? So transactions can reside in this layer ? If not where should the transaction logic reside ? UoW is limiting me for simple transactions like in the question linked . Should i make a service layer just for the purpose of these transactions ? – Geo C. Dec 17 '13 at 17:31
  • 2
    For the purpose of the transaction, and to implement your business logic. When you sell 10 products, you want to create a single order with 10 order lines, and as many shipments as necessary depending on the weight and volume of the products, and raise an alert is the new stock volum is below a certain limit, etc. That is business logic that needs to be implemented, and which doesn't belong to any of the three repositories involved. The whole point of the repositories is to decouple the persistence logic from the business logic. – JB Nizet Dec 17 '13 at 17:39
  • In your example orderLine is the root which holds reference to product and shipment . Can't a oerderLineRepository hold reference to productRepository and shipmentRepository ? Make a transaction in oderLineRepository which startsTransaction() calls simple CRUDs from these 3 repositories and commit the queries after everything went ok ? – Geo C. Dec 17 '13 at 18:10
  • 2
    I somewhat disagree. If you have a clean domain model and follow the "one aggregate root per transaction" principle, i.e., by moving all business logic that spans multiple aggregates into a long running process/saga, I don't see any reason why your repository shouldn't handle transaction logic. Quite the contrary, transaction logic is an infrastructure concern that is highly tight to the concept of databases, while other storage methods, such as file systems, do not have a concept of transactions. – Alexander Langer Dec 17 '13 at 21:38
  • @Alexander: One aggregate root per transaction simply doesn't pass the reality check. Unless you have a single "aggregate root" for your whole database. At some time, you always end up wanting to have a single transaction that spans multiple domains. And your typical enterprise application uses databases, because it needs efficient queries and ACID. You don't need transactions because you have a database. You choose to use a database because you *want* transactions. – JB Nizet Dec 17 '13 at 22:32
  • @GeoC: you can of course do that. But your OrderLineRepository won't be a repository dealing with order lines anymore. It will effectively be a Product Sale service that uses several repositories to implement business logic related to the sale of products. The usual way to name such a component is "service". Not "repository". – JB Nizet Dec 17 '13 at 22:35
  • 1
    @JBNizet You would never have an OrderLineRepository as order line is a detail of Order, so you have only OrderRepository. **Alexander Langer** has a point, for transactions spanning multiple domains/servers a saga is the cleanest way to implement it and the repository shouldn't know about it. What if one domain repository stores things in the cloud or on the file system? ACID works with some db engines, it's a special case solution, not a generic one. And efficient queries are mostly nosql domain nowadays. RDBMS still has a role but it doesn't have a monopol anymore. – MikeSW Dec 18 '13 at 07:11
  • @MikeSW: to be frank, I have no idea what a saga is. But the OP explicitely asked about transactions, so I assumed he is not using a NoSQL, No-ACID database, but a transactional one. And the OP replied telling that he would consider OrderLine to be the root, so I simply expanded on his way of seeing things. If you want Order to be the root, then so be it: it doesn't change anything: you still need a transaction that deals with multiple different repositories. – JB Nizet Dec 18 '13 at 07:19
  • @MikeSW Actually Alexander is confirming my opinion that repostories could handle transaction logic . – Geo C. Dec 18 '13 at 22:54
  • Also from where did you get your statistics about RDBMS beign surpassed by nosql solutions ? They still hold monopol on overall performance (maybe just because there isn't yet a finished&polished non relational DBMS) . The only DBMS that could surpass the realational ones are OODBMS (but it will take alot of work/time to get there). – Geo C. Dec 18 '13 at 23:03
  • @JBNizet So you are saying that an entity object with reference to other objects can't have a repository with references to the corespondent object repostories ? Example OrderRepository with reference to ProductRepository ? What if a multiple join select needs to retrieve all the orders and theyr afiliate products ? You would do this by using multiple statements per repository or one statement in one repository ? Your arguments are not elucidating and contradictory . – Geo C. Dec 18 '13 at 23:28
  • 1
    If some logic needs to call multiple repositories, then this logic should be in a service. I don't see why retrieveing an order with their products would need more than a single query. If it's needed, then I would create a service that would call the appropriate methods on the order repository, and the appropriate methods on the product repository. Otherwise, you'll quickly have a spaghetti of dependencis, you would also have a reference to the order repository in the product repository, leading to circular dependencies. – JB Nizet Dec 19 '13 at 07:33
2

Even in the one aggregate per transaction case, the code block usually looks like this:

 Order order = orderRepository.findBy(orderId);(1)
 order.doSomething();
 orderRepository.store(order);//or omitted with uow

Techonically, how to implement transaction logic and locking stratey within the repository when some steps are outside the repository?

Yugang Zhou
  • 7,123
  • 6
  • 32
  • 60
0

Because it breaks the Single Responsibility Principle. Repositories are providers of aggregate root collections, not transaction coordinators. Besides, their implementations reside in the Persistence layer, which means their field of vision is too low-level to even be aware of big picture concerns such as business transactions.

guillaume31
  • 13,738
  • 1
  • 32
  • 51
  • :) How do you persist your objects ? Actually they also require consitency on the database layer for example . Theyr field of vision is not so low , you need to map the state in the database also so you can retrieve consitent objects . I was refering specially to the database transactions (which also need constency). For consistency reasons you can even make triggers on the databse side for complex designs/logic (i had to this for a db polymorphic association wrokaround). This example si just to show that database also needs consitency which relates to the domain . – Geo C. Dec 19 '13 at 15:24
  • I don't persist my objects via repositories. I add or delete objects from collections exposed by repositories, or modify them, but these changes are not persisted right away. An Application layer Service, which is aware of the state of the use case in progress, can decide *when* to persist. *How* to persist is also not implemented by your repository but typically by your ORM. Even with no ORM, I would have a specific adapter object that translates from logical/business commit to physical commit (which can be DB transaction commit or something else). Separate responsibilities, separate objects. – guillaume31 Dec 22 '13 at 17:53
  • I think you are confusing factories with repositories . Repository is a layer that abstracts the model from a storage technology . Even if you use ORM , you should call the mapper from within a repository (who knows maybe later you will swich to a non relational database). – Geo C. Dec 22 '13 at 21:22
  • Factories have nothing to do with this. I didn't say that a repository shouldn't call the ORM, only that it shouldn't know how to persist (= make the final commit to a persistent store). There's an example of what I mean here : http://richarddingwall.name/2009/10/22/repositories-dont-have-save-methods/ – guillaume31 Dec 22 '13 at 22:50