【问题标题】:DDD Entities making use of Services使用服务的 DDD 实体
【发布时间】:2011-01-23 18:21:46
【问题描述】:

我有一个应用程序,我正在尝试使用至少名义上的 DDD 类型的域模型构建,并且正在努力解决某个问题。

我的实体有一些业务逻辑,它使用我目前在某些域服务中拥有的一些财务计算和费率计算,以及我放入值对象中的一些常量值。

我正在纠结如何让实体使用域服务中的逻辑,或者这些服务中的逻辑是否属于那里。这是我目前所拥有的:

public class Ticket
{
    public Ticket(int id, ConstantRates constantRates, FinancialCalculationService f, RateCalculationService r)
    {
        Id = id;
        ConstantRates = constantRates;
        FinancialCalculator = f;
        RateCalculator = r;
    }

    private FinancialCalculationService FinancialCalculator { get; set; }

    private RateCalculationService RateCalculator { get; set; }

    private ConstantRates ConstantRates { get; set; }

    public int Id { get; private set; }

    public double ProjectedCosts { get; set; }

    public double ProjectedBenefits { get; set; }

    public double CalculateFinancialGain()
    {
        var discountRate = RateCalculator.CalculateDiscountRate(ConstantRates.Rate1, ConstantRates.Rate2,
                                                                ConstantRates.Rate3);

        return FinancialCalculator.CalculateNetPresentValue(discountRate,
                                                            new[] {ProjectedCosts*-1, ProjectedBenefits});
    }
}


public class ConstantRates
{
    public double Rate1 { get; set; }
    public double Rate2 { get; set; }
    public double Rate3 { get; set; }
}

public class RateCalculationService
{
    public double CalculateDiscountRate(double rate1, double rate2, double rate3 )
    {
        //do some jibba jabba
        return 8.0;
    }
}

public class FinancialCalculationService
{
    public double CalculateNetPresentValue(double rate, params double[] values)
    {
        return Microsoft.VisualBasic.Financial.NPV(rate, ref values);
    }

}

我觉得某些计算逻辑确实属于那些域服务,但我不太喜欢我必须从我的存储库中手动注入这些依赖项。是否有另一种方法可以对此进行建模?我不喜欢这样有错吗?

阅读过蓝皮书,但之前没有真正构建过这种风格的东西,我正在寻找指导。

编辑

感谢大家的反馈!根据我所听到的,听起来我的模型应该更像下面的样子。这个更好看?

public class Ticket
{
    public Ticket(int id)
    {
        Id = id;
    }

    private ConstantRates ConstantRates { get; set; }

    public int Id { get; private set; }

    public double ProjectedCosts { get; set; }

    public double ProjectedBenefits { get; set; }

    public double FinancialGain { get; set; }
}



public class ConstantRates
{
    public double Rate1 { get; set; }
    public double Rate2 { get; set; }
    public double Rate3 { get; set; }
}

public class FinancialGainCalculationService
{
    public FinancialGainCalculationService(RateCalculationService rateCalculator, 
        FinancialCalculationService financialCalculator,
        ConstantRateFactory rateFactory)
    {
        RateCalculator = rateCalculator;
        FinancialCalculator = financialCalculator;
        RateFactory = rateFactory;
    }

    private RateCalculationService RateCalculator { get; set; }
    private FinancialCalculationService FinancialCalculator { get; set; }
    private ConstantRateFactory RateFactory { get; set; }

    public void CalculateFinancialGainFor(Ticket ticket)
    {
        var constantRates = RateFactory.Create();
        var discountRate = RateCalculator.CalculateDiscountRate(constantRates.Rate1, constantRates.Rate2,
                                                                constantRates.Rate3);

        ticket.FinancialGain = FinancialCalculator.CalculateNetPresentValue(discountRate,
                                                            new[] {ticket.ProjectedCosts*-1, ticket.ProjectedBenefits});
    }
}

public class ConstantRateFactory
{
    public ConstantRates Create()
    {
        return new ConstantRates();
    }
}

public class RateCalculationService
{
    public double CalculateDiscountRate(double rate1, double rate2, double rate3 )
    {
        //do some jibba jabba
        return 8.0;
    }
}

