在我当前的项目中,我们使用 Spring Boot 和 RabbitMq 进行一些内部微服务通信。
我们当前正在这两个服务中定义队列属性,用于发布/侦听此队列。此外,我们仅在发布者服务中定义交换。
但是,为了使其更易于维护,我想找到一种设置/最佳实践来定义队列一次,所有相关服务都可以依赖它。
到目前为止,我检查了 AsyncAPI 项目并考虑创建一个额外的库来外包那里的配置。
这里的最佳实践是什么,或者您如何在项目中做到这一点?
有很多选择,但没有一个是完美的。
从“最小责任”的角度来看,正确的做法是:
问题的根源在于,发布者需要知道所有路由密钥,无论它们是如何绑定的,只是为了形成它们。另一个问题是,有时订阅者也需要了解它们,如果有一些逻辑依赖于它们(这似乎不是一个好的做法)。最重要的是,发送消息的契约没有单一的事实来源,它们需要在消费者和订阅者中单独实现。除非您能够直接从规范生成生产者和消费者代码(无论是 AsyncAPI 还是任何其他),否则您必须在代码库之间共享一些字符串常量(路由键、队列和交换名称),因此没有明确的定义分离的关注点和人为因素的脆弱性。
另一种方法是将所有责任放在发布者身上,发布者可以创建交换器和队列并正确绑定它们,从而成为有关消息路由的唯一知识来源。然而,订阅者必须显式指定其队列,而将任何其他订阅者添加到同一消息“主题”或一组键将要求发布者再添加一个配置参数(因为通常您不希望对所维护的实体名称进行硬编码)在独立服务内部,使用环境变量或运行时配置代替)。如果您设法教您的发布商使用规范(AsyncAPI 效果很好),那么它就不那么麻烦了,并且可以被视为发布商代码库的一部分。只要订阅者不依赖于路由键,而是从消息有效负载中派生逻辑,这就是您可以实现的最佳关注点分离,但无论如何它都不是最小的。
如果您无法从规范中派生配置并且必须通过环境变量对每个实体“占位符”进行硬编码,则该选项尤其令人不快,因为处理消息通道上的任何额外订阅者将需要新版本的发布者(以支持先前未知的 var),它负责该通道。共享合约知识的问题在这里也没有解决。
当您需要包含许多不同消息的发布/订阅模型并且需要支持多种编程语言时,第一种方法似乎最有效。这种纯粹的 EDA 方法最终会带来一个很好的架构,每个发布者只有 1 个交换,每个订阅者有 1 个队列,并且依赖于路由键的规范明确描述了消息交换的整个逻辑。它可以通过规范中的总代码生成(完全可以使用 AsyncAPI)或(相反的方式)通过实时服务提供的一些反射实际生成规范本身来稍微改进,然后可以使用这些反射来引导整个 RabbitMQ 内部配置。
第二个不太容易受到人为因素的影响,但如果您有复杂的通信图,则可能会非常冗长,我建议仅当您只需要一些带有直接一对一绑定的消息通道时才使用它,最好是使用相同的堆栈(因为这允许在服务之间无缝共享合同)
在我看来,没有通用的良好实践。您可以尝试与您的团队一起定义您的实践;你可以尝试一种策略,但很快就会失败。
如果您的系统是“静态”的(我希望不是),您可以使用定义文件来定义配置。我不喜欢这样,但这是一种可能的方法。您可以使用 docker 尝试 DEV ENV 的此解决方案
在我们的团队中,每个工作负载定义其配置,并且rabbitmq团队使用devops方法创建VHOST及其配置。