领域对象可以和持久化对象一样吗?
Can domain object be same as persistence object?
我们的团队有一个基本上是 MIS 的项目。
我们使用的架构是 CQRS with DDD(领域驱动设计)。
我们在持久层中有持久性对象,在域层中有域对象,用于携带用户输入信息的数据传输对象,以及用于在特定页面上显示数据的视图对象。
我觉得这样的设计很棒。但是在实施项目的过程中,我们发现了一些问题让我们很受阻。最恶心的是我们必须写很多转换器来在两个不同的层之间转换对象,比如 PO 到 DO,DO 到 PO,DTO 到 DO。这些转换器中有太多的 get 和 set 语句。我们没有使用 BeanUtils 之类的东西的原因是当两个对象中的字段具有不同的类型或名称时,它不能很好地工作。显然这些代码违反了开闭原则。每当特定页面发生变化或者我们想要更改数据库中的字段时,这将是一场噩梦。
我想知道它是否真的必须将 DO 和 Po 分开,我们是否可以简化架构和设计以使它们成为相同的东西,因为在大多数情况下它们只包含相同的字段而几乎没有区别。
我们如何简化设计来避免我们面临的问题并提高我们的生产力并确保软件的可扩展性和稳定性?
如果您查看 DDD 战术模式,您将找不到任何关于 "domain objects" 的内容。您有聚合,由实体组成,这些实体之一是您的聚合根。每个限界上下文可能只有一个实体,这将是您的聚合根。
您将聚合作为一个整体持久化。基本存储库在 "collection style" 中运行,只允许您将一个新聚合放入存储库并通过其标识从存储库中检索一个聚合。
CQRS 将一个对象一分为二。一个对象是为写入而优化的聚合。通常,在 "write side" 上的命令处理程序中使用一个集合式存储库就足够了。但是,在 "read side" 上,您有更扁平的阅读模型,它们代表您想要以针对阅读进行优化的方式向用户展示的内容。
如果你说你用的是CQRS,但是只有一个模型和一个数据库,这个意义不大。如果您有命令处理程序,并不意味着您使用 CQRS。
在这个澄清之后,我们可以看看聚合持久性。 没有人说过您不能按原样保留聚合。 在某种程度上,如果您有适当的存储,这实际上是首选方法。通常,ORM 不是合适的存储,因为它会在您发现对象世界和关系数据库世界之间阻抗不匹配的地方产生摩擦。文档数据库更适合于此。 PostgreSQL JSONB 功能也是一个不错的选择。
流程将是:
- 客户端发送DTO(命令)
- 工作单元开始
- 命令处理程序从存储库中检索聚合
- 命令处理程序调用聚合上的方法来执行必要的逻辑操作
- 聚合可能会发出一些域事件,这些事件可以在同一工作单元内触发事件处理程序
- 工作单元提交更改
如您所见,这里没有任何持久性的真正位置"conversions"。如果你有一个合适的存储,那一切都很好。
但是,当您阅读时,您收到了来自客户的请求。然后,此请求会转到某个查询提供程序、数据提供程序或您称之为的任何内容。有些人将所有这些查询放入他们的存储库中,但这仅在您 不 使用 CQRS 或至少有一个持久性模型而不是两个时才有效。
查询是幂等的,它们不会改变您的系统状态,并且可以 运行 任意多次而不会产生任何后果,除非如果读取未被优化,您的持久层可能会被加载。但是,没有必要在读取端进行大量抽象。同时,您不应将聚合发送回查询您的域的客户端。您需要有一个 DTO 来代表客户的视图模型以满足特定需求,不多也不少。这个 DTO 需要优化以尽快在客户端呈现,而不需要计算和转换。这基本上就是 CQRS 建议在您的读取端执行的操作 - 准备您的数据以快速交付这些 DTO。
我们的团队有一个基本上是 MIS 的项目。 我们使用的架构是 CQRS with DDD(领域驱动设计)。 我们在持久层中有持久性对象,在域层中有域对象,用于携带用户输入信息的数据传输对象,以及用于在特定页面上显示数据的视图对象。
我觉得这样的设计很棒。但是在实施项目的过程中,我们发现了一些问题让我们很受阻。最恶心的是我们必须写很多转换器来在两个不同的层之间转换对象,比如 PO 到 DO,DO 到 PO,DTO 到 DO。这些转换器中有太多的 get 和 set 语句。我们没有使用 BeanUtils 之类的东西的原因是当两个对象中的字段具有不同的类型或名称时,它不能很好地工作。显然这些代码违反了开闭原则。每当特定页面发生变化或者我们想要更改数据库中的字段时,这将是一场噩梦。
我想知道它是否真的必须将 DO 和 Po 分开,我们是否可以简化架构和设计以使它们成为相同的东西,因为在大多数情况下它们只包含相同的字段而几乎没有区别。 我们如何简化设计来避免我们面临的问题并提高我们的生产力并确保软件的可扩展性和稳定性?
如果您查看 DDD 战术模式,您将找不到任何关于 "domain objects" 的内容。您有聚合,由实体组成,这些实体之一是您的聚合根。每个限界上下文可能只有一个实体,这将是您的聚合根。
您将聚合作为一个整体持久化。基本存储库在 "collection style" 中运行,只允许您将一个新聚合放入存储库并通过其标识从存储库中检索一个聚合。
CQRS 将一个对象一分为二。一个对象是为写入而优化的聚合。通常,在 "write side" 上的命令处理程序中使用一个集合式存储库就足够了。但是,在 "read side" 上,您有更扁平的阅读模型,它们代表您想要以针对阅读进行优化的方式向用户展示的内容。
如果你说你用的是CQRS,但是只有一个模型和一个数据库,这个意义不大。如果您有命令处理程序,并不意味着您使用 CQRS。
在这个澄清之后,我们可以看看聚合持久性。 没有人说过您不能按原样保留聚合。 在某种程度上,如果您有适当的存储,这实际上是首选方法。通常,ORM 不是合适的存储,因为它会在您发现对象世界和关系数据库世界之间阻抗不匹配的地方产生摩擦。文档数据库更适合于此。 PostgreSQL JSONB 功能也是一个不错的选择。
流程将是:
- 客户端发送DTO(命令)
- 工作单元开始
- 命令处理程序从存储库中检索聚合
- 命令处理程序调用聚合上的方法来执行必要的逻辑操作
- 聚合可能会发出一些域事件,这些事件可以在同一工作单元内触发事件处理程序
- 工作单元提交更改
如您所见,这里没有任何持久性的真正位置"conversions"。如果你有一个合适的存储,那一切都很好。
但是,当您阅读时,您收到了来自客户的请求。然后,此请求会转到某个查询提供程序、数据提供程序或您称之为的任何内容。有些人将所有这些查询放入他们的存储库中,但这仅在您 不 使用 CQRS 或至少有一个持久性模型而不是两个时才有效。
查询是幂等的,它们不会改变您的系统状态,并且可以 运行 任意多次而不会产生任何后果,除非如果读取未被优化,您的持久层可能会被加载。但是,没有必要在读取端进行大量抽象。同时,您不应将聚合发送回查询您的域的客户端。您需要有一个 DTO 来代表客户的视图模型以满足特定需求,不多也不少。这个 DTO 需要优化以尽快在客户端呈现,而不需要计算和转换。这基本上就是 CQRS 建议在您的读取端执行的操作 - 准备您的数据以快速交付这些 DTO。