【问题标题】:CQS file & namespace organizationCQS 文件和命名空间组织
【发布时间】:2017-10-15 06:34:26
【问题描述】:

我有一个非常大的解决方案,由 50 多个项目(Windows 服务、网站、类库)组成,这些项目是使用服务和存储库框架以及 Telerik DataAccess ORM 构建的。在与 Telerik 的架构师进行了长时间的讨论后,他们提出了上述设计,因为它应该符合我们当时的需求并提供可测试的代码框架。几年后,现在我们的服务类已经增长到数千行,这使得它们更难维护和测试。

在阅读和研究我们代码中的一些问题后,我遇到了 CQS(命令查询分离),它在我们的项目中对我来说更有意义,因为它将我们庞大的服务类划分为更小的可测试类。我已经成功地实现了一个小的概念,但我现在想知道当我将 1000 多个查询移动到 CQS 命名空间时我的代码将如何组织(现在专注于查询,因为我想象的命令将被组织相同) - 显然把所有查询、处理程序和结果都在自己的文件夹中,每个文件夹将有 1000 多个文件,要找到一些东西会很痛苦。

到目前为止,我已经有了这个文件夹结构

    Model
        Customer
    Queries
        CustomerNameByIdQuery
        CustomerNameByTextSearchQuery
    QueryHandlers
        CustomerNameByIdQueryHandler
        CustomerNameByTextSearchQueryHandler
    QueryResults
        CustomerNameQueryResult

两个查询都返回相同的 CustomerNameQueryResult,它只有 Id 和 Value 属性

现在成像我还需要查询完整的客户记录,所以我需要CustomerByIdQueryCustomerByIdQueryHandler 和来自Customer 模型的结果。目前还有大约 10 个针对客户的其他查询,具有不同的参数以满足不同的需求。

这种跨越数百个表的模式会产生大量查询类和处理程序,从而很难在代码中的特定位置找到我需要使用的内容(如果可能,促进代码重用)。

我正在向在大型生产应用程序中使用 CQS 的资深人士寻求有关项目中查询的命名空间/文件组织的建议,以及您的解决方案如何组织查询/处理程序/结果?例如,您是否将查询和处理程序放在同一个文件中?单独的文件是单独的目录?你如何处理同一个对象的多个查询?包含所有查询或多个文件的单个文件?您是否使用命名空间划分查询以便于编码?你知道你的结构有什么问题吗?

我知道这里没有单一的“正确”答案,但随着时间的推移一直在使用这种方法的人的一些建议将帮助我和其他人避免陷入您在文件/文件夹结构中已经遇到和解决的任何问题。

