React,GraphQL,gRPC和反应式微服务-Appwish平台中的数据流说明

Hello everyone! In this post, I'll explain how Vert.x, gRPC and GraphQL is used in Appwish project to provide APIs for UI applications.
We'll use two backend microservices as an example - graphqlapi and wishservice.

If you're unfamiliar with Appwish, it's an open-source project that I started two weeks ago in this dev.to post. It was supposed to be a kind of tutorial, but after over 130 people joined me on Slack I decided to switch the focus to building an open-source community with the goal of releasing a real-world platform that is fully open-sourced and community-driven.

To learn the dataflow in Appwish let's first take a look at the diagram:

Architecture

该图显示了Appwish中的数据流和组件之间的交互。

让我们讨论它如何从UI到数据库。

It all started in the frontend

UI, using a GraphQL client, sends a GraphQL query that hits our first backend microservice - GraphQL API.

这部分标记在下图中:

GraphQL Query

To send GraphQL queries, the client needs to know the GraphQL schema, which is declared in .graphqls files. The GraphQL server parses those files and uses them as a contract - contract for UI - backend communication.

假设客户端发送了以下查询:

query {
 wish(id: 123) {
 title
 content
 rating
 }
}

在这种情况下,GraphQL服务器必须返回UI要求的数据(id = 123的愿望的标题,内容和等级)

It can't do it on its own

即使创建了架构,GraphQL服务器也将无法提供UI要求的数据。

不知道怎么办! 您需要将架构连接到一些可执行代码,这些代码能够获取所需数据或执行适当的业务逻辑。

换句话说,在GraphQL模式中声明的所有操作都需要与可以交付的代码相关联 所需数据。

This is done by so-called RuntimeWirings and DataFetchers. A RuntimeWiring associates one of GraphQL types e.g. a "Query wish" with a DataFetcher (simply - some code that can fetch wish data).

Here's an example of wiring GraphQL types related to wishes with DataFetchers in our GraphQL server application.

提供RuntimeWirings和DataFetchers之后,GraphQL服务器将知道如何传递所需的数据。

Let's discuss how we implement DataFetchers then.

As you probably already know, Appwish use Vert.x as a main backend framework and gRPC for service-to-service communication. One of core Vert.x components is the Event Bus. Event Bus can be used as a communication channel between various parts of the application. It can be even used to communicate with other apps, but in Appwish we don't use this feature - we use gRPC to talk to other backendapplications. The Event Bus is used only for internal communication inside applications.

要获取有关愿望的数据,DataFetcher必须与另一个服务– wishservice进行对话。 Wishservice负责 管理愿望数据和相关的业务逻辑。

To do so, it must use a gRPC request. But as you can see here,
WishFetcher does not use gRPC. Instead, it sends events on the event bus. "Gimme data about wish id=123!".

可以在下图中的红色圆圈中看到此部分:

Fetcher Request

DataFetcher在事件总线上发送事件,然后由gRPC Verticle处理。

Verticle is another important concept of Vert.x. For simplicity, you may think of it as an event loop that processes events. It's conceptually really similar to Node.js' event loop - it also implements the Reactor pattern.

gRPC Verticle侦听DataFetchers发送的事件,并使用gRPC客户端从其他微服务获取所需数据。

gRPC顶点使用gRPC服务存根(客户端)从其他微服务获取所需数据的部分 如下图所示:

gRPC Request

How does this gRPC communication happen?

实际上,gRPC客户端是生成的“服务存根”。 使用gRPC时,默认情况下,您还将使用protobuf。 gRPC使用protobuf作为底层消息交换格式以及其IDL(接口定义语言)。

We create .proto files that are then used to generate code in any programming language we want - in this case, Java.

例如,这:

message WishProto {
 int64 id = 1;
 int64 authorId = 2;
 string title = 3;
 string content = 4;
 string url = 5;
 string cover_image_url = 6;
}

用于生成一种数据传输对象类,该对象类包含我们需要的数据并非常有效地进行了序列化 并反序列化以进行网络传输。

