【问题标题】:Issues with service layers and domain models服务层和域模型的问题
【发布时间】:2012-04-06 21:16:04
【问题描述】:

我正在开发一个具有在域模型上运行的服务层的应用程序。在当前设计中,我通过服务层向上传递域对象(例如,在调用 EmploymentService.getEmployee() 时返回一个 Employee 域对象,但要求对对象执行的操作通过服务进行(例如 EmploymentService.transferEmployee( int employeeId, int newLocationId)。(示例顺便说一句,是人为的)。

我觉得这有点不对劲。一,它看起来像过程编程。第二,域对象具有像Employee.setLocationId 这样的设置器,客户端可以调用它当然不会将员工转移到新位置,因为假设转移员工所需的协调不同系统的所有复杂操作都在服务中层。

如果我可以对客户端隐藏设置器,我会感觉更好,但是不同包中的 ServiceLayer 和 DAO 都需要能够访问域对象的设置器。

这种事情可以吗,还是有更好的方法? (此外,任何具有底层域模型的服务层的真实示例都将受到欢迎!)

另外,我已经阅读了贫血域模型反模式,我不认为我落入了那个陷阱,但我不完全确定!

【问题讨论】:

  • 我不确定它是否能完全解决问题,但如果可能的话,传递业务对象而不是原语不是更有意义吗?例如:EmploymentService.transferEmployee(Employee emp, Location newLoc) 无论哪种方式,+1 表示关心。

标签: java design-patterns domain-driven-design


【解决方案1】:

首先,客户调用 Employee.Transfer() 的问题是您实际上并不想要的: 我喜欢只从我的服务层返回 DTO。这些 DTO 包含数据但没有方法。这解决了客户端调用 Employee.Transfer() 的问题。

接下来,在EmploymentService.transferEmployee() 中包含所有代码的问题。你说它感觉不对,因为它看起来像过程编程。解决方案是在您放入 Service 的逻辑和放入 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 调用一起)?
  • 我会这样做:LocationService.AddEmployee(Guid locationId, Guid employeeId) 或者这个:LocationService.AddEmployee(LocationDto l, EmployeeDto e)。我更喜欢只有身份证的,其余的都是多余的。您也可以这样做:TransferEmployeeService.Execute(Guid locationId, Guid employeeId)
【解决方案2】:

首先,虽然你说你的例子是人为的,但我想说EmploymentService.transferEmployee(int employeeId, int newLocationId) 有点奇怪。通常您会将Employee 转移到Location。在 Java 代码中处理 id 是不寻常的。大多数 ORM 都会为您处理。

至于您的问题,我会将转移Employee 的逻辑放在Employee 本身中。这样,如果没有进行适当的更改,就不可能有人打电话给Employee.setLocation(Location)。这比试图从某些对象中隐藏二传手要好得多。

正如Anemic Domain Model 的维基百科页面中所述,该模式描述了一个系统,其中域转换由单独的对象控制。我个人认为转移Employee确实是一种转换,这种转换的逻辑可以而且应该在Domain层。当然,这些问题总是有点个人喜好问题,所以你可能会有不同的想法。

我发现 Martin Fowler 的 original article on the issue 是一个很好的论据,可以让您的域对象能够自我转换。

【讨论】:

  • 谢谢,蒂姆。转移 Employee 涉及与不属于域的实体进行交互的方面呢?员工调动时向管理员发送电子邮件?这似乎更适合服务级别,并且它将那种非域功能巧妙地包装在让我适合的域对象周围。
  • 我倾向于同意你关于发送需要服务的电子邮件。如果您想确保每次都更新Employee 时发送电子邮件,我可能会在更新时触发PropertyChangeEvent 并让Employee 在@987654333 时向电子邮件服务侦听器注册@ 叫做。但是,如果您只是有时想要发送电子邮件,请使用可以发送电子邮件的服务。然后你可以在拨打Employee.setLocation之后拨打Service.sendEmail
  • 实际上,我的意思是,让一个电子邮件对象注册为每个Employee 的侦听器,而不是相反。
  • 我个人更喜欢将我的域对象作为底层记录的包装器,并将域逻辑保留在中心位置的其他地方,而不是分布在整个域对象中。这样,当域逻辑发生变化时,不可避免地会发生变化,它是相当本地化的。但正如你所说,这是一个品味问题,这样做并没有错。
【解决方案3】:

对客户端隐藏 setter 的一种常用方法是将客户端需要的所有 getter 封装在 IEmployee 接口中,并将服务 API 编码到该接口。这样,setter 就对客户端隐藏了,但对于需要它们的服务和 DAO 来说仍然存在。

【讨论】:

  • 好建议,我喜欢。谢谢。
猜你喜欢
  • 2014-02-15
  • 2014-04-03
  • 1970-01-01
  • 2013-05-27
  • 1970-01-01
  • 2011-11-18
  • 2020-04-16
  • 1970-01-01
  • 2012-02-04
相关资源
最近更新 更多