DDD 重复域逻辑

DDD duplicated domain logic

问题总结

我们有 2 个限界上下文,它们表示相同的数据实体,但提供不同的功能来对所述实体进行操作。一些更复杂的计算开始以相同的方式出现在这两种情况下。我们是否复制粘贴并编写单元测试?我们是否映射并提取了一个通用的计算策略对象?

主要的 objective 是可维护性。其他都是次要的。

详情

我看过 this question,其中涉及到我的问题,但我想就特定的设计选择获得一些意见。

我们有 2 个处理投资的限界上下文。假设这些具有以下 classes:

背景 - 投资

上下文 - 报告

投资环境进行实际投资。 IE。在银行账户和投资产品之间转移资金等等。

报告上下文是只读的,并公开了一些方法来计算一些投资细节。

碰巧,一项计算投资者仍可投资于产品的新规则包含的逻辑将与报告投资者仍可投资于产品的金额完全相同。

下面是示例伪代码,供那些在查看代码时能更好地理解的人使用:

namespace Investing
{
    public class Investment
    {
        private List<InvestmentIsntruction> Instructions {get;private set;}

        public decimal GetRemainingContributionAllowed()
        {
            return SOME_PREDEFINED_LIMIT - Instructions.Sum(x => x.Amount);
        }
    }
}

namespace Reporting
{
    public class Investment
    {
        private List<InvestmentIsntruction> Instructions {get;private set;}

        public decimal GetRemainingContributionAllowed()
        {
            return SOME_PREDEFINED_LIMIT - Instructions.Sum(x => x.Amount);
        }
    }
}

此处计算极限的公式已简化。假设实际的公式要复杂得多,我们如何有效地分享这个逻辑?我们对解决方案不太挑剔。我们想要一个可以从设计角度适当激励的解决方案。

到目前为止的解决方案

1) 只需复制粘贴逻辑并编写单元测试即可确保结果始终相同。推理是上下文毕竟不同。也许将来报告公式可能会有所不同,例如,上下文之间的联系很容易被打破(调整单元测试)。

2) 提取一个常用的计算器对象,接口如下:

decimal GetRemainingAllowance(IEnumerable<IInvestmentIntsruction> instructions, decimal predefinedLimit);

在每个上下文的 classes 上实施 IInvestmentInstruction 接口。这里的动机当然是统一放一个计算的地方。我们不喜欢的是 InvestmentInstruction class 上的接口。如果我们以后有更多的重叠计算,可能会有几十个这样的接口要实现。例如:

public class InvestmentInstruction : ILimitCalculationInstruction, IOtherCalcuationInstruction, IYetSomethingElse{}

每个接口都公开一些对一些常见的重叠计算有用的东西。

3) 到目前为止,这是我们的首选解决方案 - 提取一个完全独立的计算器对象,它具有自己的接口,如解决方案 2 中所示。然后在该计算器需要使用域对象时将它们映射到通用模型。例如:

public class Calculator
{
    public decimal GetRemainingAllowance(IEnumerable<IInvestmentIntsruction> instructions, decimal predefinedLimit){...}
}

public class DedicatedCalcuatorInvestmentIsntructionModel : IInvestmentInstruction {}

namespace Investing
{
    public class Investment
    {
        private List<InvestmentIsntruction> Instructions {get;private set;}

        public decimal GetRemainingContributionAllowed(Calculator calculator)
        {
            var mappedInstructions = this.Instructions.Select(x => new DedicatedCalcuatorInvestmentIsntructionModel(){//Assign properties} );

            return calculator.GetRemainingAllowance(mappedInstructions, SOME_PREDEFINED_LIMIT);
        }
    }
}

namespace Reporting
{
    public class Investment
    {
        private List<InvestmentIsntruction> Instructions {get;private set;}

        public decimal GetRemainingContributionAllowed(Calculator calculator)
        {
             var mappedInstructions = this.Instructions.Select(x => new DedicatedCalcuatorInvestmentIsntructionModel(){//Assign properties} );

            return calculator.GetRemainingAllowance(mappedInstructions, SOME_PREDEFINED_LIMIT);
        }
    }
}
...

这似乎以一种体面的方式解决了重复问题,而没有将各种上下文与计算过于紧密地耦合在一起。这也是感觉最像是过度设计问题的解决方案。

问题

如果有的话,您会推荐我们的哪些解决方案?还有什么其他方法可以解决这个问题?

如果我未能提供一些重要信息,请告诉我。我对这个问题很深入,所以很容易忘记哪些部分不是不言自明的。

另一种选择是对所需的 GetRemainingContributionAllowed 值进行非规范化并存储它。只要对 Investment 进行了相关更改,就可以存储该值。另一种方法是让您的报告生成有点像 process,其中第一步是让域计算值,然后存储该值。然后报告位将只读取它。

报表不应执行复杂的业务功能。简单的算术之类的是可以的,但如果你正在复制你的域正在做的事情,也许将它保留在域中。