【问题讨论】:

    标签: orm architecture


    【解决方案1】:

    我们不使用 CQS,但我们有一个包含许多项目的大型解决方案,其中一个项目是存储库层。

    我们每个模型都有一个存储库类,因此按照您的示例,我们尝试让所有与 Customer Repository 中的 Customer Model 相关的查询。 事实上,这来自我们使用的层中的模型/代理/服务/存储库架构。 诀窍部分带有联系人之类的东西。我们有那个模型和那个使用 Contacts 表的充满查询的存储库。 那么来自 Customeres 查询的联系人在哪里呢?这取决于功能,它实际上是客户的功能,因此它被放置在客户存储库中。请注意,我们可以选择客户并在循环中为每个联系人调用联系人,但通常我们会避免由于性能问题。

    相对于许多 GetCustomerXXX 方法。

    是的,我们已经得到了很多方法,我们还重载了相同的方法,以允许使用许多不同的参数和分页查询包装器进行更精细的搜索,因此我们可以在每次用户向下滚动时加载搜索屏幕(所以我们会根据您的要求更改答案)。所有这些查询都位于客户存储库中。

    相对于域组织。

    每个模型都可以被任何项目/域中的许多功能使用。例如,客户位于核心域中,而账单位于财务域中。当然,您可以查询客户的所有账单,以便任何项目/域都可以使用客户模型。所有使用客户和账单的项目都将具有相同的域和几乎相同的文件夹层次结构。

    简历: 我们有一个模型项目,它被所有其他项目引用。它包含许多域以及复杂的文件夹层次结构。

    所有项目(服务和存储库除外)都引用代理项目,代理引用服务,服务引用存储库。

    这样我们就不会得到循环引用、管理复杂性并保持理智。 注意(一些使用代理的 UI 项目甚至使用不同的语言,用于实现不同的用户体验(web/winform/remote/offline)。

    编辑

    有些服务可能会变得像单体怪物。恕我直言,任何超过 300 行的内容都很大,有些服务会增长到超过 2k 行。

    一个对我们有很大帮助的因素是已经构建了基类来处理最常见的场景。

    例如,我们在存储库基类中有一个IEnumarebale<BaseModel> GetAll(GenericFilter filter) 方法,这意味着所有模型都会自动实现该方法。 事实上,大多数存储库类都是空的,因为基本的已经涵盖了,我们只需要编写自定义逻辑。

    我们还避免从另一个服务调用存储库,一般服务调用服务,从而避免几乎任何代码重复。例如,基础 Crud 类具有 CustomValidation 方法和插入/删除/更新之前/之后的事件,我们可以根据需要覆盖。

    按照前面的例子。 Customer 和 Contacts 都有自己的 CustomValidation。插入新客户(带有联系人)将自动调用这两个验证。

    所有自动化测试都调用基本方法,节省大量时间。

    【讨论】:

    • 让,感谢您详细的回答。您基本上是在描述我当前的架构。它的“问题”是服务变得非常长,因为它们包含许多连接到同一问题域的不同存储库。这就是我试图用 CQS 简化的(应该将服务分成小块)
    • @DaniAvni 查看我使用基类进行的编辑对最小化服务和特殊存储库有很大帮助。有些服务很复杂,服务调用其他模型服务,我们尽量避免调用其他存储库。
    • Jean,我们也尽量不从其他服务调用服务,并且对于我们的大部分代码它都在工作。仍然我的服务是巨大的。例如,我的任务服务有任务的 CRUD 方法、任务类型的 CRUD、预订任务的方法和许多其他方法,如果我没记错的话,会产生 3K+ 行的巨大文件。我尝试用 CQS 打破这些文件,因为我的服务中的每个方法都将转换为命令或查询,它应该使巨大的文件分成更小的可管理块
    • 这一点差异至关重要。任务服务 CRUD 将调用任务类型服务,其中包含 CRUD 的所有逻辑和验证,这样任务服务可以更小,并且仍然可以测试将责任传递给任务类型服务。不管怎样,你看起来是在正确的道路上。
    • 可能是术语不同。我们的服务不是针对每个实体的。这将使我创建 100 多个服务。相反,服务是每个“关注领域”,即任务服务具有与任务相关的所有内容(任务、任务类型、任务技能、预订等)。缺点是每个服务都非常大,因为它包含多种类型的实体的 CRUD。
    【解决方案2】:

    实际上,我采取了不同的方法。相反,我按功能对所有内容进行分组。由于每个模型(请求/响应)都直接绑定到处理程序,因此我将它们全部放在同一个文件夹中。由于每个查询也需要特定于您正在运行的查询/命令,因此我也将支持它的存储库也放在同一个文件夹中。所以我会按照你的例子来构建:

    型号 顾客 特征 查询 CustomerNameById CustomerNameByIdQuery CustomerNameByIdQueryHandler CustomerNameByIdQueryResult CustomerNameByText CustomerNameByTextSearchQuery CustomerNameByTextSearchQueryHandler CustomerNameByTextSearchResult

    但是,如果 Id/Text 最终返回完全相同的内容,我可能会将两者结合起来,只需更改 Query/Request 以同时支持两个过滤器。没有错。根据我的经验,您正在编写的查询是针对以特定方式显示结果的特定前端部分。

    我还在我的 CQS 项目中大量使用装饰器,所以如果你有验证或映射或其他什么,它们也可以放在同一个子文件夹中。这样,特定于查询/命令的所有内容都在同一个文件夹中。如果它需要外部引用,您可以将 IoC 注入该共享组件,并且您仍然可以继续使用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-07-22
      • 1970-01-01
      • 1970-01-01
      • 2017-02-27
      • 2011-06-05
      • 2015-10-31
      • 1970-01-01
      相关资源
      最近更新 更多