1

Reading about DDD, i have read that it is needed to have a repository per each aggregate entity.

For example, in an ordering system, I should have a repository for the order and another repository for the buyer.

If i am not wrong, a unit of work it is to ensure that all the actions are processed as a unit, so if i do medications in the order and in the buyer, I could ensure that all is done or nothing.

Bit i have read that it is better to ensure to modify one aggregate each time, so it shouldn't recommended to modify orders and buyers in the same process.

So this makes me think that if in each process I modify one aggregate, is it really needed a unit of work?

Perhaps the example that I expose it is too simplicity, and perhaps in some complex process, if i modify an aggregate it affects to another aggregate. But it is advise that una aggregate shouldn't to modify another aggregates outside of the aggregate that I am modifying. So again, I am wondering if it is really useful a unit of work if I follow DDD.

In summary. Is it really needed a unit of work with DDD? If in some cases it is useful, in which cases?

Thanks.

3
  • "Reading about DDD, i have read that it is needed to have a repository per each aggregate entity." - it's more that you should have a repository per some logical usage scenario (i.e. oriented around a use case, loosely speaking). Doesn't have to be mapped 1-to-1. What's "one logical usage scenario" is something that you and your team have to come up with (that is, design), based on your understanding of the domain/application. It's this strict 1-to-1 mapping to DB tables that leads to the need for the "Unit of Work" pattern (even though ORMs already implement it). Commented Mar 27, 2023 at 17:18
  • "Bit i have read that it is better to ensure to modify one aggregate each time, ..." - Couple of ways to look at this. Perhaps (1) you shouldn't model this as two separate aggregates; it could be that this is better represented with one aggregate that's aligned with how the business thinks about the ordering process, that includes the order and (some representation of) the buyer (might not be the only one in the system). Either that, or you fallback to the "Unit of Work" pattern. Or (2) the model is good, but you accept that the two entities will be only eventually consistent. Commented Mar 27, 2023 at 17:23
  • Please always include the source of your information. It's important to be able to follow the train of thoughts that lead to the conclusions you quote. Commented Mar 31, 2023 at 12:11

3 Answers 3

1

They complement, but they don't need each other. UoW works with data-driven or even-driven designs too.

A UoW work as a business transaction, where many things can happen and change. It can span several aggregates if data consistency is important or critical.

It also provides a single space/location where to track changes, so you can understand/read much better what the business transaction is about. The opposite makes you go back and forth through the code, jumping from one class/method to another up to get lost in the mist.

UoW (usually) has 2 phases:

  • (1) Job: Do the work & track changes.
  • (2) Commit: Open transactions, dump changes to the database and commit/rollback. Everything in a single place and transaction.

Now, pay attention to point (1) because everything happens in memory, nothing is sent to the DB. Not even staged.

Once everything is consistent in memory*, (3) opens a transaction, executes db statements in the due order and commits changes at the end. Or rollback if something goes wrong.

but I mean coordinate the modification of various aggregates at the same time

That's phase (1).

A UoW might involve different aggregates, services, repositories, etc. But! It's important to keep the transactionality and to do so, you do changes in memory first and commit changes at the end.

If during (1) you change/alter the state of something, somewhere, that's out of the UoW transaction scope and you have to find a compensative transaction to revert those changes.


*e.g. aggregates are valid

2

Bit i have read that it is better to ensure to modify one aggregate each time, so it shouldn't recommended to modify orders and buyers in the same process.

Modifying one aggregate is not the same as modifying one aggregate entity.

Your aggregate may consist of several tables' worth of data, e.g. a Person aggregate with many Addresses and Cars as subaggregates.
For example, if you were to create a new person with all their address and car information, you would need to update three tables to insert it all, but you would still only be modifying one aggregate.

