gRPC op de Client Side

De meeste communicatiecomponenten tussen systemen die REST gebruiken, serialiseren hun payload in JSON. Tot op heden mist JSON een breed gebruikte standaard voor schema-validatie: JSON Schema is niet wijdverspreid. Standaard schema-validatie stelt ons in staat om de validatie over te laten aan een externe bibliotheek en er mee te zijn. Zonder één moeten we terugvallen op handmatige validatie in de code. Nog erger, we moeten de validatiecode in overeenstemming houden met het schema.

XML heeft schema-validatie standaard ingebouwd: een XML-document kan een grammatica declareren waaraan het moet voldoen. SOAP, dat op XML is gebaseerd, profiteert ook daarvan.

Andere serialisatie-alternatieven bieden een schema-validatie-optie: bijvoorbeeld Avro, Kryo, en Protocol Buffers. Interessant genoeg gebruikt gRPC Protobuf om RPC tussen gedistribueerde componenten aan te bieden:

gRPC is een moderne, open source, hoogwaardige Remote Procedure Call (RPC)-framework dat in elke omgeving kan draaien. Het kan efficiënt diensten verbinden in en rond data centers met uitwisselbare ondersteuning voor load balancing, tracing, gezondheidscontrole en authenticatie. Het is ook toepasbaar in het laatste deel van gedistribueerde computing om apparaten, mobiele applicaties en browsers te verbinden met back-endservices.

Waarom gRPC?

Bovendien is Protocol een binair serialisatiemechanisme, wat veel bandbreedte bespaart. Daarom is gRPC een uitstekende optie voor communicatie tussen systemen. Maar als al je componenten gRPC gebruiken, hoe kunnen dan eenvoudige clients ermee communiceren? In deze post gaan we een gRPC-service bouwen en laten zien hoe je deze kunt aanroepen via cURL.

A Simple gRPC Service

De gRPC documentatie is uitgebreid, dus hier is een samenvatting:

  • gRPC is een Remote Procedure Call-framework.
  • Het werkt in een breed scala aan talen.
  • Het is gebaseerd op Protocol Buffers:
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

  • Het maakt deel uit van het CNCF portfolio en is momenteel in de opstartfase.

Laten we onze gRPC-service instellen. We gaan Java, Kotlin, Spring Boot en een specifiek gRPC Spring Boot-integratieproject gebruiken. De projectstructuur bevat twee projecten: één voor het model en één voor de code. Laten we beginnen met het modelproject.

I didn’t want something complicated. Reusing a simple example is enough: the request sends a string, and the response prefixes it with Hello. We design this model in a dedicated Protobuf schema file:

ProtoBuf

 

syntax = "proto3";                                        //1

package ch.frankel.blog.grpc.model;                       //2

option java_multiple_files = true;                        //3
option java_package = "ch.frankel.blog.grpc.model";       //3
option java_outer_classname = "HelloProtos";              //3

service HelloService {                                    //4
    rpc SayHello (HelloRequest) returns (HelloResponse) {
    }
}

message HelloRequest {                                    //5
    string name = 1;                                      //6
}

message HelloResponse {                                   //7
    string message = 1;                                   //6
}

  1. Protobuf-definitieversie
  2. Pakket
  3. Java-specifieke configuratie
  4. Service-definitie
  5. Verzoekdefinitie
  6. Veldenefinitie: Eerst komt het type, dan de naam en tenslotte de volgorde.
  7. Antwoorddefinitie

We zullen Maven gebruiken om de Java-sjablooncode te genereren:

XML

 

<project>
  <dependencies>
    <dependency>
      <groupId>io.grpc</groupId>                         <!--1-->
      <artifactId>grpc-stub</artifactId>
      <version>${grpc.version}</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>                         <!--1-->
      <artifactId>grpc-protobuf</artifactId>
      <version>${grpc.version}</version>
    </dependency>
    <dependency>
      <groupId>jakarta.annotation</groupId>              <!--1-->
      <artifactId>jakarta.annotation-api</artifactId>
      <version>1.3.5</version>
      <optional>true</optional>
    </dependency>
  </dependencies>
  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>                 <!--2-->
        <artifactId>os-maven-plugin</artifactId>
        <version>1.7.1</version>
      </extension>
    </extensions>
    <plugins>
      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>    <!--3-->
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>${protobuf-plugin.version}</version>
        <configuration>
          <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

  1. Afhankelijkheden tijdens compilatie
  2. Ruik informatie over het besturingssysteem; gebruikt in de volgende plugin
  3. Genereer Java-code uit het proto-bestand.

