服务层和领域模型存在的问题

4
我正在开发一个应用程序,它有一个操作领域模型的服务层。在当前设计中,我通过服务层传递领域对象(例如,在调用EmploymentService.getEmployee()时返回一个Employee领域对象),但需要对对象执行的操作通过服务层进行(例如EmploymentService.transferEmployee( int employeeId, int newLocationId),这里的例子是编造的)。
我觉得有些不太对头。首先,它似乎像过程式编程。其次,领域对象有设置器(如Employee.setLocationId),客户端可以调用该设置器,当然不会将员工转移到新位置,因为所有协调不同系统所需的复杂操作都在服务层中。
如果我可以隐藏客户端的设置器,并使不同包中的ServiceLayer和DAO能够访问领域对象的设置器,那么我会感觉更好一些。
这种方法是否可行,还是有更好的方法?(此外,欢迎任何带有基础领域模型的服务层的实际示例!)
此外,我已阅读了无血统领域模型反模式,我不认为我会掉入这个陷阱,但我并不完全确定!

5
虽然我不确定这样做能否完全解决问题,但在可能的情况下传递业务对象而不是基本类型似乎更有意义。例如:EmploymentService.transferEmployee(Employee emp, Location newLoc) 无论如何,对于你的关注点点赞。 - Matt Ball
3个回答

3

首先,客户端调用Employee.Transfer()方法的问题是你不希望发生的: 我喜欢从我的服务层仅返回DTO。这些DTO包含数据而没有方法。这解决了客户端调用Employee.Transfer()的问题。

接下来,EmploymentService.transferEmployee()中所有代码的问题。你说它看起来像过程式编程,感觉不对劲。解决方案是找到在Service和Domain对象之间放置逻辑的良好平衡点。例如:

Domain对象执行以下操作:

  • 检查是否已删除
  • 检查是否已在该位置
  • 等等

服务层执行以下操作:

  • 加载员工
  • 调用Employee.Transfer()
  • 向员工发送电子邮件
  • 向位置经理发送电子邮件
  • 等等

在此代码中,我可能会使用Location Domain对象:

public class Location
{
   public void AddEmployee(Employee emp)
   {
      if(!IsFull)
         Employees.Add(emp);
   }

   public void RemoveEmployee(Employee emp)
   {
      Employees.Remove(emp);
      If(Employees.Count < 100)
         IsFull = false;
   }
}

谢谢!所以,如果您选择在服务层公开无方法的DTO,则对于上面的代码,我假设在服务层中您会有类似LocationService.AddEmployee( Location l, Employee e)的东西?此外,您是否在DTO中包含setter以便于更新(与服务层类的Update调用一起使用)? - HolySamosa
我会这样做:LocationService.AddEmployee(Guid locationId, Guid employeeId) 或者这样做:LocationService.AddEmployee(LocationDto l, EmployeeDto e)。我更喜欢只有ID的那些,其他的都是多余的。你也可以这样做:TransferEmployeeService.Execute(Guid locationId, Guid employeeId)。 - user1209120

2
首先,虽然您说您的例子是人为构造的,但我想说EmploymentService.transferEmployee(int employeeId, int newLocationId)有点奇怪。通常您会将Employee调动到Location。在您的Java代码中处理id是不寻常的。大多数ORM将为您处理这个问题。
至于您的问题,我会把转移Employee的逻辑放在Employee本身中。这样就没有人会调用Employee.setLocation(Location)而不进行适当的更改。这比尝试从某些对象中隐藏setter要好得多。
正如维基百科页面贫血领域模型所述,该模式描述了由单独对象控制领域转换的系统。我个人认为,调动Employee确实是一种转换,这种转换的逻辑可以并且应该在领域层中。当然,这些问题总是有些品味上的差异,所以您可能会有不同的看法。
我发现马丁·福勒关于此问题的原始文章是一个非常好的让您的领域对象能够自我转换的论据。

谢谢,Tim。那么,如果涉及与实体交互的员工转移方面并不真正属于域的一部分,例如在员工被调动时向管理员发送电子邮件,这方面该怎么办呢?似乎将这种非域功能整洁地包装在域对象周围更适合服务级别,而且正是这种包装让我感到困扰。 - HolySamosa
1
我倾向于同意您的观点,即发送电子邮件需要在服务中完成。如果您想确保每次更新“员工”时都会发送电子邮件,我可能会在更新时触发“PropertyChangeEvent”,并在调用“findEmployee”时让“Employee”注册电子邮件服务侦听器。但是,如果您只想有时发送电子邮件,请使用可以发送电子邮件的服务。然后,在调用“Employee.setLocation”之后,您可以调用“Service.sendEmail”。 - Tim Pote
实际上,我的意思是让一个电子邮件对象在每个“员工”中注册为侦听器,而不是相反的方式。 - Tim Pote
1
我个人更喜欢将我的领域对象仅作为底层记录的包装器,将领域逻辑保留在中心位置而不是分散在领域对象中。这样当领域逻辑改变时,通常只需局部修改。但正如你所说,这是一个品味问题,这种做法也没有错。 - Alex

1

隐藏setter方法不让客户端调用的一种常见方法是将客户端需要的所有getter方法封装在一个IEmployee接口中,并将服务API编码为该接口。这样,setter方法对于需要它们的服务和DAO仍然可用,但对于客户端则被隐藏。


好的建议,我喜欢。谢谢。 - HolySamosa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接