gRPC 

para quem

só sabe

REST

Ricardo Longa

Front-end

Banco de dados

Mensageria

Back-end

Comunicação?

Não vamos ver...

Instalação do gRPC: Clique aqui

Não vamos ver...

Modelo OSI ou protocolos TCP-IP

HTTP 0.9

HTTP 1.0

HTTP 1.1

HTTP 2.0

"HTTP/2 breaks down the HTTP protocol communication into an exchange of binary-encoded frames, which are then mapped to messages that belong to a particular stream, all of which are multiplexed within a single TCP connection" (web.dev)

HTTP 3.0

Uma API Rest

type User struct {
    Name        string    `json:"name"`
    Coordinates []float64 `json:"coordinates"`
}

func main() {
    engine := gin.New()
    engine.GET("/users/:id", GetUser)
    engine.Run()
}

func GetUser(ctx *gin.Context) {
    userID := ctx.Param("id")
	
    loadedUser, err := usersRepository.GetByID(ctx, userID)
    // tratamento de erro ocultado

    ctx.JSON(http.StatusOK, loadedUser)
}

Protocol Buffers

syntax = "proto3";

package v1;

option go_package = "infrastructure/server/grpc";

message User {
    string name = 1;
    repeated double coordinates = 2;
}
protoc \
    --go_out=. --go-grpc_out=. \
    api/user.proto
package main

import (
	"encoding/json"
	"github.com/golang/protobuf/proto"
	...
)

type User struct {
	Name        string    `json:"name"`
	Coordinates []float64 `json:"coordinates"`
}

func main() {
	userGrpc := grpc.User{
		Name:        "Ricardo Longa",
		Coordinates: []float64{12.1111, 21.2222},
	}
	userGrpcBytes, _ := proto.Marshal(&userGrpc)
	fmt.Printf("%v\n", len(userGrpcBytes))

	userJson := User{
		Name:        "Ricardo Longa",
		Coordinates: []float64{12.1111, 21.2222},
	}
	userJsonBytes, _ := json.Marshal(&userJson)
	fmt.Printf("%v\n", len(userJsonBytes))
}

// 33
// 56

fonte: clique aqui

Uma API gRPC

syntax="proto3";

package v1;

option go_package="infrastructure/server/grpc";

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

message User {
    string id = 1;
    string name = 2;
    repeated double coordinates = 3;
    google.protobuf.Timestamp created_at = 4;
}

message GetByIDInput {
    string id = 1;
}

service UserService {
    rpc GetByID(GetByIDInput) returns (User);
}
protoc \
    --go_out=. --go-grpc_out=. \
    api/user.proto
// UserServiceServer is the server API for UsersService service.
// All implementations must embed UnimplementedUserServiceServer
// for forward compatibility
type UserServiceServer interface {
    GetByID(context.Context, *GetByIDInput) (*User, error)
    mustEmbedUnimplementedUserServiceServer()
}
func NewUserController(users *service.Users) *userController {
	return &UsersController{usersService: users}
}

type userController struct {
	api.UnimplementedUserServiceServer
	usersService *service.Users
}

func (uc *userController) GetByID(ctx context.Context, i *api.GetByIDInput) (*api.User, error) {
    userID := i.Id
    if strings.TrimSpace(userID) == "" {
    	return nil, errors.New("id is required")
    }
    
    loadedUser, err := uc.usersService.GetByID(ctx, userID)
    // tratamento de erro ocultado
    
    return presenters.ToGrpcUser(loadedUser), nil
}
import (
	gen ".../internal/infrastructure/http/grpc"
	"google.golang.org/grpc"
)


func main() {
	server := grpc.NewServer()
    
	gen.RegisterUsersServer(server, NewUsersController(nil))
    
	listener, _ := net.Listen("tcp", "localhost:50051")
    
	log.Fatal(server.Serve(listener))
}

O lado cliente em Go

type UserServiceClient interface {
    GetByID(ctx context.Context, in *GetByIDInput, opts ...grpc.CallOption) (*User, error)
}

type userServiceClient struct {
    cc grpc.ClientConnInterface
}

func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
    return &userServiceClient{cc}
}

func (c *userServiceClient) GetByID(ctx context.Context, in *GetByIDInput, 
    opts ...grpc.CallOption) (*User, error) {
    
    out := new(User)
    err := c.cc.Invoke(ctx, "/v1.UserService/GetByID", in, out, opts...)
    if err != nil {
    	return nil, err
    }
    return out, nil
}
type usersClient struct {
	userServiceClient gen.UserServiceClient
}

func NewUsersClient(usersGrpcHost string) (*usersClient, error) {
	var err error
	var cc *grpc.ClientConn

	if strings.Contains(usersGrpcHost, "443") {
		pool, _ := x509.SystemCertPool()
		creds := credentials.NewClientTLSFromCert(pool, "")

		cc, _ = grpc.Dial(usersGrpcHost, grpc.WithTransportCredentials(creds))
	} else {
		cc, _ = grpc.Dial(usersGrpcHost, grpc.WithInsecure())
	}

	return &usersClient{
		userServiceClient: gen.NewUserServiceClient(cc),
	}, nil
}

