Color Mode


    Language

Setting Up the Optimal gRPC Development Environment Using Connect-go and VSCode Dev Container

August 25, 2023

Hello there! This is Ueda from the Backend Team at MonstarLab. Recently, in a project I was involved in, the topic of using gRPC came up. So, I went through various trials and errors to quickly set up the optimal development environment. In this article, I'll introduce the method I used.

  • The code is based on the Connect-go tutorial.
  • We'll build a container development environment using Dev Container,
  • Introduce Hot Reload using tools like Air
  • Demonstrate the use of protoc-gen-validate for validation.

To focus on setting up the development environment, I'll exclude technical explanations or architecture design related to gRPC and Connect, as well as topics like CI/CD, from this article. You can find the repository for the sample code here.

Operating Environment

I have conducted operational tests in the following environment:

  • M1 MacBook Pro (Ventura 13.4.1)
  • Docker Desktop for Mac Version 4.20.1
  • VSCode Version 1.79.2
  • Dev Containers v0.295.0

Technology Stack

My technology stack includes:

  • VSCode Dev Containers
  • Golang
  • Air
  • Connect-go
  • protoc-gen-validate(PGV)

Setting Up the Environment

Personally, I believe there are three key criteria for creating the optimal development environment:

  1. Maintaining a certain level of code quality without relying solely on human attention.
  2. Compiling written code quickly to receive immediate feedback.
  3. Allowing anyone to start development promptly.

Taking these criteria into account for gRPC development environment, I have selected the following tools and libraries: VSCode Dev Container as the IDE, Golang as the programming language, Connect as the gRPC library, and libraries like Air for achieving Hot Reload. The tools chosen for this setup are considered industry standards and were also selected based on personal preferences after testing and evaluation.

Setting Up VSCode Dev Container

I added configurations based on the Go template created with the "Add Dev Container Configuration Files" command from the Dev Containers extension. To install Go configurations in the container, I set up the container build using a Dockerfile. The other main configuration items are primarily these three:

  • Introducing Features.
  • Configuring VSCode settings and introducing extensions.
  • Installing necessary tools for Go development in the Dockerfile.

While there are other things I might want to do, such as setting up git pre-commit configurations, I prioritized starting gRPC development and kept the setup minimal.

Introducing Features

Configuring Oh My Zsh

To establish my shell environment, I have adopted Oh My Zsh. Since people often have their own preferences when it comes to shells, it's best to choose something you like. Here are the steps to introduce Oh My Zsh to the Dev Container:

Installing Zsh and Oh My Zsh

By installing the common-utils feature, you can incorporate both Zsh and Oh My Zsh. Configure it within the "features" section in the devcontainer.json file like this:

{
  // // Features to add to the dev container. More info: https://containers.dev/features.
  "features": {
    "ghcr.io/devcontainers/features/common-utils:2": {
      "configureZshAsDefaultShell": true
    }
  }
}

Setting Zsh as the Default Shell

You can change the default shell to Zsh by adding the following script to the Dev Container's dockerfile. Reference: stackoverflow

RUN echo "if [ -t 1 ]; then" >> /root/.bashrc
RUN echo "exec zsh" >> /root/.bashrc
RUN echo "fi" >> /root/.bashrc

Configuring Oh My Zsh in .zshrc

Include the configuration for Oh My Zsh in the .zshrc file within the .devcontainer directory. You can use the zsh-template from Oh My Zsh for this purpose.

Copying .zshrc to the Container

Finally, add the following line to your Dockerfile to copy the .zshrc file to the home directory within the container:

ADD .zshrc $HOME

Configuring VSCode Settings and Introducing Extensions

Now let's dive into configuring VSCode. We'll start with the settings.json configuration, where I will use the official default settings. You can customize these settings to match your personal preferences. When it comes to introducing extensions, this is the part where personal preferences shine through the most. In my case, I went with the following setup. Since these are all well-known extensions, I'll skip detailed descriptions for each. Just remember to add the necessary configurations to your settings files as needed.

In your .devcontainer.json:

{
  "customizations": {
    "vscode": {
      "extensions": [
        "EditorConfig.EditorConfig",
        "streetsidesoftware.code-spell-checker",
        "wayou.vscode-todo-highlight",
        "ms-azuretools.vscode-docker",
        "shardulm94.trailing-spaces",
        "bungcip.better-toml"
      ]
    }
  }
}

Configuring Dockerfile for Golang Development

An essential part of Golang development is the Go extension from Extensions. Installing this extension is enough to get started with development.

In your .devcontainer.json:

{
  // Configure tool-specific properties.
  "customizations": {
    "vscode": {
      "extensions": ["golang.go"]
    }
  }
}

In the Dockerfile, you'll want to install dependencies required by the Go extension, such as gopls and dlv, along with any other necessary tools to complete your Golang development environment.

Dockerfile

RUN go install github.com/cweill/gotests/gotests@latest
RUN go install github.com/josharian/impl@latest
RUN go install github.com/go-delve/delve/cmd/dlv@latest
RUN go install honnef.co/go/tools/cmd/staticcheck@latest
RUN go install golang.org/x/tools/gopls@latest

Setting Up Connect-go Environment

With the Golang environment set up as described in the previous sections, we can now proceed to set up Connect-go. Essentially, by following the tutorial, you can easily get Connect-go up and running.

Introducing Extensions

For the Dockerfile, add the following commands based on the tutorial's instructions:

Dockerfile

RUN go install github.com/bufbuild/buf/cmd/buf@latest
RUN go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
RUN go install github.com/bufbuild/connect-go/cmd/protoc-gen-connect-go@latest

Writing Sample Code

Following the tutorial, create go.mod, buf.yaml, and buf.gen.yaml files, write your main code, and execute main.go to complete the Connect-go setup.

