0

Clean Architecture recommends to implement "enterprise wide rules" in the entity / domain level.

I'm struggling to understand how to deal with the following scenario: Take a warehouse where you need to make sure that safety rules are followed. For example, acid must not be stored above alkaline (lye) and vice versa. Or fresh meet must not be stored in an area without properly functioning air condition.

Because these are safety or hygiene rules which apply worldwide, the domain level seems the appropriate place to implement them. That could be a class called "WarehouseBoxUnit" with a method "AddProduct" that includes validation of environmental conditions.

However, in a typical scenario, you have to access an external state to check if there are any acid products are stored above or below a certain storage box where you want to put alkaline. Or you have even to access sensor data to check if the air conditioning is working. In any case, this state is dynamic and external. Accessing external data should be part of the infrastructure.

The conflict is that in Clear Architecture, the domain model should not have a reference to the infrastructure project. And that might be a circular reference anyway.

I could move the validation to the application layer where I would have interfaces to infrastructure classes. However, this would be risky as someone could forget to do all necessary validation when adding new features / use cases.

Is there an established way to deal with this?

Klaus P.
  • 43
  • 8

2 Answers2

0

I would create a "WarehouseShelf" domain object that the use case retrieves from a repository. The use case can then put the WarehouseBoxUnit into the shelf. The WarehouseShelf can reject this if the rules are not applicable. If the WarehouseShelf accepts the WarehouseBoxUnit you can pass the WarehouseShelf to a repository's persist method.

When the WarehouseShelf is created by the repository. You must not load all WarehouseShelfBoxUnits to create the WarehouseShell. Maybe it is sufficient to create a WharehouseBoxUnitDescribtion that can tell if it is compatible with the box description of the box you want to add.

The WarehouseShelf can also have a WarehouseConditions object that can tell if it has a working air conditioning. A WarehouseBoxUnit can tell the storage conditions it needs and you can ask the WarehouseConditions if they are met.

So finally my domain model would look like this:

+---------+        +--------------------+
| BoxUnit | ---->  | BoxUnitDescription |
+---------+        +--------------------+


+--------------+     *  +------------+    0..1  +--------------------+
|     Shelf    | -----> | ShelfSpace | -------> | BoxUnitDescription |
+--------------+        +------------+          +--------------------+
| add(BoxUnit) |       
+--------------+
       |
       |
       V
+---------------------+
| WarehouseConditions |
+---------------------+

The Shelf might have a separate list to store BoxUnit's that are added, maybe a ShelfModifications, because the ShelfSpace does only know BoxUnitDescriptions. Or you can use the BoxUnit instead of the BoxUnitDescription in the ShelfSpace.

In either case the entities are decoupled from access to external data and when you want to unit test the rules you can simply create a Shelf with BoxDescriptions, WarehouseConditions and add a BoxUnit to it.

EDIT

Another approach is using a DomainService that Voughn Vernon describes as:

A Service in the domain is a stateless operation that fullfills a domain-specific task. Often the best indication that you should create a Service in the domain model is when the operation you need to perform feels out of place as a method on an aggregate or value object. To alleviate that uncomfortable feeling, our natural tendency might be to create a static method on the class of an Aggregation Root. However, when using DDD, that tactic is a code smell that likely indicates you need a Service instead.

Voughn Vernon, Implementing Domain-Driven Design, Chapter 7 Services

But be aware that using a Domain Service usually leads to an anemic domain model that I think you should avoid. That's why I didn't mention it in the first version of my answer. But it might be a solution for you in your situation.

René Link
  • 48,224
  • 13
  • 108
  • 140
0

The answer that @rené-link posted, tackles the situation described in my question quite well.

Based on this, I came up with an approach which is more generic:

The validation logic will be implemented in the domain model.

The domain model contains then one (or multiple) delegate which retrieve state information from the outside world. These are the validation parameters. Parameters may be state but also configuration parameters (i.e. maximum weight allowed per box).

It is important to place the delegate in a common area of the application layer to ensure consistent behavior in all use cases. The delegate handler retrieves external state or configuration data through interfaces to the infrastructure.

The delegate(s) can be initiated in the constructor of the domain model or in a setter method etc. In any case, if not set properly, tests will fail.

This design will for example allow not only the warehouse issue described above, but also the control of a machine which must not be started if the cover is open. If you have multiple use cases (started manually from a web UI, started automatically with from a smart device etc.), you can demonstrate that the validation is carried out in all cases.

Klaus P.
  • 43
  • 8