Better gRPC with Connect: A Go Application Migration
Our digital cognitive behavioral therapy app Awarefy has been developing and operating its backend system using Go + gRPC / Protocol Buffers since April 2022. Due to the ongoing web app development and the need to switch to connect-go, we have migrated from grpc-go
to connect-go
.
What is Connect?
Connect is best understood as a Better gRPC.
Connect is a slim library for building browser- and gRPC-compatible HTTP APIs.
Connect is developed by Buf, an organization that is passionate about gRPC.
Buf has been providing tools for building and maintaining Protocol Buffers schema files and a schema registry even before Connect.
Their commitment to creating a more strict and comprehensible specification can be seen in their efforts.
Buf announced the concept of Connect in January 2022.
Buf's recent focus is on Connect, which is already at the center of development experiences using gRPC / Protocol Buffers.
What are the benefits of using Connect?
The benefits of using Connect include:
Supporting gRPC, Connect, and
grpc-web
with a single codebaseResolving issues with gRPC when using JavaScript in a browser as a client (
grpc-web
)gRPC compatibility allows client code incompatible with Connect to work.
Easier debugging compared to gRPC as it accepts traditional RESTful API / JSON (
application/json
) requests (although only for POST)
For more details, refer to the official documentation.
Support for Connect in various languages
Buf supports Go, Kotlin, Swift, Web(Client-side JavaScript), and Node.js.
These are for Connect support and other languages, Connect is not used; instead, gRPC is used for processing due to backward compatibility.
Awarefy's app is developed using Flutter, so Dart support is awaited. Advantageously, the Flutter / Dart side code does not need to be changed if it is non-Connect gRPC.
connect-go
connect-go
is a library for developing client/server applications with Connect support in the Go language.
Before Connect, grpc-go
was practically essential for developing gRPC web servers in the Go language.
grpc-go
required adherence to grpc-go
conventions, which had some differences from RESTful API development knowledge. Connect can be said to have fewer differences.
A demo app using connect-go is available.
This should give you a good sense of the overall implementation.
Development flow with Connect
The development flow with Connect is as follows:
Write Protocol Buffers definitions
Generate code with the Buf command
Implement the backend
This development flow is no different from that of gRPC.
An example of buf.gen.yaml
is as follows:
version: v1
managed:
enabled: true
plugins:
- name: go
out: gen
opt: paths=source_relative
- name: connect-go
out: gen
opt: paths=source_relative
Tips for migrating from grpc-go
to connect-go
Here are some tips for migrating from grpc-go
to connect-go
. Since the code is only shown in fragments, please compare it with the demo app mentioned earlier and proceed.
An official migration guide is also available.
HTTP Server
This might be the most significant difference.
s := grpc.NewServer()
The part that depended on grpc-go
is no longer needed, and the code is changed to use http.Server
.
mux := http.NewServeMux()
srv := &http.Server{
Addr: fmt.Sprintf(":%v", port),
Handler: h2c.NewHandler(
mux,
&http2.Server{},
),
}
in this sense, switching to Connect brings the development closer to standard web app development.
Request / Response
For request and response handling, simply wrap the request with connect.Request
and the response with connect.Response
. This can be done with a simple find and replace.
type healthCheckController struct{}
func NewHealthCheckServiceServer() svc.HealthCheckServiceHandler {
return &healthCheckController{}
}
func (h healthCheckController) Check(
context.Context,
*connect.Request[pb.HealthCheckRequest],
) (
*connect.Response[pb.HealthCheckResponse],
error,
) {
return connect.NewResponse(&pb.HealthCheckResponse{}), nil
}
Note: The code starts with svc.
and pb.
are imports from files automatically generated by the buf
command.
Error Codes As mentioned earlier, since Connect is gRPC-compatible, you can use the google.golang.org/grpc/status library for expressing errors in gRPC.
However, it is better to switch to Connect's error-related features, as they are almost mechanically interchangeable.
connect.NewError(connect.CodeUnauthenticated, errors.New("failed to get a token"))
Interceptor (Middleware)
Interceptor (Middleware) is another area with significant changes.
The example below is a logging interceptor.
func NewLoggingInterceptor() connect.UnaryInterceptorFunc {
interceptor := func(next connect.UnaryFunc) connect.UnaryFunc {
return connect.UnaryFunc(func(
ctx context.Context,
req connect.AnyRequest,
) (connect.AnyResponse, error) {
Logger.Info(
"Request",
zap.String("Procedure", req.Spec().Procedure),
zap.String("Protocol", req.Peer().Protocol),
zap.String("Addr", req.Peer().Addr),
)
return next(ctx, req)
})
}
return connect.UnaryInterceptorFunc(interceptor)
}
The code above next()
processes the request, and the code below processes the response.
Interceptors need to be registered and used.
mux := http.NewServeMux()
mux.Handle(cg.NewHealthCheckServiceHandler(
controller.NewHealthCheckServiceServer(),
connect.WithInterceptors(
interceptor.NewLoggingInterceptor(),
),
))
Wrapping Up
The development experience with Connect is truly amazing, so we highly recommend giving it a try. In the future, we plan to share the validation results of Connect-Web, so stay tuned.
Subscribe to my newsletter
Read articles from Takahiro Ikeuchi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by