如何构建 CQRS 应用程序的查询端?

发布于 2025-01-10 13:36:08 字数 899 浏览 0 评论 0原文

我正在开发两种不同的服务:

  1. 第一个服务通过 REST API 处理所有写入操作,它包含将数据维持在一致状态所需的所有业务逻辑,并将实体保存在数据库上。当实体发生更改(创建、更新、删除等)时,它还会向消息代理发布事件。它以 DDD 方式构建。
  2. 第二个仅处理读取,也使用 REST API。它订阅同一个消息代理以处理第一个服务发布的事件,然后将接收到的数据保存到内存数据库中以便快速读取。

没什么花哨的,只是具有最终一致性的 CQRS。

对于第一个服务,我对如何构建应用程序有一个清晰的头脑:

  • 我有 domain 包,其中包含每个不同聚合的子包。每个聚合都有自己的域对象和自己的存储库接口。
  • 我有包含不同应用程序服务的应用程序包,它们基本上只是编排域对象并调用存储库来保存/更新数据,以及事件发布者来发布域事件。事件发布者接口也在这个包中。
  • 我有基础设施包,其中包括存储库实现所在的持久性包和事件发布者实现所在的消息传递包。
  • 最后,interfaces 包是我保存 REST API 控制器/处理程序的地方。

对于第二项服务,我非常不确定如何构建它。我的疑问如下:

我应该使用存储库模式吗?公平地说,在这种情况下它似乎是多余的并且不是很有用。这里没有域对象也没有规则,导致要保存/更新的数据已经由第一个服务验证。

如果我避免使用存储库模式,我想我必须将数据库客户端注入我的应用程序服务中,并直接访问数据。这是一个好的做法吗?如果是,返回的对象将放在哪里?它们也会成为应用程序层的一部分吗?

完全跳过应用程序服务并将数据库客户端直接注入控制器/处理程序是否有意义?如果查询有点复杂怎么办?这会用大量数据库逻辑污染控制器,从而使切换实现变得更加困难(在这种情况下将没有接口)。

你怎么认为?

I am working on two different services:

  1. The first one handles all of the write operations through a REST API, it contains all of the required business logic to maintain data in a consistent state, and it persists entities on a database. It also publishes events to a message broker when an entity is changed (creation, update, deletion, etc). It's structured in a DDD fashion.
  2. The second one only handles reads, also with a REST API. It subscribes to the same message broker in order to process the events published by the first service, then it saves the received data to an in memory database for fast reads.

Nothing fancy, just CQRS with eventual consistency.

For the first service, I had a clear mind on how to structure the application:

  • I have the domain package with subpackages for each different aggregate. Each aggregate has its own domain objects, and its own repository interface.
  • I have the application package with different application services, and they basically just orchestrate the domain objects and call repositories to persist/update data, and the event publisher to publish domain events. The event publisher interface is also in this package.
  • I have the infrastructure package, which includes a persistence package, where the repository implementations reside, and a messaging package, where the event publisher implementation resides.
  • Finally, the interfaces package is where I keep the controllers/handlers for the REST API.

For the second service, I'm very unsure on how to structure it. My doubts are the following:

Should I use the repository pattern? To be fair it seems redundant and not very useful in this scenario. There are no domain objects nor rules here, cause the data to be saved/updated is already validated by the first service.

If I avoid using the repository pattern, I suppose I'd have to inject the database client in my application service, and access the data directly. Is this a good practice? If yes, where would the returned objects fit? Would they also be part of the application layer?

Would it make sense to skip the application service entirely and inject the database client straight up in the controller/handler? What if the queries are a bit complicated? This would pollute the controllers with a lot of db logic, making it harder to switch implementations (there would be no interface in this case).

What do you think?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

神经暖 2025-01-17 13:36:09

查询端仅包含获取数据的方法,因此它可以/应该非常简单。

你是对的,像存储库模式这样的持久性之上的抽象可能会让人感觉多余。
您实际上可以在控制器中调用数据库。即使涉及测试,在查询方面,您也只需要测试实际数据库的基本集成测试。进行单元测试不会测试太多。

另一方面,将数据库调用逻辑包装在类似于存储库的查询服务中也是有意义的。您只需在控制器中注入查询服务接口,该接口应该使用您通用的语言!您将在此查询服务中拥有所有数据库逻辑,并保持数据库的复杂性,同时保持控制器非常简单。
您可以根据需要根据事件使用多个读取模型来避免复杂的查询。

The Query side will only contain the methods for getting data, so it can/should be really simple.

You are right, an abstraction on top of your persistence like a repository pattern can feel redundant.
You can actually call the database in your controller. Even when it comes to testing, on the query side you only need basically integration tests that test the actual database. Having unit tests won't test much.

On the other hand, it can make sense to wrap the database calling logic in a query service similar to a repository. You would inject only that query service interface in your controller, which should use your ubiquitous language! You would have all the db logic in this query service and keep the db complexity there, while keeping the controller really simple.
You can avoid complex queries by having multiple read models based on your events depending on your needs.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文