Na compilatie, zou de structuur ongeveer zo moeten zijn:

We kunnen de klassen in een JAR verpakken en gebruiken in een web app-project. Het laatste is in Kotlin, maar alleen omdat het mijn favoriete JVM-taal is.

We hebben enkel een specifieke Spring Boot starter-afhankelijkheid nodig om gRPC-eindpunten te integreren met Spring Boot:

XML

 

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-server-spring-boot-starter</artifactId>
  <version>2.14.0.RELEASE</version>
</dependency>

Hier is het belangrijke deel:

Kotlin

 

@GrpcService                                                        //1
class HelloService : HelloServiceImplBase() {                       //2
  override fun sayHello(
      request: HelloRequest,                                        //2
      observer: StreamObserver<HelloResponse>                       //3
  ) {
    with(observer) {
      val reply = HelloResponse.newBuilder()                        //2
                               .setMessage("Hello ${request.name}") //4
                               .build()
      onNext(reply)                                                 //5
      onCompleted()                                                 //5
    }
  }
}

  1. De grpc-server-spring-boot-starter detecteert de annotatie en werkt zijn betovering.
  2. Referentieklassen gegenereerd in bovenstaand project
  3. De methode-handtekening staat een StreamObserver-parameter toe. De klasse komt van grpc-stub.jar
  4. Haal de aanvraag op en voeg een prefix toe om de antwoordbericht te bouwen.
  5. Speel de gebeurtenissen.

We kunnen nu de web app starten met ./mvnw spring-boot:run.

Testen van de gRPC-Service

Het hele idee achter de post is dat het onmogelijk is om de gRPC-service te benaderen met reguliere tools. Om te testen hebben we toch een toegewijd hulpmiddel nodig. Ik vond grpcurl. Laten we het installeren en gebruiken om de beschikbare services te vermelden:

Shell

 

grpcurl --plaintext localhost:9090 list   #1-2

  1. Verwijs alle beschikbare gRPC-serviceszonder TLS-verificatie. 
  2. Om botsingen tussen gRPC en andere kanalen, zoals REST, te voorkomen, gebruikt Spring Boot een ander poortnummer.
Plain Text

 

ch.frankel.blog.grpc.model.HelloService   #1
grpc.health.v1.Health                     #2
grpc.reflection.v1alpha.ServerReflection  #2

  1. De gRPC-service die we hebben gedefinieerd
  2. Twee extra services aangeboden door de aangepaste starter

We kunnen ook in de structuur van de service duiken:

Shell

 

grpcurl --plaintext localhost:9090 describe ch.frankel.blog.grpc.model.HelloService
Java

 

service HelloService {
  rpc SayHello ( .ch.frankel.blog.grpc.model.HelloRequest ) returns ( .ch.frankel.blog.grpc.model.HelloResponse );
}

Eindelijk kunnen we de service aanroepen met data:

Shell

 

grpcurl --plaintext -d '{"name": "John"}' localhost:9090 ch.frankel.blog.grpc.model.HelloService/SayHello
JSON

 

{
  "message": "Hello John"
}

Toegang tot de gRPC-service met reguliere tools

Stel je voor dat we een reguliere JavaScript-clienttoepassing hebben die toegang moet krijgen tot de gRPC-service. Wat zijn de alternatieven?

De algemene aanpak is via grpc-web:

A JavaScript implementation of gRPC for browser clients. For more information, including a quick start, see the gRPC-web documentation.

gRPC-web-clients verbinden zich met gRPC-services via een speciale proxy; standaard gebruikt gRPC-web Envoy.

In de toekomst verwachten we dat gRPC-web wordt ondersteund in taalspecifieke webframeworks voor talen zoals Python, Java en Node. Voor details, zie de roadmap.

grpc-web

