How to handle Persistence with Rich Domain Model
Asked Answered
C

1

7

I am redesigning my NodeJS application because I want to use the Rich Domain Model concept. Currently I am using Anemic Domain Model and this is not scaling well, I just see 'ifs' everywhere.

I have read a bunch of blog posts and DDD related blogs, but there is something that I simply cannot understand... How do we handle Persistence properly.

To start, I would like to describe the layers that I have defined and their purpose:

Persistence Model

  • Defines the Table Models. Defines the Table name, Columns, Keys and Relations
  • I am using Sequelize as ORM, so the Models defined with Sequelize are considered my Persistence Model

Domain Model

  • Entities and Behaviors. Objects that correspond to the abstractions created as part of the Business Domain
  • I have created several classes and the best thing here is that I can benefit from hierarchy to solve all problems (without loads of ifs yay).

Data Access Object (DAO)

  • Responsible for the Data management and conversion of entries of the Persistence Model to entities of the Domain Model. All persistence related activities belong to this layer
  • In my case DAOs work on top of the Sequelize models created on the Persistence Model, however, I am serializing the records returned on Database Interactions in different objects based on their properties. Eg.: If I have a Table with a column called 'UserType' that contains two values [ADMIN,USER], when I select entries on this table, I would serialize the return according to the User Type, so a User with Type: ADMIN would be an instance of the AdminUser class where a User with type: USER would simply be a DefaultUser...

Service Layer

  • Responsible for all Generic Business Logic, such as Utilities and other Services that are not part of the behavior of any of the Domain Objects

Client Layer

  • Any Consumer class that plays around with the Objects and is responsible in triggering the Persistence

Now the confusion starts when I implement the Client Layer...

Let's say I am implementing a new REST API:

POST: .../api/CreateOrderForUser/
{
  items: [{
    productId: 1,
    quantity: 4
  },{
    productId: 3,
    quantity: 2
  }]
}

On my handler function I would have something like:

function(oReq){
  var oRequestBody = oReq.body;
  var oCurrentUser = oReq.user; //This is already a Domain Object
  var aOrderItems = oRequestBody.map(function(mOrderData){
    return new OrderItem(mOrderData); //Constructor sets the properties internally
  });
  var oOrder = new Order({
    items: aOrderItems
  });

  oCurrentUser.addOrder(oOrder);

  // So far so good... But how do I persist whatever 
  // happened above? Should I call each DAO for each entity 
  // created? Like, first create the Order, then create the 
  // Items, then update the User?

}

One way I found to make it work is to merge the Persistence Model and the Domain Model, which means that oCurrentUser.addOrder(...) would execute the business logic required and would call the OrderDAO to persist the Order along with the Items in the end. The bad thing about this is that now the addOrder also have to handle transactions, because I don't want to add the order without the items, or update the User without the Order.

So, what I am missing here?

Curitiba answered 16/2, 2017 at 23:23 Comment(1)
You probably need to read up more about Repositories, Aggregates, Aggregate Roots and Application Services if you want to go full DDD. Also have a look at the concept of Unit of Work. And I'm afraid you have to dig into non-Node code samples if you need concrete examples of how an Application Service is typically implemented.Ordinate
C
1

Aggregates.

This is the missing piece on the story.

In your example, there would likely not be a separate table for the order items (and no relations, no foreign keys...). Items here seem to be values (describing an entity, ie: "45 USD"), and not entities (things that change in time and we track, ie: A bank account). So you would not directly persist OrderItems but instead, persist only the Order (with the items in it).

The piece of code I would expect to find in place of your comment could look like orderRepository.save(oOrder);. Additionally, I would expect the user to be a weak reference (by id only) in the order, and not orders contained in a user as your oCurrentUser.addOrder(oOrder); code suggests.

Moreover, the layers you describe make sense, but in your example you mix delivery concerns (concepts like request, response...) with domain concepts (adding items to a new order), I would suggest that you take a look at established patterns to keep these concerns decoupled, such as Hexagonal Architecture. This is especially important for unit testing, as your "client code" will likely be the test instead of the handler function. The retrieve/create - do something - save code would normally be a function in an Application Service describing your use case.

Vaughn Vernon's "Implementing Domain-Driven Design" is a good book on DDD that would definitely shed more light on the topic.

Caneghem answered 23/5, 2019 at 11:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.