Avoiding anemic domain model - a real example
Asked Answered
D

4

79

I am trying to understand Anemic Domain Models and why they are supposedly an anti-pattern.

Here is a real world example.

I have an Employee class, which has a ton of properties - name, gender, username, etc

public class Employee
{
    public string Name { get; set; }
    public string Gender { get; set; }
    public string Username { get; set; }
    // Etc.. mostly getters and setters
}

Next we have a system that involves rotating incoming phone calls and website enquiries (known as 'leads') evenly amongst sales staff. This system is quite complex as it involves round-robining enquiries, checking for holidays, employee preferences etc. So this system is currently seperated out into a service: EmployeeLeadRotationService.

public class EmployeeLeadRotationService : IEmployeeLeadRotationService
{
     private IEmployeeRepository _employeeRepository;
     // ...plus lots of other injected repositories and services

     public void SelectEmployee(ILead lead)
     {
         // Etc. lots of complex logic
     }
}

Then on the backside of our website enquiry form we have code like this:

public void SubmitForm()
{
    var lead = CreateLeadFromFormInput();

    var selectedEmployee = Kernel.Get<IEmployeeLeadRotationService>()
                                 .SelectEmployee(lead);

    Response.Write(employee.Name + " will handle your enquiry. Thanks.");
}

I don't really encounter many problems with this approach, but supposedly this is something that I should run screaming from because it is an Anemic Domain Model.

But for me its not clear where the logic in the lead rotation service should go. Should it go in the lead? Should it go in the employee?

What about all the injected repositories etc that the rotation service requires - how would they be injected into the employee, given that most of the time when dealing with an employee we don't need any of these repositories?

Disquietude answered 18/5, 2010 at 5:49 Comment(4)
So what does an ILead look like, if it isn't obvious to put .SelectEmployee() in it?Maryammaryann
Well the lead in this case is a web enquiry, so it will have a Comments property etc. But we also have phone enquiries, applications, quotes etc which are all slightly different. The ILead interface would have properties like LocationOfLead, TimeOfLead etcDisquietude
So I guess it is more obvious to put .SelectEmployee() on the lead than the employee, but that doesn't address the other concerns: the injection of dependant repositories; the lack of SoC; the overall complexity of having all this SelectEmployee code in the Lead class (actually we would need a LeadBase class so that we don't reuse the code across all the inheriting lead classes) when a lot of the time (e.g. during reporting) we are really not concerned with how the employee was selected.Disquietude
Could be of some interest: medium.com/@wrong.about/…Payable
H
59

In this case this doesn't constitute an Anemic Domain Model. An Anemic Domain Model is specifically about validating and transforming the objects. So an example of this would be if an external function actually changed the state of the Employees or updated their details.

what is happening in this case is you are taking all of the employees and making a selection of one of them based on their information. It is fine to have a separate object that examines others and makes decisions with regard to what it finds. It is NOT ok to have an object that is used to transition an object from one state to another.

An example of an Anemic Domain Model in your case would be to have an external method

updateHours(Employee emp) // updates the working hours for the employee

that takes an Employee object and updates its hours worked for the week, making sure that flags are raised if the hours exceed a certain limit. The problem with this is that if you only have Employee objects then you have no knowledge of how to modify their hours within the correct constraints. In this case the way to deal with it would be to move the updateHours method into the Employee class. That is the crux of the Anemic Domain Model anti pattern.

Hastings answered 18/5, 2010 at 6:15 Comment(2)
But what if the Employee is a Persistent Object for the database. Why should I put a method in there? Same question is valid for DTO`s where you do not put methods inside. Where then would you put the updateHours method?Loire
updateHours belongs into the Employee class. You should pass it any data necessary to update hours, for example the task which was completed. Collaborator objects are fine too, but preferably no services.Moniz
R
33

I think your design is fine here. As you know, the anemic domain model anti-pattern is a backlash against the trend of avoiding any behaviour coded in domain objects. But conversely it doesn't mean all behaviour relating to a domain object must be encapsulated by that object.

As a rule of thumb, behaviour that is intrinsicly tied to the domain object and is defined entirely in terms of that one domain object instance can be included in the domain object. Otherwise, to keep responsibilities clear, it's best to put it externally in a collaborator/service like you have done.

Resume answered 18/5, 2010 at 6:0 Comment(1)
exactly. THis is realyl an external module (LeadQueueManager or whatever) with a lot of internal logic - this is absolutely not an anemic domain model. What does an employee know about call queue scheduling? Nothing ;)Allo
U
14

It's all in your head - consider rotation service to be a part of the domain model and the problem dissolves.

Rotation needs to keep information about many employees, so it belongs to neither lead, nor to any single employee object. It does to deserve to be a domain object in itself.

Just renaming "RotationService" to something like "Organization.UserSupportDepartment" makes it obvious.

Upbraiding answered 18/5, 2010 at 6:56 Comment(0)
E
0

If your domain model contains only roles and things, not activities as behavior, then it is anemic. However, I'm talking about behavior in regards to a model not an object. I talk of the difference between them in another answer... https://mcmap.net/q/263160/-is-anemic-domain-model-a-bundle-of-smart-services-and-stupid-object-without-a-defined-behavior

From your question, you break my first two domain analysis modelling rules:-

  1. Behavior modeled as (recorded) Activities are at the heart of a domain model. Add them first.
  2. Model domain activities as Classes, not methods.

I would add an activity "Enquiry" to the model. With it the model has behavior, and can combine and work as group of objects without an external controller or script.

EnquiryHandlerModel

Executioner answered 3/8, 2015 at 12:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.