I believe ttrpc stands for teeny tiny rpc. Don't quote me on it. Created for low latency environments based off of the famous gRPC. I was introduced by ttrpc in a talk given by a containerD maintainer. Unexpected for you to know how containerD functions to get started with ttrpc.

This guide is aimed for developers who know gRPC and would like to get started with ttrpc. Knowledge of gRPC isn't required, however knowledge of golang and tcp is needed. I will felish out more of this tutorial for newbies to the rpc space soon.

Important to note. ttrpc does sacrifice security features to reduce latency. I would not recommend opening up a ttrpc service to the world or external facing clients. I think it’s perfect for air gapped environments or already trusted inter service communication like a cache that runs in/with your service.

Requirements

Make sure you have gRPC installed

$ go get -u google.golang.org/grpc

Also the protoc plugin for Go

$ go get -u github.com/golang/protobuf/protoc-gen-go

A thought out guide

Next you will need to install ttrpc plugin for protoc

$ go get github.com/containerd/ttrpc
$ cd $GOPATH/src/github.com/containerd/ttrpc/cmd/protoc-gen-gogottrpc/ 
$ go install
// verify the binary is $GOBIN
$ ls $GOBIN
// you should see 'protoc-gen-gogottrpc' in stdout

Next create a package in your Go workspace

$ mkdir $GOPATH/src/ttrpc-demo
$ cd $GOPATH/src/ttrpc-demo

We'll create a folder titled ‘pb’ to write our proto files in

$ mkdir pb
$ cd pb

Create a file titled hello.proto in the contents fo the file put

syntax = "proto3";

option go_package = "hello;hello";

service HelloService{
    rpc HelloWorld(HelloRequest) returns (HelloResponse);
}

message HelloRequest{
    string msg = 1;
}

message HelloResponse{
    string response = 1;
}

Now we need to generate our code using the ttrpc plugin we installed earlier. This will create a new directory titled hello with generated code for your service.

$ protoc -I ../pb/ --gogottrpc_out=plugins=ttrpc:../pb ../pb/*.proto

Lets write some server side code. Create a new directory titled server/ttrpc/ and a file titled server.go

$ mkdir -p $GOPATH/src/ttrpc-demo/server/ttrpc
$ touch $GOPATH/src/ttrpc-demo/server/ttrpc/server.go

In the server.go file, lets write our service calls

package main

import (
	"context"
	"errors"
	"fmt"
	"net"
	"os"
	"ttrpc-demo/pb/hello"

	"github.com/containerd/ttrpc"
)

const port = ":9000"

func main() {

	s, err := ttrpc.NewServer()
	defer s.Close()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	lis, err := net.Listen("tcp", port)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	hello.RegisterHelloServiceService(s, &helloService{})
	if err := s.Serve(context.Background(), lis); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
}

type helloService struct{}

func (s helloService) HelloWorld(ctx context.Context, r *hello.HelloRequest) (*hello.HelloResponse, error) {
	if r.Msg == "" {
		return nil, errors.New("ErrNoInputMsgGiven")
	}
	return &hello.HelloResponse{Response: "Hi How are you"}, nil
}

Now lets generate our client side code. Let's create a new directory and a client.go file

$ mkdir -p $GOPATH/src/ttrpc-demo/client/ttrpc/
$ touch $GOPATH/src/ttrpc-demo/client/ttrpc/client.go

Inside client.go – place contents below

package main

import (
	"context"
	"fmt"
	"net"
	"os"
	"ttrpc-demo/pb/hello"

	"github.com/containerd/ttrpc"
)

const port = ":9001"

func main() {
	conn, err := net.Dial("tcp", port)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to dial: %v \n", err)
		os.Exit(1)
	}
	client := hello.NewHelloServiceClient(ttrpc.NewClient(conn))
	serverResponse, err := client.HelloWorld(context.Background(), &hello.HelloRequest{
		Msg: "Hello Server",
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Fprintln(os.Stdout, serverResponse.Response)
}