面向事件的架构的反模式

再一次问好!预期“软件架构师”课程的开始,我们准备了另一本有趣的材料的翻译。




在过去的几年中,微服务体系结构越来越流行。有很多资源可以教您如何正确实施它,但是很多时候人们都将其称为“银池”。有许多反对使用微服务的争论,但其中最重要的是,这种架构充满不确定性,其复杂程度取决于您如何管理服务与团队之间的关系。您可以找到很多文献,这些文献可以解释为什么(也许)您认为微服务不是最佳选择。

为了满足对可伸缩性的需求,letgo的我们从整体迁移到微服务,并立即确信其对团队工作的有益影响。如果正确使用,微服务将为我们提供以下优势:

  • : , . ( ..) . ( ) Users.
  • : , . . , , , , . .

-


并非所有的微服务架构都是事件驱动的。有人主张使用HTTP(gRPC,REST等)在此体系结构中的服务之间进行同步通信。在letgo,我们尝试不遵循这种模式,而是将我们的服务与域事件异步关联。这是我们这样做的原因:

  • : . , DDoS . , DDoS . , . .



  • (bulkheads) – , , .
  • : . , , , , . , , API, , , , . Users , Chat.

基于此,我们在letgo尝试坚持服务之间的异步通信,而同步仅在功能MVP这样的例外情况下有效。我们之所以这样做,是因为我们希望每个服务都可以根据消息总线中其他服务发布的域事件来生成自己的实体。

我们认为,实现微服务架构的成败取决于您如何应对其固有的复杂性以及服务之间的交互方式。在不将通信基础结构转移到异步的情况下划分代码将使您的应用程序变成分布式的整体。



Letgo中的事件驱动架构

今天,我想分享一个示例,说明如何在letgo中使用域事件和异步通信:我们的User实体存在于许多服务中,但是其创建和编辑最初是由Users服务处理的。在用户服务的数据库中,我们存储了大量数据,例如姓名,电子邮件地址,头像,国家/地区等。在我们的聊天服务中,我们也有一个用户概念,但是我们不需要Users服务中User实体拥有的数据。用户名,头像和ID(指向个人资料的链接)显示在对话框列表中。我们说在聊天中,只有用户实体的投影,其中包含部分数据。实际上,在聊天中,我们并不是在谈论用户,我们称其为“谈话者”。此投影是指聊天服务,它是基于聊天从用户服务接收的事件而建立的。

我们对清单也一样。在“产品”服务中,我们存储每个列表的n张图片,但是在对话框的列表视图中,我们显示一张主图片,因此从“产品到聊天”的投影只需要一张图片,而不是n张图片。


查看我们聊天中的对话框列表。它显示了后端上的哪些特定服务提供了信息。

如果再次查看对话框列表,您将看到我们显示的几乎所有数据不是由聊天服务创建的,而是属于它的,因为用户和聊天的投影是聊天的拥有。在可访问性和预测一致性之间需要权衡取舍,我们将不在本文中讨论,但是我只想说,扩展许多小型数据库显然比扩展大型数据库容易得多。


Letgo架构的简化视图

反模式


一些直观的解决方案经常成为错误。这是我们在与域相关的体系结构中遇到的最重要的反模式列表。

1.重大事件
我们试图使域事件尽可能小,同时又不损失其域值。在使用大型实体重构遗留代码库并切换到事件体系结构时,我们应该格外小心。这样的实体可能导致我们发生致命事件,但是由于我们的领域事件已转换为公共合同,因此我们需要使它们尽可能简单。在这种情况下,最好从侧面查看重构。首先,我们使用事件风暴技术设计事件然后重构服务代码以使其适应我们的事件。

对于“产品和用户”问题,我们也应该格外小心:许多系统使用产品和用户实体,并且这些实体通常将所有逻辑置于其后,这意味着所有域事件都与它们相关联。

2.有意图的
事件根据定义,领域事件是已经发生的事件。如果将某些内容发布到消息总线以请求其他服务中发生的事情,则很可能会运行异步命令,而不是创建域事件。通常,我们指的是过去的域事件:ser_registeredproduct_published等等。一种服务对另一种服务的了解越少越好。使用事件作为命令链接服务,并增加一项服务的更改会影响其他服务的可能性。

3.缺乏独立的序列化或压缩
系统我们主题领域中事件的序列化和压缩系统不应该依赖于编程语言。您甚至不需要知道用什么语言编写消费者服务。例如,这就是为什么我们可以使用Java或PHP序列化程序。让您的团队花时间讨论和选择串行器,因为将来更改它会很困难并且很耗时。我们在letgo中使用JSON,但是还有许多其他具有良好性能的序列化格式。