这部分:

service WishService {
 rpc GetAllWish (AllWishQueryProto) returns (AllWishReplyProto) {
 }
 rpc GetWish (WishQueryProto) returns (WishReplyProto) {
 }
 rpc CreateWish (WishInputProto) returns (WishReplyProto) {
 }
 rpc UpdateWish (UpdateWishInputProto) returns (WishReplyProto) {
 }
 rpc DeleteWish (WishQueryProto) returns (WishDeleteReplyProto) {
 }
}

定义与愿望服务通信的接口。 所有rpc方法都有名称,输入和输出。

使用此声明,我们可以生成Java代码,然后将其用于与wish服务和其他服务进行通信 应用程序。

当我们从.proto文件中的服务声明生成源代码时,我们同时获得了客户端和服务器。 希望服务使用生成的服务器来处理来自其他应用程序的请求,其他应用程序使用生成的客户端来发送请求。

在客户端,生成的客户端(服务存根)就是您与愿望服务进行通信所需的全部。 您不需要执行任何操作(当然,除了处理从其他应用程序接收到的数据的逻辑外)。

You can see the use of generated client here..

我将快速介绍存根的一种用法:

    eventBus.<WishQueryProto>consumer(Address.WISH.get(), event -> {
      stub.getWish(event.body(), grpc -> {
        if (grpc.succeeded()) {
          event.reply(grpc.result());
        } else {
          event.fail(FAILURE_CODE, WISH_SERVICE_COMMUNICATION_ERROR_MESSAGE);
        }
      });
    });

gRPC客户端服务注册了一个事件使用者,该使用者接受“ WishQueries”作为输入。

当事件发生时(当DataFetchers请求愿望数据时),将执行上面的代码。

Stub.getWish()被调用。 存根是生成的愿望服务gRPC客户端,而getWish对应于我们在.proto文件中声明的内容:

 rpc GetWish (WishQueryProto) returns (WishReplyProto) {
 }

如您所见,我们使用WishQueryProt作为输入,调用stub.GetWish()方法,并等待WishReply输出。

It exactly matches what we declared in .proto file service definition.

等待来自另一个应用程序的结果是异步的,因此等待结果的代码被声明为lambda回调,当结果到达时将调用该回调:

grpc -> {
        if (grpc.succeeded()) {
          event.reply(grpc.result());
        } else {
          event.fail(FAILURE_CODE, WISH_SERVICE_COMMUNICATION_ERROR_MESSAGE);
        }
      }

如果gRPC调用成功,我们将事件总线上的REPLY连同gRPC请求结果(WishReply)发送到DataFetcher。 否则,我们会通知DataFetcher有关失败的信息。

好的,那很难。 从UI发送的GraphQL查询通过了GraphQL服务器,DataFetcher,gRPC客户端,现在已经到达 在愿望服务gRPC服务器中。

Let's now discuss how gRPC calls are served by the gRPC server

要再次实现gRPC服务器,我们需要遵循在.proto文件中声明的内容:

service WishService {
 rpc GetAllWish (AllWishQueryProto) returns (AllWishReplyProto) {
 }
 rpc GetWish (WishQueryProto) returns (WishReplyProto) {
 }
 rpc CreateWish (WishInputProto) returns (WishReplyProto) {
 }
 rpc UpdateWish (UpdateWishInputProto) returns (WishReplyProto) {
 }
 rpc DeleteWish (WishQueryProto) returns (WishDeleteReplyProto) {
 }
}

生成的gRPC服务器具有5种我们必须重写/实现的方法-GetAllWish,GetWish,CreateWish,UpdateWish和DeleteWish及其相应的输入和输出(根据.proto文件的所有内容)。

All we have to do is to provide implementation for those methods.

In wish service it's done here.

让我们看一下一种覆盖的方法:

  /**
   * This method gets invoked when other service (app, microservice) invokes stub.getWish(...)
   */
  @Override
  public void getWish(final WishQueryProto request, final Promise<WishReplyProto> response) {
    eventBus.<Optional<Wish>>request(
      Address.FIND_ONE_WISH.get(), converter.toDomain(WishQuery.class, request),
      event -> {
        if (event.succeeded() && event.result().body().isPresent()) {
          response.complete(converter.toProtobuf(WishReplyProto.class, new WishReply(event.result().body().get())));
        } else if (event.succeeded()) {
          response.complete();
        } else {
          response.fail(event.cause());
        }
      });
  }

如您所见,方法名称与我们在.proto文件中声明的名称匹配。 我们还将获得WishQuery输入和WishReply的承诺。

我们要做的就是获取输入,与数据库对话以获取所需的数据并解决WishReply承诺。

当我们使用Vert.x时,它是使用事件总线实现的。 我们发送事件总线请求,并等待直到其中一个处理程序响应我们所需的内容。

这部分标记在下图上:

Database Event Bus Request

gRPC服务器使用事件总线来请求愿望数据。 在事件总线的第二侧,有一个数据库Verticle,它消耗这些请求,与数据库对话并响应结果:

Database Access

数据库Verticle使用DatabaseService注册用于侦听希望数据请求的处理程序。

Here's the implementation of the DatabaseService.

如您所见,事件总线处理程序的声明如下:

  public void registerEventBusEventHandlers() {
    eventBus.<AllWishQuery>consumer(Address.FIND_ALL_WISHES.get())
      .handler(event -> wishRepository.findAll(event.body()).setHandler(findAllHandler(event)));

    eventBus.<WishQuery>consumer(Address.FIND_ONE_WISH.get())
      .handler(event -> wishRepository.findOne(event.body()).setHandler(findOneHandler(event)));

    eventBus.<WishInput>consumer(Address.CREATE_ONE_WISH.get())
      .handler(event -> wishRepository.addOne(event.body()).setHandler(addOneHandler(event)));

    eventBus.<UpdateWishInput>consumer(Address.UPDATE_ONE_WISH.get())
      .handler(event -> wishRepository.updateOne(event.body()).setHandler(updateOneHandler(event)));

    eventBus.<WishQuery>consumer(Address.DELETE_ONE_WISH.get())
      .handler(event -> wishRepository.deleteOne(event.body()).setHandler(deleteOneHandler(event)));
  }

它们通过WishQueries / Inputs使用事件总线事件。 发生事件时,他们使用wishRepository(实际上具有数据库客户端和与SQL查询相关的逻辑的代码)与数据库进行交互。

The database calls are asynchronous thanks to the Reactive Vert.x Postgres Client.

这就是为什么我们为wishRepository方法的这些异步结果设置处理程序的原因:

// setHandler to react to async result
wishRepository.findOne(event.body()).setHandler(findOneHandler(event))

处理程序只是简单地将REPLIES发送到gRPC Verticle,如果成功则通过事件总线从数据库请求数据,或者如果发生错误则通知失败:

  private Handler<AsyncResult<Optional<Wish>>> updateOneHandler(
    final Message<UpdateWishInput> event) {
    return query -> {
      if (query.succeeded()) {
        event.reply(query.result(), new DeliveryOptions().setCodecName(Codec.WISH.getCodecName()));
      } else {
        event.fail(1, "Error updating the wish in the database");
      }
    };
  }

If the whole flow succeeds, the data is returned to the UI that sent the initial GraphQL request

Returning Data

Conclusions

Okay, that's all I wanted to cover in this post. I hope it gave you a high-level overview of the dataflow in Appwish project. There's so many concepts used that I couldn't cover everything in detail - if you want to dig deeper you can use links I put in the post, write a comment or ask me a question on Slack.

I'd also like to invite you to contribute to the project.

Everything we do is 100% open-source and transparent. Over 130 people already joined us on Slack. We're slowly starting with the implementation. If you want more information, please read my previous posts, write a comment or join the Slack channel where I give frequent updates about the project status.

from: https://dev.to//pjeziorowski/react-graphql-grpc-and-reactive-microservices-the-dataflow-in-appwish-platform-explained-34ag

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值