← Back to context

Comment by gabrieledarrigo

9 months ago

> I would never combine functionality to update an order with the data and structure of the an order. The reason is simple: the business constraints don't always live inside the order.

> Here's an example why such an approach inevitably must fail: if the business says that orders can only be made until 10000 items have been ordered in a month, then you cannot model that constraint inside of the order class. You must move it outside - to the entity that knows about all the orders in the system. That would be the OrderRepository or however you want to call it.

It's not that hard.

If the constraint of your example is a domain constraint, and so it's *always* valid, then when you hydrate an Order entity, other than the Order data itself, you also need to provide the total number of orders.

```

// orders is the repository, and it hydrates the entity with the total number of orders for this month

order = orders.new()

// Apply the check

order.canBeCreated()

```

Where the `canBeCreated` method is as simple as:

```

if this.ordersInAMonth > TOTAL_NUMBER_OF_ORDER_IN_A_MONTH ...

```

Fixed.

It's the same as using the OrdersRepository to query the number of orders directly before creating one, but here, the logic is just in the class.

Now, your example is pretty stupid, so I know it must not be taken literally but...

PS: I'm all but not a DDD advocate.

I would say that this code is not good (or to be more diplomatic: not optimal). Firstly, because between `order = orders.new()` and `order.canBeCreated()`, it's possible to insert other calls or actions on or with that order, which should actually not be allowed/possible.

And second, because my original critics still holds: you now have some kind of order-entity-unrelated information inside the order (or inside its `canBeCreated()`). This will force you to touch the order-entity when removing a business constrain that is unrelated to the (single) order-entity. Because otherwise, where does "ordersInAMonth" come from? It must be able to talk to the database or something.

> Now, your example is pretty stupid, so I know it must not be taken literally but...

No no, you absolutely can take it literal. It might not be very realistic, but that doesn't change the fact that we can use it to discuss pros and cons of different designs.

> It's the same as using the OrdersRepository to query the number of orders directly before creating one, but here, the logic is just in the class.

As with my two issues that I mentioned above, the problem is the "just" in your sentence. It appears that your assessment is that the code living in a different place is merely a problem of the code being in a different place with no effect on productivity. But to me, having the code in a "wrong" place becomes a really big problem over time, especially in a big code base.

Also, we can extend this example. Let's say we have two or more entities. Like orders, users and stores and there are constraints that span and impact the state and/or creation of all of them at the same time.

Now let's compare the different approaches of us. In my case, it's rather easy: there must be some "system" or "service" the lives above all the entities that are constrained by a business rule. So if there is a business rule that touches entities A, B and C, then there must be some "system" or "service" that knows about all A, B and C and can control each of them. In other words, there cannot be a "system" or "service" that controls just A anymore. The logic to ensure the constraint then lives in that service.

With your approach, how and where do you put the code for that constraint?

And let's, just for the sake of the argument, assume that you cannot push the constraint into the database. Because that basically would be such an uber-service as described by me above. In reality, we might employ the database to (also) enforce constraints. But for the sake of the discussion, let's say we use a database where we cannot.

Looking forward to your response!