Domain Managed Project: Preventing Anemic Domains and Modeling Real Roles

I am looking for some tips on how much I should worry about avoiding an abnormal domain model. We are just starting out with DDD and are struggling with analysis paralysis for simple design decisions. The last thing we adhere to is what certain business logic belongs to, for example, we have an Order object that has properties such as Status , etc. Now say that I need to execute a command like UndoLastStatus , because someone made a mistake with the order it is not as simple as changing the Status , as other information needs to be registered and the properties changed. Now in the real world, this is a pure administration task. So, as I see, I have two options that I can think of:

  • Option 1: add a method for ordering, for example, Order.UndoLastStatus() , although this view makes sense, it really does not reflect the domain. Order is also the main object in the system, and if everything related to order is placed in the order class, things can get out of control.

  • Option 2. Create a Shop object and with it will have different services that represent different roles. Therefore I could have Shop.AdminService , Shop.DispatchService and Shop.InventoryService . Therefore, in this case I will have Shop.AdminService.UndoLastStatus(Order) .

Now the second option, we have something that reflects the domain much more, and allows developers to talk with business professionals about similar roles that actually exist. But he is also moving towards an anemic model. What would be the best way to go at all?

+6
source share
4 answers

Option 2 would lead to a process code for sure. It may be easier to develop, but much harder to maintain.

Now in the real world this is a pure administration task

The "administration" of tasks should be closed and called through publicly available, fully "domain" actions. Preferably, it is still written in easy-to-understand code that is called from a domain.

As I see it, the problem is that UndoLastStatus does not make sense to a domain expert.
Rather, they talk about making, canceling, and filling out orders.

Something along these lines might fit better:

 class Order{ void CancelOrder(){ Status=Status.Canceled; } void FillOrder(){ if(Status==Status.Canceled) throw Exception(); Status=Status.Filled; } static void Make(){ return new Order(); } void Order(){ Status=Status.Pending; } } 

I personally do not like the use of "statuses", they automatically apply to everything that uses them - I see that there is an unnecessary connection .

So I would have something like this:

 class Order{ void CancelOrder(){ IsCanceled=true; } void FillOrder(){ if(IsCanceled) throw Exception(); IsFilled=true; } static Order Make(){ return new Order(); } void Order(){ IsPending=true; } } 

It is best to use so-called domain events to change related things when changing order status.
My code will look like this:

 class Order{ void CancelOrder(){ IsCanceled=true; Raise(new Canceled(this)); } //usage of nested classes for events is my homemade convention class Canceled:Event<Order>{ void Canceled(Order order):base(order){} } } class Customer{ private void BeHappy(){ Console.WriteLine("hooraay!"); } //nb: nested class can see privates of Customer class OnOrderCanceled:IEventHandler<Order.Canceled>{ void Handle(Order.Canceled e){ //caveat: this approach needs order->customer association var order=e.Source; order.Customer.BeHappy(); } } } 

If the order is too large, you can check that the contexts are limited (as Eric Evans says - if he had the chance to write his book again, he would move the restricted context to the very beginning).

In short, this is a domain-driven decomposition form.

The idea is relatively simple - itโ€™s quite normal to have several Orders from different points of view, for example, contexts.

eg. - Order from the purchase context, Order from the accounting context.

 namespace Shopping{ class Order{ //association with shopping cart //might be vital for shopping but completely irrelevant for accounting ShoppingCart Cart; } } namespace Accounting{ class Order{ //something specific only to accounting } } 

But usually enough of the domain itself avoids complexity and is easily decomposable if you listen to it close enough. For instance. You can hear from experts such terms as OrderLifeCycle, OrderHistory, OrderDescription, which you can use as anchors for decomposition.

NB: Keep in mind - I got a zero idea of โ€‹โ€‹your domain.
It is very likely that these verbs that I use are completely strange to him.

+6
source

I would be guided by the principles of GRASP . Apply the principle of designing an information expert, that is, you must assign responsibility to the class, which, of course, has the most information needed to make the changes.

In this case, since the change in the status of the order is associated with other objects, I would have each of these low-level domain objects support the method of applying the change to itself. Then, also use the domain service level, as described in option 2, which abstracts the entire operation, spanning multiple domain objects as needed.

Also see the Facade template.

0
source

I think that the presence of a method such as UndoLastStatus in the Order class seems a little wrong, because the reasons for its existence are, in a sense, not included in the order volume. On the other hand, having a method that is responsible for changing the status of an order, Order.ChangeStatus fits perfectly into the domain model. The order status is the correct concept of the domain, and changing this status must be done through the Order class, since it owns the data related to the order status - the responsibility for the Order class is to keep it consistent and in good condition.

Another way to think about the fact that the Order object is what is stored in the database, and this is the โ€œlast stopโ€ for all changes applied to the Horde. It is easier to talk about what the correct state for the order may be from the point of view of the Order, and not from the point of view of the external component. This is what DDD and OOP talk about, which makes it easier for people to talk about code. Moreover, access to private or protected members may be required to perform a state change, in which case using the method in the order class is the best option. This is one of the reasons that anemic domain models are not approved - they transfer responsibility for maintaining the state in accordance with the class of ownership, thereby violating encapsulation, among other things.

One way to implement a more specific operation, such as UndoLastStatus, would be to create an OrderService that provides the domain and how external components work on the domain. Then you can create a simple command object as follows:

 class UndoLastStatusCommand { public Guid OrderId { get; set; } } 

OrderService will have a method to handle this command:

 public void Process(UndoLastStatusCommand command) { using (var unitOfWork = UowManager.Start()) { var order = this.orderRepository.Get(command.OrderId); if (order == null) throw some exception // operate on domain to undo last status unitOfWork.Commit(); } } 

So, now the domain model for Order provides all the data and behavior that correspond to the order, but the OrderService and service level as a whole announce the various operations performed in the order and expose the domain for use by external components, such as the presentation level.

We also consider the concept of domain events , which discusses anemic domain models and how to improve them.

0
source

It looks like you are not managing this domain from the tests. Take a look at Rob Vens's work, especially his work on search modeling, time inversion, and active passive.

0
source

Source: https://habr.com/ru/post/893540/


All Articles