Tangentially, IF the "modify one aggregate" advice is referring to one aggregate instance (rather than aggregate type), I also don't quite agree with the advice you've been given.
For example, if I'm trying to do a bulk import of a lot of people from e.g. an import file, I want all of those aggregates in a single transaction. It would be a horrible user experience if some of the import succeeded and the user was now forced to come up with a new file that only contains the ones that failed. It is better to not import anything so the user can fix the existing file and try again.

Others might disagree with that example and say they prefer to already import the things that work. That's okay, as long as it makes sense for their use case. Both approaches can be valid. The point I'm trying to make here is that if your advice says that you're allowed to only do one of these and not the other as a blanket rule, I wholeheartedly disagree.

and perhaps in some complex process, if i modify an aggregate it affects to another aggregate.

That is indicative of bad aggregate design - to some extent. There are business cases where it can make sense but this is very contextual.

Generally speaking, your aggregates should be independent modules of your business logic that have no bearing on one another (other than potentially a reference ID to another aggregate).
I should in principle be able to separate your codebase into microservices, one per aggregate, without much effort or needing to break up any coupling between the aggregates.

In summary. Is it really needed a unit of work with DDD?

As far as I'm concerned, UOWs and DDD have nothing directly to do with one another. They're both a kind of good practice and I tend to use them both frequently, but each for their own reason.

2
  • Thanks for the answer. Perhaps I didn't exaplain well. In the example of person with address and cars. Yes, I have one aggregate and I have my Person repository for that, and this repository can interact with the needed tables in the database (person, address and car). When I told unit of work, I didn't mean about coordinate the update of many tables related with the same aggregate, but I mean coordinate the modification of various aggregates at the same time. Commented Mar 27, 2023 at 8:33
  • @ÁlvaroGarcía: The bulk import paragraph I wrote addresses that scenario.
    – Flater
    Commented Jun 15, 2023 at 23:49
2

They are part of DDD but you can do without them

With your example operation where you add a customer and an order in a single operation

A naive approach without any restrictions you might open a database transaction, insert the rows and complete the transaction.

DDD has the concept of separating out Aggregate Roots. So You have Customer and Order and can't* use a single database transaction and so DDD explicitly calls out the concept of a Unit of Work to deal with these scenarios. Essentially a roll your own distributed transaction.

However.

Assuming you are using the same database technology for all your ARs you probably can simply wrap the operation in a transaction scope or other distributed transaction construct which comes with the drivers.

Also.

You can always engineer out unit of work patterns by changing your processes. Include an extra step in your process which goes back and updates the status of the Customer to "first order placed!" after the order goes in or whatever.

Its this second option I would go for in most cases. You have separated out Customer and Order for good design reasons. If you start joining them together for many operations then you are questioning that design choice and limiting the benefits it can deliver.

ie

PlaceOrder()
{
    var customer = new Customer() {
       Id = NewGuid
       name = "bob"
       state = "new" //new customers orders are not processed
    }
    var order = new Order(){
       Item = "Vr headset"
       CustomerId = customer.Id
    }

    try
    {
        //no transaction possible across these!
        customerRepo.AddCustomer(customer); //cloud no-sql db
        orderRepo.AddOrder(order); //csv file on old pc in the warehouse
    }
    catch
    {
       //oh no! throw an exception and pick up manually to resolve
    } 
    //no problems
    customer.State = "confirmed" //confirmed customer orders are processed
    customerRepo.Update(customer)
    

}
4
  • "DDD has the concept of separating out Aggregate Roots". Do you mean that one bounded context should to have only one AR? Commented Jun 15, 2023 at 15:47
  • no you can have multiple ARs in a BC
    – Ewan
    Commented Jun 15, 2023 at 18:09
  • At first I would think for the first option, to run the two operations in one transaction. But How and where I could implement the second option, using the extra step? In the application layer? Could you show and example and how it could be implemented? Commented Jun 16, 2023 at 7:36
  • ok done. its pretty self explanatory though
    – Ewan
    Commented Jun 16, 2023 at 13:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.