如何在领域驱动设计中使用多态实体进行领域建模?

How to Approach Domain Modelling with Polymorphic Entity in Domain-Driven Design?

我正在重建一个会计软件。该软件可以处理多种类型的交易,例如销售、库存补货和费用。我没有会计背景,但我在网上学到了一些概念。我正在尝试根据这些原则对域进行建模:

  1. 一个日记帐分录由多个记录组成,每个记录要么是借方,要么是贷方。在给定的日记帐分录中,这些记录的借方和贷方总额必须相等(平衡)
  2. 必须根据源文档创建日记帐分录。不能无缘无故地简单地创建一个日记条目。

根据这些,我得出结论,必须存在这些实体:

  1. JournalEntry 个实体,其中有许多 JournalEntryItem 个值对象。该实体负责保持其所有条目处于平衡状态。
  2. SourceDocument 实体,其中包含正在进行的交易的一些信息

JournalEntitySourceDocument都是聚合根,JournalEntity引用SourceDocument身份。

问题来了。作为日记帐分录创建基础的源文档可以是具有不同行为的不同事物。例如,一个'expense'源文件可能由一对费用类别和费用金额的列表组成,而一个'sales'源文件可能由相关存货、每个存货的金额和每个存货的价格组成每一个单元。

这让我想到了模型中的另一种方法:

  1. 创建一个摘要 SourceDocument class,具有创建日期和身份属性等共享属性
  2. 创建 SalesSourceDocumentExpenseSourceDocument 等,从摘要 SourceDocument
  3. 扩展而来
  4. JournalEntry 实体仍然需要引用 SourceDocument 身份。

虽然这很有道理,但我认为这不是在 DDD 中对问题建模的最佳方式,特别是因为它需要有一个抽象实体。

从我在网上阅读的有关DDD的资料来看,当遇到需要抽象实体的问题时,我需要将其分离到不同的限界上下文中。但是,我不确定它是否可以完成,因为 SourceDocument 身份可能 return 在 'sales' 上下文中的具体 SalesSourceDocument,但它也可能 return 'expense' 上下文中没有任何内容。

解决这个问题的最佳方法是什么?

What is the best way to approach this problem?

将您的单独用例视为单独的,直到您发现需要调整它们的领域问题。

您的销售单据和费用单据是不同的东西,您的数据模型应该反映出:与每个单据相关的信息存储在不同的表中;这些表的架构会随着对特定于它们管理的域的更改的响应而发展。

Duplication is far cheaper than the wrong abstraction -- Sandi Metz

我认为这里要认识到的重要一点是 源文档 术语是描述 期刊条目文档,从JournalEntry的角度来看。文档是它们自己的 AR,它很可能在没有 journal 的情况下存在,那么为什么要根据与另一个 AR 的关系来命名它们的抽象?

源文档很可能是描述与期刊条目相关的文档的必要术语,而且它很可能是您的模型也是如此,但可能采用值对象的形式,例如 SourceDocument { id, type }.

这并不意味着如果有用的话,您也不能拥有 Document 抽象:没有理由不惜一切代价避免有用的多态性。

我的分析可能有误(无论如何所有模型都是错误的),但我只是想给你一个关于特定建模方面的不同视角。

I am rebuilding a software in accounting. This sofware can handle many types of transactions, e.g. sales, inventory restock, and expenses. I have no background in accounting, but I learned some concepts online.

如果您可以在线了解您的整个域,那么它很可能是一个通用的子域,您很可能可以在其中购买现成的产品。与其上网尝试自己想出一个模型,不如让领域专家参与进来:也许你的整个愿景在这里都是错误的。

此外,考虑到 源文档 很可能存在于不同的上下文中,Accounting 上下文似乎可以充当支持不明确了解或不依赖其他 BC 的 BC。

这是其中一个答案可能会变得很长的问题,所以我会尽力保持简短。我不是会计专家,但在我的职业生涯中我不得不实施 很多 会计。因此,当我有时间(一天)时,我会 developing one of those generic sub-domains

我的一个建议是不要将 classification 构建为代码结构。然而,识别 class化并不总是那么容易。一个“简单”的例子可能是 Customer。我们可以有 GoldCustomerSilverCustomer。我们可以从 abstract Customer 中提取 class 这些。我们必须决定我们为什么要这样做。例如,如果要确定每个周期需要的最低订单量,并且 upgrade/downgrade 客户 Type 才有资格获得一些折扣,那么可以很容易地将其抽象为自己的概念。如果业务决定添加 PlatinumCustomerBronzeCustomer 那么我们必须更改代码以继承 abstract class。如果我们宁愿将该概念建模为可以通过额外的 实例 (数据)定义的东西,那么我们不需要具有表示这些的代码结构:

public class CustomerDiscount
{
    public int Type { get; }
    public decimal MinimumSpend { get; }
    public int Period { get; }
    public decimal DiscountPercent { get; }
}

我的意思是,可以现成购买的通用子域(如@p|a|x 所述)不知道任何其他域中的任何概念,但需要能够将需求表示为与其领域特定语言相关的一组实例。

会计的核心是关于有借方和贷方的 Account。从那里开始,使用 Journal 对交易进行分组并具有某种形式的 PostingRule 可以表示将金额细分为交易。如果没记错的话,Martin Fowler 在他的分析模式书中有一些会计方面的内容。您的帐户也 class 使用会计科目表 (COA) 进行了化,有时这些是标准化的 (SCOA)。这些 class 使事情变得有趣的原因在于它们通常还决定了您的发帖规则。

我不喜欢你领域中的抽象 classes,因为它们往往会使事情复杂化,但正如@p|a|x 所提到的,没有理由完全避免它们。我发现 abstract classes 和继承通常比业务域模型更适合技术问题。如果您有一些业务方面的技术解决方案,那么您可能会发现它们很有用。如果您确实发现自己正在考虑任何类型的继承,请尝试查看您是否不能使用其他一些 concept/class.

来表示这种关系