De beschrijving vermeldt een enkele beperking: het werkt alleen voor JavaScript (tot nu toe). Er is echter nog een andere. Het is behoorlijk intrusief. Je moet het proto bestand krijgen, boilerplate code genereren en je code laten aanroepen. Je moet dit doen voor elke clienttype. Nog erger, als het proto bestand verandert, moet je de clientcode in elk van hen opnieuw genereren.

Er is echter een alternatief, als je een API Gateway gebruikt. Ik zal beschrijven hoe je dit kunt doen met Apache APISIX, maar misschien kunnen andere gateways hetzelfde doen. grpc-transcode is een plugin die gRPC-oproepen naar REST en terug kan transcoderen.

De eerste stap is het registreren van het proto bestand in Apache APISIX:

Shell

 

curl http://localhost:9180/apisix/admin/protos/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d "{ \"content\": \"$(sed 's/"/\\"/g' ../model/src/main/proto/model.proto)\" }"

Het tweede stap is het aanmaken van een route met bovenstaande plugin:

Shell

 

curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/helloservice/sayhello",                           #1
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",                                       #2
      "service": "ch.frankel.blog.grpc.model.HelloService",  #3
      "method": "SayHello"                                   #4
    }
  },
  "upstream": {
    "scheme": "grpc",
    "nodes": {
      "server:9090": 1
    }
  }
}'

  1. Definieer een fijne route.
  2. Verwijs naar het proto-bestand dat gedefinieerd is in de vorige opdracht.
  3. gRPC service
  4. gRPC methode

Op dit moment kan elke client een HTTP-verzoek doen naar het gedefinieerde eindpunt. Apache APISIX zal de oproep omzetten naar gRPC, deze doorsturen naar de gedefinieerde service, het antwoord ontvangen en het opnieuw omzetten.

Shell

 

curl localhost:9080/helloservice/sayhello?name=John
JSON

 

{"message":"Hello John"}

In vergelijking met grpc-web, stelt de API Gateway-aanpak het mogelijk om het proto-bestand te delen met slechts één component: de Gateway zelf.

Voordelen van Omzetting

Op dit moment kunnen we de mogelijkheden van de API Gateway benutten. Stel dat we een standaardwaarde willen als er geen name wordt doorgegeven, bijvoorbeeld World. Ontwikkelaars zouden dit graag in de code zetten, maar elke verandering in de waarde vereist een volledige build en implementatie. Veranderingen kunnen bijna-direct zijn als we de standaardwaarde in de routes verwerkingsketen van de Gateway plaatsen. Laten we onze route dienovereenkomstig aanpassen:

Shell

 

curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/helloservice/sayhello",
  "plugins": {
    "grpc-transcode": {
      ...
    },
    "serverless-pre-function": {                    #1
      "phase": "rewrite",                           #2
      "functions" : [
        "return function(conf, ctx)                 #3
          local core = require(\"apisix.core\")
          if not ngx.var.arg_name then
            local uri_args = core.request.get_uri_args(ctx)
            uri_args.name = \"World\"
            ngx.req.set_uri_args(uri_args)
          end
        end"
      ]
    }
  },
  "upstream": {
      ...
  }
}'

  1. Algemene al-doel plugin wanneer geen past
  2. Herformuleer het verzoek.
  3. Magisch Lua-code die het werkt

Nu kunnen we het verzoek uitvoeren met een lege argument en het verwachte resultaat krijgen:

Shell

 

curl localhost:9080/helloservice/sayhello?name
JSON

 

{"message":"Hello World"}

Conclusie

In deze post hebben we kort gedefinieerd wat gRPC is en hoe het voordelen biedt voor communicatie tussen services. We hebben een eenvoudige gRPC-service ontwikkeld met behulp van Spring Boot en grpc-server-spring-boot-starter. Het heeft echter wel een nadeel: reguliere clients kunnen de service niet benaderen. We moesten overstappen op grpcurl om het te testen. Hetzelfde geldt voor clients gebaseerd op JavaScript – of de browser.

Om deze beperking te omzeilen, kunnen we een API Gateway inzetten. Ik heb laten zien hoe je Apache APISIX kunt configureren met de grpc-transcode plugin om het gewenste resultaat te bereiken.

De volledige broncode voor deze post is te vinden op GitHub.

Verder Gaan

Source:
https://dzone.com/articles/grpc-on-the-client-side