DDD聚合序列化的最佳做法


23

根据DDD,域逻辑不应被序列化,对象关系映射等技术问题所污染。

那么,如何在不通过getter和setter公开公开状态的情况下序列化或映射聚合状态?我已经看到了很多示例,例如存储库实现,但是实际上所有示例都依赖于实体上的公共访问器和用于映射的值对象。

我们可以使用反射来避免公共访问器,但是IMO这些域对象仍将隐式依赖于序列化问题。例如,在不调整序列化/映射配置的情况下,您无法重命名或删除私有字段。因此,您必须考虑序列化,而应将重点放在域逻辑上。

那么,要遵循的最佳妥协是什么?与公共访问器一起生活,但要避免将它们用于映射代码以外的其他用途?还是我只是想念一些明显的东西?

我对序列化DDD域对象(由实体和值对象组成的聚合)的状态特别感兴趣。这与无状态服务在简单数据容器对象上运行的常规脚本事务脚本场景中的序列化无关

Answers:


12

物体种类

为了便于讨论,我们将对象分为三种不同的类型:

业务领域逻辑

这些是完成工作的对象。他们将钱从一个支票帐户转移到另一个,执行订单,以及我们期望商业软件采取的所有其他行动

域逻辑对象通常不需要访问器(getter和setter)。相反,您可以通过以下方式创建对象:将其依赖项通过构造函数处理,然后通过方法操作对象(告诉您,不要问)。

数据传输对象

数据传输对象是纯状态。它们不包含任何业务逻辑。他们将始终具有访问器。它们可能带有或不带有setter,具体取决于您是否以不变的方式编写它们。您将在构造函数中设置字段,并且它们的值在对象的生存期内不会改变,或者您的访问器将被读/写。实际上,这些对象通常是可变的,以便用户可以对其进行编辑。

查看模型对象

视图模型对象包含可显示/可编辑的数据表示形式。它们可能包含业务逻辑,通常仅限于数据验证。视图模型对象的一个​​示例可能是InvoiceViewModel,其中包含一个Customer对象,一个Invoice Header对象和Invoice Line Items。视图模型对象始终包含访问器。

因此,在不包含字段访问器的意义上,唯一的“纯”对象将是“域逻辑”对象。序列化此类对象可保存其当前的“计算状态”,以便以后可以检索它以完成处理。视图模型和DTO可以自由序列化,但是实际上它们的数据通常保存到数据库中。

序列化,依赖关系和耦合

确实,序列化会创建依赖关系,从某种意义上说,您必须反序列化为兼容的对象,但这并不一定意味着您必须更改序列化配置。好的序列化机制是通用的。他们不在乎是否更改属性或成员的名称,只要它仍然可以将值映射到成员即可。实际上,这仅意味着您必须重新序列化对象实例,以使序列化表示(xml,json等)与您的新对象兼容。无需对串行器进行任何配置更改。

确实,对象不应该关心它们的序列化方式。您已经描述了可以将此类问题与域类分离的一种方法:反射。但是,序列化程序应该考虑如何对对象进行序列化和反序列化。毕竟是它的功能。使对象与序列化过程脱钩的方法是使序列化成为通用功能,能够在所有对象类型上工作。

人们感到困惑的一件事是,必须在两个方向上进行去耦。它不是; 它只需要朝一个方向工作。实际上,您永远无法完全解耦。总会有一些耦合。松散耦合的目的是使代码维护更加容易,而不是删除所有依赖项。


我同意你对脱钩的观点。序列化程序取决于域对象,没关系。但并非相反。但是,我不同意您对域对象的公共访问器的看法。实际上,他们经常有它们,是的。但是IMO最好在干净的面向对象设计中实现域逻辑:告诉,不要问。但是仍然需要用于映射目的的访问器(ORM,序列化,GUI ...)。如果可能的话,那就是我要避免的事情。
EagleBeak 2014年

如果您没有访问者,您如何计划访问您的字段?
罗伯特·哈维

实际上,我所指的不是您描述的三种对象,而是DDD术语中的“聚合”及其子对象(实体,值对象)。我现在意识到,我的问题对此还不够明确。抱歉! 请在上方查看我的修改。
EagleBeak 2014年

1
这基本上是一个未解决的问题-您不能同时暴露DTO的封装,解耦和序列化\编码是实现折衷的一种方法。但是,侵入性的方式要少得多:yegor256.com/2016/07/06/data-transfer-object.html
Basilevs

1
这就放弃了封装,任何人都可以实现或使用friend类来读取对象的内部。
Basilevs

-1

序列化的基本目的是确保一个系统生成的数据能够被一个或多个兼容系统使用。

最简单,最可靠的序列化方法是将数据转换为类型无关的格式,该格式将结构保持为简单易用的格式。例如,最普遍的序列化格式(即JSON,XML)使用定义良好的基于​​文本的格式。文本易于生成,传输和使用。

使用这些格式之一可能不理想的原因有两个。

  1. 效率

    将所有数据转换为基于文本的等效项涉及固有成本。如果文本是表示所有不同形式的数据的最有效方法,则数据类型将不存在。另外,这些格式的结构对于异步或部分检索数据的子集来说并不理想。

    例如,XML和JSON假定将使用从头到尾进行写入和读取的数据。为了处理内存不足的非常大的数据集,使用数据的系统可能需要部分处理数据的能力。在那种情况下,可能需要专用的序列化/反序列化实现来处理数据。

  2. 精确

    将数据从其预期类型序列化/反序列化为数据不可知类型所需的强制转换会导致精度损失。

有人可能会说,生成对象和数据的二进制表示形式显然是最有效,最准确的解决方案。主要缺点是,所有消耗和产生数据的系统的实现都必须保持兼容。从理论上讲,这是一个简单的约束,但随着生产系统随着时间的推移趋于变化/发展,在实践中保持这种噩梦。

照这样说。一般而言,将序列化/反序列化与特定于域的细节解耦是合理的,因为通用格式更健壮,在各种系统中得到更好的支持,并且使用时几乎不需要增加维护开销。


抱歉,但这不能回答我的问题。这是关于将域对象与序列化脱钩,而不是涉及序列化的原因或各种格式的利弊。如何在不公开公开其私有状态的情况下序列化域对象?
EagleBeak 2014年

@EagleBeak哦,我不知道您的关注是专门针对处理私人成员。在您的情况下,您可以使用二进制序列化(假设接收系统遵循域对象创建时所遵循的相同规则/结构),或者编写一些逻辑来在序列化之前仅提取公共数据。
Evan Plaice 2014年

我认为“通常”的假设是序列化为通用格式(例如xml,json)的数据将是公开的,并且特权是通过ACL或其他等效项通过API控制的。通用序列化/反序列化更多地涉及将数据与从一个系统到另一个系统的业务逻辑解耦。
Evan Plaice 2014年

我同意通常在要序列化的对象上使用公共访问器。但是我仍然想更多地了解它与DDD的关系以及它对域逻辑封装的强烈关注。是否所有DDD从业者都只是通过公共访问器公开域模型的状态以进行序列化(并且在示例中从未提及)?我对此表示怀疑。请不要误会我的意思。非常感谢您的投入。只是我对另一个方面感兴趣。(到目前为止,我认为我的问题不太清楚,但是罗伯特·哈维的回答和你的回答让我开始思考。)
EagleBeak 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.