架构分层和工作单元模式
architecture layering and unit of work pattern
我正在尝试找出架构/分层和工作单元的良好实践。
我们使用 C#,编写 MVC 前端应用程序。目前的正常结构是:
MVC
服务/领域层
存储层
EF6
MVC 将简单地调用该服务(此处没有逻辑)。该服务包含所有域逻辑。存储库使用 EF6 处理数据访问。在 EF 之上拥有存储库层的三个主要原因是:
1)单一职责(SRP),服务处理业务关心的事情,存储库处理获取和保存数据。使用存储库意味着它们不会混合到同一个方法中。
2) 测试,它使存储库的最小起订量变得更容易。 (我知道您现在可以模拟 dbcontext,老实说还没有尝试过这种方法)。
3) 从域中抽象出 EF,因为它并不真正关心它,(尽管这是一个非常薄弱的论点)。
我们将为系统的不同部分提供单独的服务和存储库,例如:
1) 客户服务
2) 发票服务
3) 订单服务
每个存储库都有自己的实现(即没有通用存储库),允许我们准确地编辑我们想要的内容并创建我们想要的 return 查询,而不是获取非常大的对象或制作大量的个人调用,(并避免必须从域中传递大量包含状态)。
这种结构总体上效果很好。我们发现服务层 类 有时会变得非常大(最终会破坏 SRP)。认为我们最终应该将它们拆分为子服务。如果它是 CustomerService,它可能会变成 CustomerQueryService 和 CustomerAdminService。我们还发现这种结构有助于存储库,因为我们的一些查询最终会非常大,将数据转换为正确的格式并且不会提取比所需更多的数据。
当你想使用交易时,我会感到失望。如果我将每个存储库变成一个工作单元存储库(我认为),这仍然可以工作。当您需要执行跨越两个或多个服务的操作时,就会出现问题。例如,创建订单可能还需要调用发票服务来创建发票。但是,如果由于某种原因,如果第二部分失败,您也想回滚订单,并且 return 对客户来说是一个有用的错误。
我不确定如何实现它,或者这是否是一个坏主意。我将如何设置允许多个服务和存储库(convcerns 和 SRP 分离)的工作单元模式?
你的问题中有很多概念不仅仅涉及技术部分。我一直在尝试从技术上解决它,但这在很长一段时间内总是失败 运行。重要的是 业务专家 必须说的话。如果应用得当,这就是 领域驱动设计 大放异彩的地方。作为开发人员,我们试图对业务如何运作做出假设,而没有真正询问他们将如何解决他们的问题。这导致我们一切都是事务性的东西,2PC,残缺不全的运行时间怪物.我的第一个问题是:"Why orders have to be transactional with invoice service?"。请教业务专家。 "Do you want to loose a 50M order because of the bug in invoicing system?" 这对我来说似乎是错误的。询问业务人员他们希望如何处理这种失败案例。
据我了解,即使无法开具发票,商界人士也总是愿意接受订单。但我不了解你所从事的业务,所以我无法给你一个真正彻底的答案。也许有一些我不知道的极端情况。
对您的问题的回应非常复杂,仅提供技术解决方案是错误的,但我会尝试为您指出一些资源,这些资源可能会帮助您更进一步。如果您正在设计复杂的系统,请参考 领域驱动设计 将是一个好的开始,因为它解决了许多此类问题。
- 请教业务人员这种情况应该如何处理。他们经常会告诉您,即使无法开具发票,他们也想接受订单。发票不起作用的概率是多少?如果这是 1% 的时间,则故障不是很重要,可以由业务人员直接处理(打电话给人员,为不满意的客户提供折扣等)业务人员有很多方法 知道如何处理。至少他们不会失去任何秩序。恕我直言,这是最重要的部分。
- 研究领域驱动设计限界上下文 (BC)。限界上下文是领域驱动设计的核心模式。这是 DDD 的战略设计部分的重点,它是关于处理大型模型和团队的。对我来说,Ordering Service 和 Invoicing Service 是两个不同的 BC。不要让它们成为事务性的,除非有一个你知道的正当理由。
- 如何同步两个不同的BC?您还需要查看 Domain Events 和 Aggregates。它们由聚合根生成,通常由中间层 异步 发布,例如 事件总线 并由同一 BC 或另一个 BC 中的其他聚合使用。 V. Vernon 撰写了一篇关于如何设计聚合的精彩文章(link 在下面的参考资料中)。在你的场景中,这意味着你会在你的 OrderService BC 中产生 OrderPlaced 事件(我不喜欢名字中的 Service 因为它意味着不同的东西对不同的人,但我尽量坚持你的例子)他们应该被持久化,所以如果 InvoiceService BC 不可用或者如果有错误,这些事件可以在之后使用并在中创建发票稍后。
- Eventual consistency: 这个是第3点引出来的,你要知道对你的系统有什么影响,怎么处理。简而言之,这是一种用于分布式计算以实现高可用性的一致性模型,非正式地保证,如果没有对给定数据项进行新的更新,最终对该项目的所有访问都将 return 最后更新的值。因此,根据您的情况,这意味着,如果 InvoiceService 崩溃或不可用,则在稍后的某个时间点下订单时不会立即开具发票发票服务将上线。这是一个非常重要的概念,因为它可以帮助您处理复杂的模型,并且不会丢失对业务人员很重要的任何顺序。
- 您已经谈到了查询,这通常也是您必须处理的事情。将读取与写入分开也将非常有益。这意味着您有一个用于写入的模型(域的状态更改)和另一个用于读取的模型(只是用于不同目的的非规范化数据,如 UX 视图等)。有一种架构模式称为 CQRS 但请注意,如果不对您的领域有非常深入的了解,就无法应用它。 CQRS也适用于一个或几个BC,但不适用于整个系统。这真的取决于。我在这里提到这种模式是为了进一步调查,因为从理论上讲,当您将读取与写入分开时,您正在执行 CQRS。这是非常技术性的架构模式,因此您可以在不完全了解您正在做的事情的情况下直接加入您的组织。
这可能无法解决您的问题,这可能会让您感到失望。但我更愿意给你一些提示,这样你就可以走得更远,因为我去过那里,做所有事务性的事情通常是处理真实系统中事情的糟糕方式。
参考文献:
- 限界上下文:http://martinfowler.com/bliki/BoundedContext.html
- 域事件:http://lostechies.com/jimmybogard/2010/04/08/strengthening-your-domain-domain-events/
- 聚合根:http://dddcommunity.org/library/vernon_2011/
- 关于最终一致性和 CQRS 的精彩文章:http://lostechies.com/jimmybogard/2012/06/26/eventual-consistency-cqrs-and-interaction-design/
我正在尝试找出架构/分层和工作单元的良好实践。
我们使用 C#,编写 MVC 前端应用程序。目前的正常结构是:
MVC 服务/领域层 存储层 EF6
MVC 将简单地调用该服务(此处没有逻辑)。该服务包含所有域逻辑。存储库使用 EF6 处理数据访问。在 EF 之上拥有存储库层的三个主要原因是:
1)单一职责(SRP),服务处理业务关心的事情,存储库处理获取和保存数据。使用存储库意味着它们不会混合到同一个方法中。 2) 测试,它使存储库的最小起订量变得更容易。 (我知道您现在可以模拟 dbcontext,老实说还没有尝试过这种方法)。 3) 从域中抽象出 EF,因为它并不真正关心它,(尽管这是一个非常薄弱的论点)。
我们将为系统的不同部分提供单独的服务和存储库,例如:
1) 客户服务 2) 发票服务 3) 订单服务
每个存储库都有自己的实现(即没有通用存储库),允许我们准确地编辑我们想要的内容并创建我们想要的 return 查询,而不是获取非常大的对象或制作大量的个人调用,(并避免必须从域中传递大量包含状态)。
这种结构总体上效果很好。我们发现服务层 类 有时会变得非常大(最终会破坏 SRP)。认为我们最终应该将它们拆分为子服务。如果它是 CustomerService,它可能会变成 CustomerQueryService 和 CustomerAdminService。我们还发现这种结构有助于存储库,因为我们的一些查询最终会非常大,将数据转换为正确的格式并且不会提取比所需更多的数据。
当你想使用交易时,我会感到失望。如果我将每个存储库变成一个工作单元存储库(我认为),这仍然可以工作。当您需要执行跨越两个或多个服务的操作时,就会出现问题。例如,创建订单可能还需要调用发票服务来创建发票。但是,如果由于某种原因,如果第二部分失败,您也想回滚订单,并且 return 对客户来说是一个有用的错误。
我不确定如何实现它,或者这是否是一个坏主意。我将如何设置允许多个服务和存储库(convcerns 和 SRP 分离)的工作单元模式?
你的问题中有很多概念不仅仅涉及技术部分。我一直在尝试从技术上解决它,但这在很长一段时间内总是失败 运行。重要的是 业务专家 必须说的话。如果应用得当,这就是 领域驱动设计 大放异彩的地方。作为开发人员,我们试图对业务如何运作做出假设,而没有真正询问他们将如何解决他们的问题。这导致我们一切都是事务性的东西,2PC,残缺不全的运行时间怪物.我的第一个问题是:"Why orders have to be transactional with invoice service?"。请教业务专家。 "Do you want to loose a 50M order because of the bug in invoicing system?" 这对我来说似乎是错误的。询问业务人员他们希望如何处理这种失败案例。
据我了解,即使无法开具发票,商界人士也总是愿意接受订单。但我不了解你所从事的业务,所以我无法给你一个真正彻底的答案。也许有一些我不知道的极端情况。
对您的问题的回应非常复杂,仅提供技术解决方案是错误的,但我会尝试为您指出一些资源,这些资源可能会帮助您更进一步。如果您正在设计复杂的系统,请参考 领域驱动设计 将是一个好的开始,因为它解决了许多此类问题。
- 请教业务人员这种情况应该如何处理。他们经常会告诉您,即使无法开具发票,他们也想接受订单。发票不起作用的概率是多少?如果这是 1% 的时间,则故障不是很重要,可以由业务人员直接处理(打电话给人员,为不满意的客户提供折扣等)业务人员有很多方法 知道如何处理。至少他们不会失去任何秩序。恕我直言,这是最重要的部分。
- 研究领域驱动设计限界上下文 (BC)。限界上下文是领域驱动设计的核心模式。这是 DDD 的战略设计部分的重点,它是关于处理大型模型和团队的。对我来说,Ordering Service 和 Invoicing Service 是两个不同的 BC。不要让它们成为事务性的,除非有一个你知道的正当理由。
- 如何同步两个不同的BC?您还需要查看 Domain Events 和 Aggregates。它们由聚合根生成,通常由中间层 异步 发布,例如 事件总线 并由同一 BC 或另一个 BC 中的其他聚合使用。 V. Vernon 撰写了一篇关于如何设计聚合的精彩文章(link 在下面的参考资料中)。在你的场景中,这意味着你会在你的 OrderService BC 中产生 OrderPlaced 事件(我不喜欢名字中的 Service 因为它意味着不同的东西对不同的人,但我尽量坚持你的例子)他们应该被持久化,所以如果 InvoiceService BC 不可用或者如果有错误,这些事件可以在之后使用并在中创建发票稍后。
- Eventual consistency: 这个是第3点引出来的,你要知道对你的系统有什么影响,怎么处理。简而言之,这是一种用于分布式计算以实现高可用性的一致性模型,非正式地保证,如果没有对给定数据项进行新的更新,最终对该项目的所有访问都将 return 最后更新的值。因此,根据您的情况,这意味着,如果 InvoiceService 崩溃或不可用,则在稍后的某个时间点下订单时不会立即开具发票发票服务将上线。这是一个非常重要的概念,因为它可以帮助您处理复杂的模型,并且不会丢失对业务人员很重要的任何顺序。
- 您已经谈到了查询,这通常也是您必须处理的事情。将读取与写入分开也将非常有益。这意味着您有一个用于写入的模型(域的状态更改)和另一个用于读取的模型(只是用于不同目的的非规范化数据,如 UX 视图等)。有一种架构模式称为 CQRS 但请注意,如果不对您的领域有非常深入的了解,就无法应用它。 CQRS也适用于一个或几个BC,但不适用于整个系统。这真的取决于。我在这里提到这种模式是为了进一步调查,因为从理论上讲,当您将读取与写入分开时,您正在执行 CQRS。这是非常技术性的架构模式,因此您可以在不完全了解您正在做的事情的情况下直接加入您的组织。
这可能无法解决您的问题,这可能会让您感到失望。但我更愿意给你一些提示,这样你就可以走得更远,因为我去过那里,做所有事务性的事情通常是处理真实系统中事情的糟糕方式。
参考文献:
- 限界上下文:http://martinfowler.com/bliki/BoundedContext.html
- 域事件:http://lostechies.com/jimmybogard/2010/04/08/strengthening-your-domain-domain-events/
- 聚合根:http://dddcommunity.org/library/vernon_2011/
- 关于最终一致性和 CQRS 的精彩文章:http://lostechies.com/jimmybogard/2012/06/26/eventual-consistency-cqrs-and-interaction-design/