Twirp vs gRPC. Is it worth it?
If you use a micro-service architecture, you most likely know that the overhead of communication between services often becomes a significant problem, and if you have encountered this problem, you most likely started using one of the RPC frameworks on top of Protobuf , for example, gRPC from Google or Go-Kit by Peter Bourgon or something else. Retelling what it is and how to use it makes no sense, everything is fairly well described before me. I myself actively use gRPC in my projects, but here Twich decided to release my implementation of protobuf Twirp . If you are wondering why they needed it or how it differs, go under the cat.
First of all, let's look at the reasons that made Twich release their own version of ProtoBuf:
As you can see, simplicity is the main reason Twich decided to write their implementation of Protobuf.
Now let's see how to use this library.
If you have already set up your development environment on Go, then you need to install the following packages
For example, we write a simple service that increments the value passed as a parameter.
We will generate the code for our client by running the following command
As a result, two files will be created.
Next, we add the implementation of our service
Now, if you start the client with the go run main.go command, the service can be accessed via HTTP:
Or in binary format
In general, the framework itself is almost identical in approach to gRPC, but it is simple to implement and simultaneously supports HTTP 1.1. In my opinion, its applicability, if you need an RPC service with which you plan to simultaneously interact with the UI through HTTP and between services through Protobuf.
References:
First of all, let's look at the reasons that made Twich release their own version of ProtoBuf:
- Lack of support for HTTP 1.1. gRPS relies on HTTP trailers and full-duplex streams. Twirp supports both HTTP 1.1 and HTTP / 2, which is very important because a large number of load balancers (both hardware and software) support only HTTP 1.1 - including the AWS Elastic Load Balancer. But unlike gRPC, Twirp does not support streaming RPC , which is the case when your API is built on the basis of Request-Response and is not required.
- The complexity of implementing the grpc-go library . The library includes a full implementation of HTTP / 2, independent of standard libraries, which makes it difficult to understand and analyze errors.
- GRPC version compatibility. Due to the fact that gRPC is rather complicated, the Go generated code is quite simple and all requests are redirected to grpc-go . This connection leads to the fact that the client is forced to use the same version as the server. And if you have a large number of customers and the service interact with each other, then the version between them should be identical. It is clear that this leads to difficulties in deploying and deploying microservices.
- Twitch also points out that grpc-go requires a specific version of protobuf - github.com/golang/protobuf . But for me this problem seems far-fetched, since protobuf has only one release of version v1.0.0, which is used by all versions of grpc-go.
- gRPC only supports binary message form and message sniffing is very difficult to analyze. Twirp supports both binary message format in protobuf format and non-binary message format in JSON format. This gives you an advantage, say if you want to interact with the service through a regular HTTP Request using JSON
As you can see, simplicity is the main reason Twich decided to write their implementation of Protobuf.
Now let's see how to use this library.
If you have already set up your development environment on Go, then you need to install the following packages
go get github.com/twitchtv/twirp/protoc-gen-twirp
go get github.com/golang/protobuf/protoc-gen-go
For example, we write a simple service that increments the value passed as a parameter.
syntax = "proto3";
service Service {
rpc Increment(Request) returns (Response);
}
message Request {
int32 valueToIncrement = 1; // must be > 0
}
message Response {
int32 IncrementedValue = 1; // must be > 0
}
We will generate the code for our client by running the following command
protoc --proto_path=$GOPATH/src:. --twirp_out=. --go_out=. ./paperclips.protoAs a result, two files will be created.
- Increment.pb.go - contains code generation for messages
- Increment.twirp.go - contains service interfaces and functions
Next, we add the implementation of our service
package main
import (
"fmt"
"log"
"net/http"
"context"
pb "TwirpSample/Server/Twirp"
)
// Server implements the Increment service
type Server struct {
value int32
}
// NewServer creates an instance of our server
func NewServer() *Server {
return &Server{
value: 1,
}
}
// Increment returns the incremented value of request.ValueToIncrement
func (s *Server) Increment(ctx context.Context, request *pb.Request) (*pb.Response, error) {
return &pb.Response{
IncrementedValue: request.ValueToIncrement + 1,
}, nil
}
func main() {
fmt.Printf("Starting Increment Service on :6666")
server := NewServer()
twirpHandler := pb.NewServiceServer(server, nil)
log.Fatal(http.ListenAndServe(":6666", twirpHandler))
}
Now, if you start the client with the go run main.go command, the service can be accessed via HTTP:
curl --request "POST" \
--location "http://localhost:6666/Service/Increment" \
--header "Content-Type:application/json" \
--data '{ValueToIncrement: 0}' \
--verbose
Output:
{"IncrementedValue":1}
Or in binary format
package main
import
(
"fmt"
rpc "TwirpSample/Server/Twirp"
"net/http"
"context"
)
func main() {
fmt.Println("Twirp Client Example.")
client := rpc.NewServiceProtobufClient("http://localhost:6666", &http.Client{})
v, err := client.Increment(context.Background(), &rpc.Request{ValueToIncrement: 11})
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("Value: %d", v.IncrementedValue)
}
Output:
Twirp Client Example.
Value: 11
In general, the framework itself is almost identical in approach to gRPC, but it is simple to implement and simultaneously supports HTTP 1.1. In my opinion, its applicability, if you need an RPC service with which you plan to simultaneously interact with the UI through HTTP and between services through Protobuf.
References: