In DDD can an entity and repository pull from multiple tables?

Viewed 1364

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?

1 Answers

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).

Related