func (u *usersClient) GetUser(ctx context.Context, userID string) (*domains.User, error) {
    md := map[string]string{"another_info": "..."}

    outgoingContext := metadata.NewOutgoingContext(ctx, metadata.New(md))
    getByIDInput := gen.GetByIDInput{Id: userID})
    
    userGrpc, err := u.userServiceClient.GetByID(outgoingContext, &getByIDInput)
    if err != nil {
    	return nil, err
    }

    return userGrpc.ToEntity(), nil
}

Streams

syntax="proto3";

package v1;

option go_package="infrastructure/server/grpc";

message UserCoordinatesInput {
  string id = 1;
  repeated double coordinates = 2;
}

message Empty {}

service UserService {
    rpc UpdateUserCoordinates(stream UserCoordinatesInput) returns (Empty);
}
func (uc *userController) UpdateUserCoordinates(stream api.UserService_UpdateUserCoordinatesServer) error {
    userInterface := stream.Context().Value("user")
    user, _ := userInterface.(*domains.User)
    
    for {
    	input, err := stream.Recv()
    	if err == io.EOF {
    		return stream.SendAndClose(&emptypb.Empty{})
    	}
    	if err != nil {
    		return err
    	}

    	userCoordinates := &domains.UserCoordinates{
    		ID: user.ID,
    		Location: &domains.Location{
    			Coordinates: input.Coordinates,
    		},
    	}
        usersCoordinatesRepository.Upsert(context.Background(), userCoordinates)
    }
}

grpc-web

FROM envoyproxy/envoy-alpine:v1.17.0
COPY envoy.yaml /etc/envoy/envoy.yaml
RUN apk --no-cache add ca-certificates
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 8080 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                codec_type: auto
                stat_prefix: ingress_http
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: local_service
                      domains: ["*"]
                      routes:
                        - match: { prefix: "/v1.OrdersService" }
                          route:
                            cluster: orders-proxy
                            auto_host_rewrite: true
                            max_stream_duration:
                              grpc_timeout_header_max: 0s
                      cors:
                        ...
                http_filters:
                  - name: envoy.filters.http.grpc_web
                  - name: envoy.filters.http.cors
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
static_resources:
  clusters:
    - name: orders-proxy
      type: strict_dns
      connect_timeout: 20s
      http2_protocol_options: {}
      lb_policy: round_robin
      dns_refresh_rate: 90s
      load_assignment:
        cluster_name: orders-proxy
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: <service_address>
                      port_value: 443
      dns_lookup_family: V4_ONLY
      transport_socket:
        name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
          common_tls_context:
            alpn_protocols: h2
            validation_context:
              trusted_ca:
                filename: /etc/ssl/certs/ca-certificates.crt
          sni: <service_address>
message CancelOrderRequest {
    string order_id = 1;
    optional bool with_fee = 2;
}

message CheckIfCancelOrderHasFeeRequest {
    string order_id = 1;
}

message CheckIfCancelOrderHasFeeResponse {
    bool has_cancel_fee = 1;
    optional int64 fee_value = 2;
}

service OrdersService {
    // Admin ou estabelecimento
    rpc CheckIfCancelOrderHasFee(CancelOrderRequest) returns (CheckIfCancelOrderHasFeeResponse);
    rpc CancelOrder(CancelOrderRequest) returns (google.protobuf.Empty);
}
protoc \
    google/protobuf/empty.proto \
    google/protobuf/timestamp.proto \
	orders.proto \
	-I=/Users/longa/go/src/source.cloud.google.com/voa-delivery/orders/api \
	--js_out=import_style=commonjs,binary:./src/proto \
	--grpc-web_out=import_style=typescript,mode=grpcwebtext:./src/proto
export class OrdersService implements Orders {
    ordersClient: OrdersServiceClient;

    constructor() {
        this.ordersClient = new OrdersServiceClient(process.env.ENVOY_URL, null, null);
    }

    async checkIfCancelOrderHasFeeGrpc(orderID: string): Promise<number> {
        const token = await Services.getInstance().auth.getToken();

        const request = new CancelOrderRequest();
        request.setOrderId(orderID);

        const response = await this.ordersClient.checkIfCancelOrderHasFee(request, {'x-token': token});
        return response.getFeeValue();
    }

    async cancelOrderGrpc(orderID: string): Promise<void> {
        const token = await Services.getInstance().auth.getToken();

        const request = new CancelOrderRequest();
        request.setOrderId(orderID);

        this.ordersClient.cancelOrder(request, {'token': token}, (e: grpcWeb.RpcError, response: Empty) => {
            if (e == null) {
                return;
            }
            throw e;
        });
    }
}

Não vamos ver...

Benchmarks (Rest x gRPC): Clique aqui

+1.2M

+133k

entregas

intermediações

"Reconheça e agradeça àqueles que estiveram ou seguem na luta com você. Gratidão por aquele que me ensinou na prática que desistir nunca é uma opção."

Dedico esta palestra ao meu pai!
⭐ 17/07/1943     ✝️ 19/03/2022

gRPC para quem só sabe REST

By Ricardo Longa

gRPC para quem só sabe REST

  • 350