Skip to content

Latest commit

 

History

History
206 lines (149 loc) · 5.47 KB

example.md

File metadata and controls

206 lines (149 loc) · 5.47 KB
id title sidebar_label
example
Usage Example: Haberdasher
Usage Example

Let's make the canonical Twirp service: a Haberdasher.

The Haberdasher service makes hats. It has only one RPC method, MakeHat, which makes a new hat of a particular size.

Make sure to Install Protobuf and Twirp before starting.

By the end of this, we'll run a Haberdasher service with a strongly typed client.

There are 5 steps here:

  1. Write a Protobuf service definition
  2. Generate code
  3. Implement the server
  4. Mount and run the server
  5. Use the client

Write a Protobuf Service Definition

Start with the proto definition file, placed in rpc/haberdasher/service.proto:

syntax = "proto3";

package twirp.example.haberdasher;
option go_package = "github.com/example/rpc/haberdasher";

// Haberdasher service makes hats for clients.
service Haberdasher {
  // MakeHat produces a hat of mysterious, randomly-selected color!
  rpc MakeHat(Size) returns (Hat);
}

// Size of a Hat, in inches.
message Size {
  int32 inches = 1; // must be > 0
}

// A Hat is a piece of headwear made by a Haberdasher.
message Hat {
  int32 inches = 1;
  string color = 2; // anything but "invisible"
  string name = 3; // i.e. "bowler"
}

It's a good idea to add comments on your Protobuf file. These files can work as the primary documentation of your API. The comments also show up in the generated Go types.

Generate code

To generate code run the protoc compiler pointed at your service's .proto files:

$ protoc --twirp_out=. --go_out=paths=. rpc/haberdasher/service.proto

See Generator Command Line Arguments for details about running the generator.

The code should be generated in the same directory as the .proto files.

/rpc
  /haberdasher
    service.proto
    service.pb.go     # generated by protoc-gen-go
    service.twirp.go  # generated by protoc-gen-twirp

If you open the generated .twirp.go file, you should see a Go interface like this:

// A Haberdasher makes hats for clients.
type Haberdasher interface {
    // MakeHat produces a hat of mysterious, randomly-selected color!
    MakeHat(context.Context, *Size) (*Hat, error)
}

along with code to instantiate clients and servers.

Implement the Server

Now, our job is to write code that fulfills the Haberdasher interface. This will be the "backend" logic to handle the requests.

The implementation could go in internal/haberdasherserver/server.go:

package haberdasherserver

import (
    "context"
    "math/rand"

    "github.com/twitchtv/twirp"
    pb "github.com/example/rpc/haberdasher"
)

// Server implements the Haberdasher service
type Server struct {}

func (s *Server) MakeHat(ctx context.Context, size *pb.Size) (hat *pb.Hat, err error) {
    if size.Inches <= 0 {
        return nil, twirp.InvalidArgumentError("inches", "I can't make a hat that small!")
    }
    return &pb.Hat{
        Inches:  size.Inches,
        Color: []string{"white", "black", "brown", "red", "blue"}[rand.Intn(4)],
        Name:  []string{"bowler", "baseball cap", "top hat", "derby"}[rand.Intn(3)],
    }, nil
}

Mount and run the server

To serve our Haberdasher over HTTP, use the generated server constructor New{{Service}}Server. For Haberdasher, it is:

func NewHaberdasherServer(svc Haberdasher, opts ...interface{}) TwirpServer

This constructor wraps your interface implementation as an TwirpServer, which is a http.Handler with a few extra bells and whistles.

The http.Handler can be mounted like any other HTTP handler. For example, using the standard library http.ListenAndServe method.

In cmd/server/main.go:

package main

import (
    "net/http"

    "github.com/example/internal/haberdasherserver"
    "github.com/example/rpc/haberdasher"
)

func main() {
  server := &haberdasherserver.Server{} // implements Haberdasher interface
  twirpHandler := haberdasher.NewHaberdasherServer(server)

  http.ListenAndServe(":8080", twirpHandler)
}

If you go run ./cmd/server/main.go, you'll be running your server at localhost:8080. All that's left is to create a client!

Use the Client

Client stubs are automatically generated, hooray!

For each service, there are 2 client constructors:

  • New{{Service}}ProtobufClient for Protobuf requests.
  • New{{Service}}JSONClient for JSON requests.

You should use the ProtobufClient. See Protobuf vs JSON for comparison.

Clients in other languages can also be generated by using the respective protoc plugins defined by their languages, for example --twirp_ruby_out.

To use the Haberdasher service from another Go project, import the auto-generated client.

For example, in cmd/client/main.go:

package main

import (
    "context"
    "net/http"
    "os"
    "fmt"
    "github.com/example/rpc/haberdasher"
)

func main() {
    client := haberdasher.NewHaberdasherProtobufClient("http://localhost:8080", &http.Client{})

    hat, err := client.MakeHat(context.Background(), &haberdasher.Size{Inches: 12})
    if err != nil {
        fmt.Printf("oh no: %v", err)
        os.Exit(1)
    }
    fmt.Printf("I have a nice new hat: %+v", hat)
}

If you have the server running in another terminal, try running this client with go run ./cmd/client/main.go. Enjoy the new hat!