【问题标题】:DDD duplicated domain logicDDD重复域逻辑
【发布时间】:2020-02-03 06:17:59
【问题描述】:

问题总结

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

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

详情

我查看了this question,它触及了我的问题,但我想就特定的设计选择获得一些意见。

我们有 2 个有界上下文来处理投资。假设它们具有以下类:

背景 - 投资

  • 投资
  • 投资说明

上下文 - 报告

  • 投资
  • 投资说明

投资环境进行实际投资。 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);

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

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);
        }
    }
}
...

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

问题

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

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

【问题讨论】:

    标签: .net domain-driven-design


    【解决方案1】:

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

    报告不应执行复杂的业务功能。简单的算术等是可以的,但如果您要复制您的域正在做的事情,可能会将其保留在域中。

    【讨论】:

    • 感谢您的意见,埃本。我同意你的报告声明。不过,在我们的解决方案中还有其他有关此问题的实例。我确实认为您可能会指出我们系统中的一个问题,我们正在使用 DDD 对不属于核心业务问题的问题进行建模。
    • 它会将其存储在写入侧 DB 还是读取 DB 上?我倾向于将任何形式的非规范化视为读取问题,因此将这些数据存储在单独的数据库/模式中,以免在存在数据差异时混淆哪些数据是事实来源。
    猜你喜欢
    • 2014-09-20
    • 2017-03-27
    • 2016-12-06
    • 1970-01-01
    • 2019-06-30
    • 2015-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多