避免贫血域模型 - 一个真实的例子
我试图理解贫血领域模型以及为什么它们被认为是反模式。
这是一个现实世界的例子。
我有一个 Employee 类,其中包含大量属性 - 姓名、性别、用户名等
public class Employee
{
public string Name { get; set; }
public string Gender { get; set; }
public string Username { get; set; }
// Etc.. mostly getters and setters
}
接下来我们有一个系统,该系统涉及在销售人员之间均匀轮流来电和网站查询(称为“潜在客户”)。该系统相当复杂,因为它涉及循环查询、检查假期、员工偏好等。因此,该系统目前被分成一个服务: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
}
}
然后,在我们网站查询表单的背面,我们有这样的代码:
public void SubmitForm()
{
var lead = CreateLeadFromFormInput();
var selectedEmployee = Kernel.Get<IEmployeeLeadRotationService>()
.SelectEmployee(lead);
Response.Write(employee.Name + " will handle your enquiry. Thanks.");
}
我并没有真正遇到这种方法的很多问题,但据说这是我应该尖叫的事情,因为它是一个贫血领域模型。
但对我来说,不清楚领先轮换服务的逻辑应该去哪里。它应该领先吗?它应该放在员工身上吗?
轮换服务所需的所有注入存储库等怎么样?考虑到大多数时候我们在与员工打交道时不需要任何这些存储库,那么如何将它们注入到员工中?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
在这种情况下,这并不构成贫血领域模型。贫血域模型专门用于验证和转换对象。因此,一个例子是外部函数实际上更改了员工的状态或更新了他们的详细信息。
在这种情况下,您将收集所有员工,并根据他们的信息选择其中一名员工。拥有一个单独的对象来检查其他对象并根据其发现的内容做出决定是很好的。拥有一个用于将对象从一种状态转换到另一种状态的对象是不行的。
在您的情况下,贫乏域模型的一个示例是使用一个外部方法
,该方法采用 Employee 对象并更新其本周的工作时间,确保在工作时间超过特定限制时引发标志。这样做的问题是,如果您只有 Employee 对象,那么您不知道如何在正确的约束内修改他们的工作时间。在这种情况下,处理方法是将 updateHours 方法移至 Employee 类中。这就是贫血领域模型反模式的关键。
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
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.
我认为你的设计在这里很好。如您所知,贫乏的领域模型反模式是对避免在领域对象中编码的任何行为的趋势的强烈反对。但相反,这并不意味着与域对象相关的所有行为都必须由该对象封装。
根据经验,本质上与域对象相关并且完全根据一个域对象实例定义的行为可以包含在域对象中。否则,为了保持职责清晰,最好将其放在外部协作者/服务中,就像您所做的那样。
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.
这一切都在您的脑海中 - 将轮换服务视为域模型的一部分,问题就解决了。
轮换需要保留许多员工的信息,因此它既不属于领导,也不属于任何单个员工对象。它本身确实值得成为一个域对象。
只需将“RotationService”重命名为“Organization.UserSupportDepartment”之类的名称就很明显了。
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.
如果您的领域模型仅包含角色和事物,而不包含作为行为的活动,那么它是贫乏的。但是,我谈论的是与模型有关的行为,而不是对象。我在另一个答案中谈到了它们之间的区别... https://stackoverflow.com/a/31780937/116442
从您的问题来看,您打破了我的前两个领域分析建模规则:- 建模
我会向模型添加一个活动“查询”。有了它,模型就有了行为,并且可以组合并作为一组对象工作,而无需外部控制器或脚本。
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://stackoverflow.com/a/31780937/116442
From your question, you break my first two domain analysis modelling rules:-
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.