public class FinancialCalculationService
{
    public double CalculateNetPresentValue(double rate, params double[] values)
    {
        return Microsoft.VisualBasic.Financial.NPV(rate, ref values);
    }

}

在这一点上,域模型最终变得相当贫乏,但随着我添加功能,它可能会有更多功能。

编辑 2

好的,我收到了更多反馈,也许我的“计算”服务更像是我的实体可以依赖的策略对象。这是另一个在实体中使用更多逻辑并利用这些策略对象的方法。对此有什么想法?直接在实体中实例化这些助手有什么问题吗?我不认为我会想在我的测试中模拟那些,但是我也不能在不测试这些策略对象的情况下测试 CalculateFinancialGain 方法。

public class Ticket
{
    public Ticket(int id, ConstantRates constantRates)
    {
        Id = id;
        ConstantRates = constantRates;
    }

    private ConstantRates ConstantRates { get; set; }

    public int Id { get; private set; }

    public double ProjectedCosts { get; set; }

    public double ProjectedBenefits { get; set; }

    public double CalculateFinancialGain()
    {
        var rateCalculator = new RateCalculator();
        var financeCalculator = new FinanceCalculator();
        var discountRate = rateCalculator.CalculateDiscountRate(ConstantRates.Rate1, ConstantRates.Rate2,
                                                                ConstantRates.Rate3);

        return financeCalculator.CalculateNetPresentValue(discountRate,
                                                            ProjectedCosts*-1, 
                                                            ProjectedBenefits); 
    }
}

public class ConstantRates
{
    public double Rate1 { get; set; }
    public double Rate2 { get; set; }
    public double Rate3 { get; set; }
}

public class RateCalculator
{
    public double CalculateDiscountRate(double rate1, double rate2, double rate3 )
    {
        //do some jibba jabba
        return 8.0;
    }
}

public class FinanceCalculator
{
    public double CalculateNetPresentValue(double rate, params double[] values)
    {
        return Microsoft.VisualBasic.Financial.NPV(rate, ref values);
    }

}

【问题讨论】:

  • 您能否描述一下“Ticket”与域的关系?
  • 我认为票是我的总根。它基本上是对应用程序的增强/缺陷请求。该应用程序正在尝试根据预计的成本/收益来计算特定维护票证的“价值”。
  • 您使用的是 IoC 模式吗?我建议将这些计算器移到您可以注入的属性中。将使测试负载更容易。
  • 是的,我使用的是 IoC 容器。现在我想起来了,我可以很容易地将这些注入我的实体。我也将我的存储库用作工厂,但我认为我现在需要将它们分开。据我了解,如果我使用的 ORM 会更难做(即注入依赖项),但我不在这里,所以应该很容易。

标签: c# domain-driven-design


【解决方案1】:

让您的服务接受 Ticket 实体作为参数。服务应该是无状态的,并且同一个服务应该能够向任意数量的实体提供其服务。

在您的情况下,我会将FinancialCalculatorServiceRateCalculatorService 从您的实体中提取出来,并使每个服务上的方法接受Ticket 实体作为参数。

花点时间阅读pg. 105 of Domain-Driven Design by Eric Evans

【讨论】:

  • 谢谢——我读这本书已经快一年了,很多细节都忘记了。因此,听起来某物是否是服务取决于它是否适合通用语言。在这种情况下,RateCalculatorService 绝对符合该域的要求。金融计算器,没有那么多。
  • 在过去的一周内,我刚刚从头到尾读完了这本书,所以我一直记忆犹新 =)
  • 在您的情况下,您所说的有些道理,您将拥有一个“财务计算器”,它可以作为一项服务,根据您通过询问“利率”获得的“财务收益”来计算“ Ticket' (并且票证只是使用“RateCalculator”来封装计算该费率的逻辑)。感觉就像“RateCalculator”可以是一项服务,这样您就不必在每次重构票证实体时都实例化它,但我会根据您所在领域的普遍语言做更有意义的事情。
【解决方案2】:

鉴于我们对这些课程的了解,我不认为它们真的是 blue book sense 中的服务,我会将计算器保留在Ticket 中。

FinancialCalculatorServiceRateCalculationService 都不依赖于域实体——它们都对原始值进行操作。应用程序不必担心如何计算票证带来的经济收益,因此将这些信息封装在票证本身内是很有价值的。

如果它们确实不依赖于域实体,请考虑将它们视为“独立类”​​而不是“服务”(再一次,在蓝皮书术语中)。 Ticket 依赖于策略对象(FinancialCalculatorRateCalculator)当然适用,这些对象本身没有外来依赖,并且本身不修改域实体的状态。

编辑 2 更新。我认为使计算器成为独立类的优势之一是您可以独立于Ticket 对它们进行测试。严格来说,工单不负责执行这些计算,他们负责正确调用那些协作类。因此,我倾向于使它们像您最初的示例中那样可注入/可模拟。

【讨论】:

  • 将它们注入我的实体可以回到我最初的一些痛苦,因为这是我需要在我的存储库中做的一些额外工作。我想我明白为什么这是一个引起如此多争论的主题了:-)
【解决方案3】:

我会说服务使用实体,而不是相反。

另一件事,不确定你的域,但你确定票是一个实体而不是一个值对象吗?

【讨论】:

  • 我对实体的理解是它在域的上下文中具有特定的身份。在这种情况下,Ticket 上的 Id 基本上是案例编号。那么,我认为是吗? :-) 你会说不同吗?回复:使用实体的服务,然后您是否会使用 FinancialGainCalculationService 对其进行建模,该服务使用票证和其他两个服务来获得该财务收益编号?如果业务逻辑在服务中,那不会导致贫乏的领域模型吗?谢谢!
  • 所以您的业务语言中使用票证 ID 作为参考?我不认为实体应该能够执行改变自己状态的工作......我相信这项工作属于服务级别,然后可以将这些更改重新协调到 repo 中。
  • 让我澄清一下,在经典的 Order 模型中。一个订单,作为一个聚合,可以向自己添加产品详细信息行,但是我不认为订单应该能够放置自己,这会将新状态保存回该聚合的存储库中......我相信这种状态的保存应该发生在服务级别
  • 另一个问题:您的计算器是否依赖存储库?如果不是,它们可能不是服务。 DDD 不仅仅是实体、值对象和服务的世界。可以有其他“帮助”对象来推动事物。
  • @E Rolnicki - 虽然同意并非所有东西都必须是实体、值对象和服务,但如果手头的对象不访问存储库,它可能不是我觉得很难遵循的服务.据我了解,对 Service 进行分类的主要内容是它没有状态或身份,而是执行操作或抽象复杂任务。
【解决方案4】:

您实际上已经解决了一个已经有很多讨论的问题。赛道两边都有信徒,所以你需要自己决定什么是最有意义的。

就我个人而言,我没有让我的实体使用服务,因为它围绕“我如何干净利落地将服务引入我的实体?”这方面做了大量工作。问题。

在我看来,CalculateFinancialGains() 更像是一个服务级别调用。这确实导致 Ticket 非常贫血,但我认为它还有其他行为?如果不是,那可能是一种气味......

【讨论】:

    【解决方案5】:

    这个问题实际上是“清洁代码”一书中的一个讨论示例(第 96-97 页)。潜在的问题是是否使用过程方法或面向对象的方法。希望我在这里重复几个部分没有违规,但这是 Bob Martin 声明的指导:

    过程代码(使用数据结构的代码)可以轻松添加新功能,而无需更改现有数据结构。另一方面,OO 代码可以轻松添加新类,而无需更改现有功能。

    恭维也是真的:

    程序代码使得添加新数据结构变得困难,因为所有函数都必须更改。 OO 代码使添加新功能变得困难,因为所有类都必须更改。

    我的理解是,DDD 的“值类型”就是 Bob Martin 所说的数据结构。

    希望这会有所帮助,而不仅仅是增加噪音:)

    【讨论】:

      猜你喜欢
      • 2014-12-27
      • 2011-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-31
      • 1970-01-01
      相关资源
      最近更新 更多