4

I'm very new to DDD and i'm trying to implement my use case properly.

I have multiple entities that are all tied together, much like the usual aggregate example of an Order which encapsulates LineItem.

From what I understand from basic DDD I'm inclined to create 2 entities, one for an Order and another for a Line Item, but then there seem to be 2 options going from there:

  • Have both repositories return Tx, instantiate them all into another interface/struct as an "aggregate" (probably not the correct name) to make the transaction to create a new order

  • Create a new repository that would then do transactions on it's own for the whole scope of the Order ( creating the order + line items )

For me the 2nd option seems much easier to implement than the first one. I can make joins, tx, or anything needed in this repository to retrieve data from both tables ( order and lines items ) and also can create transaction and ensure their data integrity. But I'm not sure if it's good practice.

"pseudocode" would look like this :

type Order struct {
   ID int
   lineItems []LineItem
   ...
}

type LineItem struct {
   ID int
   ...
}

type OrderRepoistory interface {
   GetOrders()([]*Order,err)
   GetOrder(id int)(*Order,err)
   Create(order *Order) err
}

And inside the OrderRepository implementation the create function would look something like this:

func Create(order *Order) err {
  tx := BeginTX
   insert order into the orders table
   insert the lineitems into the line_items table
  tx.commit or rollback
  return nil or err
}

func GetOrder(orderId int)*Order {
   var order Order
   var items []*LineItem
   row,_ := db.Query("select * from orders where id = $1",orderId)
   row.Scan(&order)
   rows ,_ := := db.Query("select * from line_items where order_id = $1",orderId)
   loop and get append each rows to items slice
   // copy slice to 
   order.LineItems = items
   return &order
}

So both Create and Getorder of the repository include queries on both tables to retrieve one Order.

Does this implementation make sense as it seems very broad for an "entity"? If not, what's the proper way to make transaction and queries over multiple tables at once?

Arnold Schrijver
  • 3,588
  • 3
  • 36
  • 65
  • 4
    DDD is concerned with concepts, not implementations. DDD does not specify exactly how entities map to the tables of an underlying data store - in fact, DDD can be used with data stores that do not involve a concept of tables at all. Moreover, even if it *did* specify, it is only a framework of guidelines, not hard and fast rules. – Adrian Jul 15 '20 at 14:52
  • i thought i understood that an entity had to have it's own repositories.I believe the implementation kind of matter when you need atomicity across multi table while still preserving the " pureness " of entities that where my understanding fails when impelmenting. Because either i always return TX and then the implementation leaks over services or i define my domain as something " bigger " than a simple entity. Or perhaps my understanding of entities is wrong. – estebanmanuel Jul 15 '20 at 14:59
  • 1
    The implementation absolutely matters to functionality, but it doesn't matter to DDD. – Adrian Jul 15 '20 at 15:25
  • In another [Answer](https://stackoverflow.com/a/66579712/8295283) I just provided related to transaction handling I link to a great series of blog articles detailing all the ins and outs of DDD based architectures for Golang. – Arnold Schrijver Mar 11 '21 at 09:45
  • Yes, you can. the `aggregate` represent more than one entity. so for example when you persist on your repo you should do it in a transaction (if you are using a RDBS). I think the DDD book have the exmpaple of the table `person` and `customer`. The aggregate `customer` needs to insert in two tables when its created. – gipsh Mar 05 '22 at 16:21

1 Answers1

3

I recommend to look into the concept of aggregates more closely. A repository is more or less representing a collection of aggregates and one entity is always the aggregate root which you can consider to be parent of several entities that are related to each other - as an aggregate.

The order could be the aggregate root and order lines could be entities and with that would be completely loaded from the persistence when you load the order aggregate.

Or the line items could be aggregates on their own in which case you would usually only keep a list of ids to line items in your order aggregate.

But that really depends on the business logic. Because that's, amongst other stuff, what DDD is about - reflecting the business context the best you can.

But no matter how you design you domain model with your aggregates, entities and value objects these classes can look completely different than your database model.

Structure your domain model classes in a way that makes it easy to implement the domain logic and structure your data model in a way that makes it easy for persisting and querying the data.

So to your question:

...if not what's the proper way to make transaction and queries over multiple tables at once ?

If you request an aggregate from your repository (e.g. an order via an order id) then the repository implementation should take care of all involved queries which can of course be performed on several different tables. Because again, the domain model classes shell be independent of the database model.

The aggregate should then be loaded with all data within the repository method which includes all data of child entities and used value objects. But this still happens during one repository method, such as findById().

Concerning transactions the same applies for storing aggregates. Here the repository takes a fully loaded aggregate with the current state and then saves it along with all data. This can again involve inserts or updates on several tables (when speaking of relational database).

knittl
  • 246,190
  • 53
  • 318
  • 364
Andreas Hütter
  • 3,288
  • 1
  • 11
  • 19