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