Using Twirp with Go: A Quick Guide
Twirp, developed by Twitch, is a framework for service-to-service communication that leverages Protocol Buffers (Protobuf) for defining and implementing RPC (Remote Procedure Call) APIs. In this guide, we'll take a quick dive into how you can use Twirp with Go (often referred to as Golang) to build efficient and maintainable microservices.
Why Twirp?
Language-agnostic: Twirp relies on Protobuf, which supports various languages, making it easier to build cross-language services.
Simple and Clear: Unlike the complexities of gRPC, Twirp provides a much simpler interface and clearer error handling.
HTTP-based: Twirp uses HTTP/1.1 and HTTP/2, allowing for easier debugging and compatibility.
Alternatives
1. Twirp vs. gRPC
Protobufs: Both Twirp and gRPC use protobufs as their Interface Definition Language (IDL), ensuring strong typing and efficient serialization/deserialization.
Simplicity: Twirp emphasizes simplicity in its design. Unlike gRPC, which introduces concepts like streams and channels, Twirp's design is more straightforward with only unary RPCs.
Transport Layer: gRPC uses HTTP/2 by default and is optimized for it, offering features like multiplexing and streaming. Twirp, on the other hand, operates over standard HTTP, allowing for broader compatibility with existing infrastructure and tools. However, it can miss out on some of the efficiencies of HTTP/2.
Error Handling: Twirp has built-in error codes similar to HTTP status codes, making errors more comprehensible and directly mappable to HTTP responses. gRPC uses its set of status codes.
Language Support: gRPC has a wide range of supported languages due to its larger ecosystem. Twirp's main implementations are in Go and Python, though community support for other languages exists.
2. Twirp vs. JSON-RPC
Data Format: Twirp uses protobufs, offering both a JSON and protobuf-based wire format. JSON-RPC, as the name suggests, only uses JSON.
IDL: While Twirp has a clear IDL (protobufs), JSON-RPC doesn't prescribe a specific IDL, which can lead to looser contracts between services.
Error Handling: Twirp offers more structured error handling with built-in error codes, while JSON-RPC has a more generic error object structure.
HTTP Methods: Twirp uses standard HTTP methods (
POST
for service calls). In contrast, JSON-RPC typically usesPOST
for all requests, regardless of the operation.
3. Twirp vs. REST
Style: REST is an architectural style, whereas Twirp is an RPC framework. While REST focuses on representing resources and their state, Twirp, like other RPC systems, emphasizes calling functions or methods.
Data Format: Twirp uses protobufs, allowing for efficient binary serialization. REST, while not limited to, commonly uses JSON.
Standardization: Twirp provides a more structured and strict contract between client and server due to protobufs. REST doesn't enforce a specific contract, leading to potential inconsistencies across implementations.
Verbosity: Twirp routes are more verbose (often reflecting the full method name), whereas REST emphasizes concise, resource-based URLs.
Error Handling: REST relies on standard HTTP status codes for errors. Twirp has its own set of error codes but maps them transparently to HTTP status codes.
Getting Started
1. Install the Necessary Tools
Before diving into the code, ensure you have the necessary tools:
# Install Protocol Buffers Compiler (protoc)
$ brew install protobuf
# Install the Twirp generator for Go
$ go get github.com/twitchtv/twirp/protoc-gen-twirp
2. Define Your Service
Create a file called helloworld.proto
:
syntax = "proto3";
package helloworld;
service HelloWorld {
rpc Hello(HelloReq) returns (HelloResp);
}
message HelloReq {
string name = 1;
}
message HelloResp {
string message = 1;
}
This defines a simple service with a single Hello
method that takes in a HelloReq
and returns a HelloResp
.
3. Generate Go Code
Run the following command to generate Go code from the .proto
file:
$ protoc --go_out=paths=source_relative:. --twirp_out=paths=source_relative:. helloworld.proto
This will generate two files: helloworld.twirp.go
(containing Twirp server and client code) and helloworld.pb.go
(containing Protobuf message types).
4. Implement the Service
Create a new file named main.go
and write:
package main
import (
"context"
"net/http"
"github.com/twitchtv/twirp"
"path_to_your_generated_code/helloworld"
)
type helloWorldServer struct{}
func (h *helloWorldServer) Hello(ctx context.Context, req *helloworld.HelloReq) (*helloworld.HelloResp, error) {
if req.Name == "" {
return nil, twirp.InvalidArgumentError("name", "cannot be empty")
}
return &helloworld.HelloResp{Message: "Hello, " + req.Name + "!"}, nil
}
func main() {
server := &helloWorldServer{}
twirpHandler := helloworld.NewHelloWorldServer(server, nil)
http.ListenAndServe(":8080", twirpHandler)
}
5. Run the Server
$ go run main.go
Your server will start on port 8080
and can be interacted with using any HTTP client or the generated Twirp client.
Conclusion
Twirp offers a simple and efficient way to build RPC services with Go. Its reliance on Protocol Buffers ensures a consistent interface, while its simplicity makes it attractive for developers familiar with HTTP and JSON. Dive in, and see how Twirp can enhance your microservice architecture!