4.缺乏标准结构
当我们开始将letgo后端移植到面向事件的体系结构时,我们就域事件的通用结构达成了一致。看起来像这样:

{
  “data”: {
    “id”: [uuid], // event id.type”: “user_registered”,
    “attributes”: {
      “id”: [uuid], // aggregate/entity id, in this case user_id
      “user_name”: “John Doe”,
    }
  },
  “meta” : {
    “created_at”: timestamp, // when was the event created?
    “host”: “users-service” // where was the event created?
  }
}

对我们的域事件具有通用的结构,使我们能够快速集成服务并实现带有抽象的某些库。

5.缺乏模式验证
在序列化过程中,我们在letgo遇到了没有强类型化的编程语言问题。


{
  “null_value_one”: null, // thank god
  “null_value_two”: “null”,
  “null_value_three”: “”,
}

可以确保事件序列化的完善的测试文化以及对序列化库如何工作的理解有助于解决这一问题。我们在letgo上正在切换到Avro和Confluent Schema Registry,这为我们提供了确定域事件结构的单点方法,并避免了此类错误以及过时的文档。

6.贫血领域事件
正如我之前所说的,顾名思义,域事件必须在域级别具有一个值。正如我们试图避免实体中状态的不一致一样,我们也必须在域事件中避免这种情况。让我们用以下示例进行说明:系统中的产品具有经度和纬度的地理位置,这些地理位置存储在Products服务的product表的两个不同字段中。所有产品都可以“移动”,因此我们将通过域事件来介绍此更新。以前,为此,我们有两个事件:product_latitude_updatedproduct_longitude_updated,如果您不是国际象棋的白手起家,这没有多大意义。在这种情况下,product_location_updated事件将更有意义。product_moved


白嘴鸦是棋子。它曾经被称为旅游。小车只能在任何数量的未占用字段中垂直或水平移动。

7.缺乏调试工具
我们的letgo每分钟产生数千个域事件。所有这些事件都成为了非常有用的资源,可用于了解我们系统中正在发生的事情,注册用户活动,甚至使用事件搜索在特定时间点重建系统状态。我们需要熟练地使用此资源,为此,我们需要工具来检查和调试事件。 “向我展示约翰·杜(John Doe)最近3个小时内发生的所有事件”之类的请求也可以用于检测欺诈。为此,我们在ElasticSearch,Kibana和S3上开发了一些工具。

8.缺乏事件监控
我们可以使用域事件来测试系统的运行状况。当我们部署某些东西时(一天会发生几次,具体取决于服务),我们需要工具来快速验证正确的操作。例如,如果我们在生产中部署了新版本的Products服务,并且发现product_published事件的数量减少了20%,可以肯定地说我们坏了东西。我们目前正在使用InfluxDB,Grafana和Prometheus通过派生函数来实现这一目标。如果您回想一下数学课程,您将会理解,函数f(x)在点x的导数等于在该点绘制到函数图的切线角的切线。如果您具有在主题区域中发布特定事件的速度的功能并从中获取衍生信息,则将看到此功能的峰值,并且可以基于这些峰值设置通知。使用这些通知,您可以避免使用诸如“如果我们在5分钟内每秒发布少于200个事件的警告我”之类的短语,而是着眼于发布速度的重大变化。


这里发生了一些奇怪的事情……或者可能只是一次营销活动

。9.希望一切都会好起来的希望
我们正在尝试创建可持续的系统并降低其修复成本。除了基础结构问题和人为因素外,影响事件架构的最常见事件之一就是事件的丢失。我们需要一个计划,通过该计划,我们可以通过重新处理所有丢失的事件来恢复系统的正确状态。在这里,我们的策略基于两点:

  • : , « , », - , . letgo Data, Backend.
  • : - . , , , message bus . – , , . , user_registered Users, , MySQL, user_id . user_registered, , . , , - MySQL ( , 30 ). -, DynamoDB. , , , . , , , , .

10.缺少有关域事件的文档
我们的域事件已成为后端所有系统的公共接口。正如我们记录REST API一样,我们也需要记录域事件。该组织的任何员工都应该能够查看每个服务发布的每个域事件的更新文档。如果我们使用方案来检查域事件,它们也可以用作文档。

11.抵制自己事件的消费
允许甚至鼓励您使用自己的域事件在系统中创建投影,例如,针对阅读进行了优化。一些团队拒绝使用此概念,因为它们仅限于消耗其他人的事件的概念。

在课程中见!

All Articles