作者:
Matteo —— Co-Founder @ Dozer;
Vivek —— Co-Founder @ Dozer
作为开发人员,你肯定不会对从多个微服务中创建一致 API 的挑战感到陌生。但是有了 Dozer,你可以告别繁琐复杂的过程,迎来闪电般快速的结果。Dozer 使整个过程的每一步自动化,从构建数据 pipelines 到实时数据聚合、索引和极速查询。只需简单的配置,你就可以生成 gRPC 和 REST API,并与你的面向客户的产品进行集成。让我们一起体验下使用 Dozer 创建的实时 API 的速度和便捷性。
简介
微服务架构是一种软件设计方法,它着重于将大型的、单体的应用程序拆分为更小、更独立的服务,可以独立开发、部署和维护。然而,这种方法可能会引来新的挑战,例如要处理分布式系统的复杂性,确保数据的一致性和可靠性,以及处理故障。
解决这些挑战的一种方法是使用 Saga 模式,它提供了一种在微服务架构中管理分布式事务的方式。Saga 模式是一种设计模式,它协调多个服务来完成事务,即使出现故障也可以完成。
虽然 Saga 模式是管理分布式事务的强大工具,但当需要将来自多个服务的数据集成在一起创建一致的 API 时,它可能会引来额外的挑战。
所有的配置文件和数据库示例都可以在我们的 GitHub samples repo 中找到。
场景
首先,我们以一个航空公司的订票网站为例。整个应用程序分为两个主要的微服务:
• 一个预订微服务:处理所有的预订、机票和登机证件
• 一个航班主数据微服务:维护所有航班的主数据,包括航线、飞机等。
每个服务都维护自己的数据库:
BOOKINGS, TICKETS, TICKET_FLIGHTS表是预订微服务数据库的一部分
AIRPORTS, FLIGHTS是航班主数据微服务数据库的一部分。
下面是我们数据库的实体关系图:
为了改善用户体验,我们想要创建几个新的 APIs:
•Booking retrieval APIs: 提供特定旅客的预定详情
•Routes listing APIs: 提供所有可用路线清单
由于这些 APIs 面向客户,所以要求延迟可忽略,且能够支持高吞吐量。出于明显的原因,这不能通过每次查询 Postgres 数据库表来实现。我们可以考虑创建 materialized views,但这些视图必须在每次记录更新时刷新,这也是不可行的选项。
传统上,这些用例是通过引入一个中间缓存层(如 Redis)来解决的,但即使是这种情况,保持缓存始终更新并决定正确的逐出策略也不是轻松的事情。除此之外,我们还需要构建一个高效和可扩展的 API 层,最好使用像 gRPC 这样的协议。
总之,没有简单的解决方案,实现需要付出一些不易的努力。
Dozer能提供什么帮助
上述需求正是 Dozer 的完美应用场景。Dozer 是一款低代码开源平台,可以自动构建超快的 gRPC 和 REST 只读 APIs,始终保持数据更新,可与前端代码(如 React )集成。为了始终保持缓存层的更新,Dozer 依赖于 Postgres CDC,并实现了一个 SQL 流引擎,可用于在传输过程中预处理数据。
让我们深入了解每个 API 以及如何使用 Dozer 构建它们。以下是我们想要实现的 API 列表:
配置连接和数据源
首先,我们需要创建一个新的 dozer-config.yaml 文件。该文件有 4 个主要模块: connections, sources, sql and endpoints。让我们定义两个连接到我们的微服务数据库:
一旦连接被定义,我们需要明确指出我们将从哪些源表中获取数据:
上面只是一些示例的数据源定义。您可以点击这里参考完整的配置文件,以获取完整的列表。
配置和构建 endpoints
一旦定义了数据源,我们终于到了构建 API 过程中有意思的地方。Dozer 配置文件有一个 endpoints 模块,我们可以在其中定义 endpoints 列表。在我们的例子中,我们将有 3 个定义:
这就是定义新 API endpoints 所需的全部内容。现在你可能想知道 sources 和 endpoints 是如何相互连接的。让我们深入了解 endpoint 配置:每个 endpoint 的所有参数中,都有一个 table_name 属性,该属性定义了要缓存的实际数据源。它可以匹配源的 table_name 属性(如果我们想要在缓存中复制完整的源表),或者是一个 SQL 临时表的名称(下面会进行说明)。
使用SQL预处理数据
在我们的场景中,我们希望在将数据插入缓存之前对其进行预处理。为此,Dozer提供了一个 sql 模块,其中可以表达转换。所有的 SQL SELECT 语句都必须包含一个 INTO ,这将与一个 endpoint 的 table-name 属性相匹配。在我们的示例中,我们的 sql 模块如下所示:
SELECT 语句后面跟着一个 INTO ,其中的值与我们要构建的缓存名称相匹配。
这就是用 Dozer 创建低延迟的 REST 和 gRPC APIs 所需的全部内容。所有 APIs 都附带了预嵌入的授权层,以允许直接与前端应用程序集成。我们将在另一篇即将发布的文章中详细介绍此主题。
缓存同步
由于 Dozer 直接与 Postgres CDC 进行交互,因此无需特殊操作即可保持缓存的实时性。每当 Postgres 发生 UPDATE、 INSERT 或 DELETE 操作时,Dozer 会基于 SQL 查询会基于SQL查询自动预处理这些操作,并在几毫秒内自动将结果传播到缓存中。
Dozer 完全由 RUST 构建,以实现较低的查询延迟。
查询数据
使用 gRPC 或 REST 查询 Dozer 非常简单。gRPC endpoint 还支持反射,特别适用于 enforce schemas 和 discover services。例如,我们可以使用 grpcurl 命令行工具从 passenger_id检索所有预订的详细信息:
List Services
Query an Endpoint with filters
由于整个对象已经被预先计算并存储在低延迟的混合(内存+磁盘)缓存中,因此响应很即时。
底层实现
上面已经解释了如何轻松地开始使用 Dozer,接下来让我们深入了解 Dozer 的架构和幕后发生的事情。
为了实时处理数据,Dozer 将所有的dozer-config.yaml 查询转换成DAG(Directed Acyclic Graph)的形式。DAG 定义了查询的流式执行,其中每个节点都是一个 source、一个 processor 或一个 sink。比如,下面是给 BOOKING DETAILS 查询生成的 DAG:
可以注意到,我们有两个connectors( bookings_conn 和 flights_conn),每个都有多个 output ports,对应一个数据库表。所有的连接都进入一个product node,负责执行 join 操作,然后是一个projection node,它将执行 field selection。
一旦启动,Dozer就会连接到源数据库,有选择地获取数据库表并开始监听 CDC 事件。然后将这些事件注入 DAG。
每个节点本质上都是一个 processor,能够处理3种类型的操作: INSERT、 DELETE和 UPDATEs。每当接收到 CDC 消息时,每个 processor 都会更新其内部状态,并停止、传播或生成 新消息,并通知下游节点。这些事件流最终传播到缓存中,以便其状态始终与源保持同步。
如果你有兴趣更深入地了解 Dozer,可访问 GitHub 。
别忘了在 GitHub 点个 ⭐️ +分享哦!感谢支持~
也欢迎加入我们的 Discord 频道~
如想使用此示例,请访问 GitHub samples repo。