逻辑何时属于业务对象/实体,何时属于服务?

时间:2011-01-29 23:23:29

标签: .net domain-driven-design entity business-logic

在尝试理解领域驱动设计时,我一直回到一个我似乎无法明确回答的问题。

如何确定哪个逻辑属于域实体,哪个逻辑属于域服务?

实施例: 我们有一个在线商店的订单类。此类是实体和聚合根(它包含OrderItems)。

Public Class Order:IOrder
{
    Private List<IOrderItem> OrderItems

    Public Order(List<IOrderItem>)
    {
        OrderItems = List<IOrderItem>
    }

    Public Decimal CalculateTotalItemWeight()
    //This logic seems to belong in the entity.
    {
        Decimal TotalWeight = 0
        foreach(IOrderItem OrderItem in OrderItems)
        {
            TotalWeight += OrderItem.Weight
        }
        return TotalWeight

    }
}

我想大多数人会同意CalculateTotalItemWeight属于实体。但是,在某些时候我们必须将此订单发送给客户。要做到这一点,我们需要做两件事:

1)确定发运此订单所需的邮资费率。

2)确定邮资费率后打印运输标签。

这两个操作都需要在Order实体之外的依赖项,例如外部Web服务来检索邮资费率。我们该如何完成这两件事?我看到了几个选项:

1)直接在域实体中编码逻辑,如CalculateTotalItemWeight。然后我们打电话:

Order.GetPostageRate
Order.PrintLabel

2)将逻辑放在接受IOrder的服务中。然后我们打电话:

PostageService.GetPostageRate(Order)
PrintService.PrintLabel(Order)

3)为在Order上运行的每个操作创建一个类,并通过Constructor Injection将该类的实例传递给Order(这是选项1的变体,但允许重用RateRetriever和LabelPrinter类): / p>

 Public Class Order:IOrder
{
    Private List<IOrderItem> OrderItems
    Private RateRetriever _Retriever
    Private LabelPrinter _Printer

    Public Order(List<IOrderItem>, RateRetriever Retriever, LabelPrinter Printer)
    {
        OrderItems = List<IOrderItem>
        _Retriever = Retriever
        _Printer = Printer
    }

    Public Decimal GetPostageRate
    {
        _Retriever.GetPostageRate(this)
    }

     Public void PrintLabel
    {
        _Printer.PrintLabel(this)
    }
}

如果有的话,您为这种逻辑选择了哪一种方法?您选择的理由是什么?最重要的是,是否有一套指导方针可以让您选择?

4 个答案:

答案 0 :(得分:1)

我会用(2)。

它不会为您的订单商品增加额外的复杂性。

对我而言,似乎是帮助服务的自然使用。

更新:回复评论:维基页面声明:

  

贫血领域模型:有了这个   模式,逻辑通常   在单独的类中实现   转换域的状态   对象

答案 1 :(得分:1)

我倾向于有外部服务来确定运费。对我来说,这是应用程序逻辑而不是特定于订单的逻辑。例如,您可能会决定为特定规模的订单或特定忠诚客户群提供免费送货服务。对我而言,逻辑将倾向于独立于订单的构建方式而改变。

我很可能会让代码负责将订单(某种订单处理器服务,在应用程序层或命令处理程序中)交给服务以获取运费,然后通过按顺序进入,所以我猜选项2。

对于打印运输标签,我倾向于让域名引发http://www.udidahan.com/2009/06/14/domain-events-salvation/的事件。然后,单独的处理程序将处理打印标签。同样,这样做的逻辑是,您打印标签的方式可能与您构建订单的方式不同,因此将它们分开是有意义的。使用域事件似乎是确保在正确的时间打印标签的最简洁方法,而无需订单(或订单处理器)了解打印逻辑。

答案 2 :(得分:1)

如果您要访问外部网络服务以获得邮资费率,最好在应用层创建界面,因为埃文本身建议如果您想与外部网络服务交谈,您应该在应用程序中构建界面您可以将服务实现注入到域对象中。对于打印运输标签,因为标签仅在确定邮资费率时打印,所以像PostageRateConfirmed这样的事件也会引发您的域名。

http://danhaywood.com/2010/04/30/accessing-domain-services-from-entities/

答案 3 :(得分:0)

我的观点: 域是包含应用程序逻辑的域,没有基础架构。逻辑是,当确认订单时,打印标签并确定运费。这应该在域中。

然后,基础架构可以完成域想要执行的操作。域可以通过消息传递或事件让基础架构知道。

这样,没有基础设施泄漏到域中,您只需要一种方法将消息传输出域。