Introducing Hot Reload

To enhance the development environment, we will introduce Hot Reload. Hot Reload requires two steps:

  1. Automatic code generation.
  2. Hot Reload for Golang code.

Automatic Code Generation

For automatic code generation, utilize the buf generate command upon saving proto files. You can achieve this using the "Run on Save" extension. Add "emeraldwalk.RunOnSave" to your devcontainer.json and configure it in .vscode/settings.json:

{
  // Run on Save
  // proto-gen
  "emeraldwalk.runonsave": {
    "commands": [
      {
        "match": "\\.proto$",
        "cmd": "rm -rf gen | buf generate"
      }
    ]
  }
}

This cmd is used to remove all files within the gen directory. This precaution is taken because the buf generate command might not remove inadvertently generated files.

As the scale of your project increases, I think I will need to reconsider it and potentially implement a more refined approach.

Introducing Golang's Hot Reload with Air

For Golang's Hot Reload, we've introduced the use of Air. There are various ways to install it, but by adding RUN go install github.com/cosmtrek/air@latest to your Dockerfile, the setup is complete. The configuration file needs to be set up as well. Running the air init command within the container will generate the .air.toml file, which you can modify based on the .air_sample.toml, adapting it for your project's structure.

In this setup, we've set delay=500. This delay is important because without it, when RunOnSave operates, it might not be able to read files under the gen directory.

Once the Hot Reload configuration is in place, make sure Air automatically starts after the container is up. Use the postStartCommand in your devcontainer.json:

{
  "postStartCommand": "air -c .air.toml"
}

With these steps, your Hot Reload configuration is now fully set up.

Adding Request Validation Feature

Now, let's move on to adding the request validation feature, something that would be useful in practical usage. We'll use protoc-gen-validate (PGV) for Connect's validation.

Add RUN go install github.com/envoyproxy/protoc-gen-validate@latest to your Dockerfile. Then, as documented, add the necessary configurations to buf.gen.yaml and buf.yaml. Be careful not to forget adding opt according to your environment's needs.

buf.gen.yaml

plugins:
  - plugin: buf.build/bufbuild/validate-go
    out: gen
    opt: paths=source_relative

buf.yaml

deps:
  - buf.build/envoyproxy/protoc-gen-validate

After this, run the buf mod update command to generate the buf.gen.yaml file. Be aware that automatically running bef generate might prevent you from receiving error messages. If things don't work smoothly, manually running the commands is recommended.

Now, let's implement validation. Import validate in your greet.proto file and modify the validation rules for GreetRequest, like so:

greet/v1/greet.proto

syntax = "proto3";

package greet.v1;

import "validate/validate.proto";

option go_package= "connect-sample/gen/greet/v1;greetv1";

message GreetRequest {
  string name = 1 [(validate.rules).string = {min_len: 5, max_len: 10}];
}

message GreetResponse {
  string greeting = 1;
}

service GreetService {
  rpc Greet(GreetRequest) returns (GreetResponse) {}
}

Running the buf generate command will generate the gen.pb.validate.go file. Upon inspecting its contents, you'll notice that the GreetRequest type struct includes methods like Validate and ValidateAll. You can use these methods. For instance, you can add validation like this:

func (s *GreetServer) Greet(
 ctx context.Context,
 req *connect.Request[greetv1.GreetRequest],
) (*connect.Response[greetv1.GreetResponse], error) {
	if err := req.Msg.Validate(); err != nil {
	  return nil, err
	}
	log.Println("Request headers: ", req.Header())
	res := connect.NewResponse(&greetv1.GreetResponse{
		Greeting: fmt.Sprintf("Hello, %s!", req.Msg.Name),
	})
	res.Header().Set("Greet-Version", "v1")
	return res, nil
}

With this, the validation setup is complete.

Testing the Setup

You can test the setup using the following three commands:

git clone https://github.com/Yukio0315/connect-sample

devcontainer open # if devcontainer CLI installed

root ➜ /workspaces/connect-sample (main) $ go run ./cmd/client/main.go
YYYY/MM/DD hh:mm:ss unknown: invalid GreetRequest.Name: value length must be between 5 and 10 runes, inclusive

With these steps, you should see that the server runs without issues and the validation is working as expected.

Conclusion

This article has explained how to set up the optimal gRPC development environment using Connect-go within a Dev Container, all in a speedy manner. By incorporating features like linting and hot reload, I have been able to create an environment that's fairly ideal for development. While there might still be areas that need improvement, I have managed to establish the essential environment needed to kickstart development. I hope this article proves helpful to those involved in gRPC development and beyond.

Article Photo by Simon Berger

grpcgolangairconnect-goprotoc-gen-validateprotobufvscodedev containerbackend

Author

Yukio Ueda

Yukio Ueda

Backend Engineer

Golang & TypeScript enthusiast

You may also like

November 7, 2024

Introducing Shorebird, code push service for Flutter apps

Update Flutter apps without store review What is Shorebird? Shorebird is a service that allows Flutter apps to be updated directly at runtime. Removing the need to build and submit a new app version to Apple Store Connect or Play Console for review for ev...

Christofer Henriksson

Christofer Henriksson

Flutter

May 27, 2024

Introducing UCL Max AltPlay, a turn-by-turn real-time Football simulation

At this year's MonstarHacks, our goal was to elevate the sports experience to the next level with cutting-edge AI and machine learning technologies. With that in mind, we designed a unique solution for football fans that will open up new dimensions for wa...

Rayhan NabiRokon UddinArman Morshed

Rayhan Nabi, Rokon Uddin, Arman Morshed

MonstarHacks

ServicesCasesAbout Us
CareersThought LeadershipContact
© 2022 Monstarlab
Information Security PolicyPrivacy PolicyTerms of Service