Skip to content

Latest commit

 

History

History
204 lines (151 loc) · 5.68 KB

example.md

File metadata and controls

204 lines (151 loc) · 5.68 KB
id title sidebar_label
example
Usage Example: Haberdasher
Usage Example

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

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

By the end of this, we'll run a Haberdasher server and have a client that can send it requests.

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 Protobuf definition file, placed in rpc/haberdasher/service.proto:

syntax = "proto3";

package twirp.example.haberdasher;
option go_package = "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. They'll also show up in the generated Go code, so they'll show up in Godoc.

Generate code

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

$ protoc --proto_path=$GOPATH/src:. --twirp_out=. --go_out=. ./rpc/haberdasher/service.proto

The code is generated in the same directory as the .proto files.

You should see the generated files next to the service.proto file:

/rpc
  /haberdasher
    service.pb.go     # auto-generated by protoc-gen-go (for protobuf serialization)
    service.proto     # original protobuf definition
    service.twirp.go  # auto-generated by protoc-gen-twirp (servers, clients and interfaces)

If you open the generated .twirp.go file, you will 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" full of logic that we'll be serving.

For example, the implementation could go in internal/haberdasherserver/server.go:

package haberdasherserver

import (
    "context"
    "math/rand"

    "github.com/twitchtv/twirp"
    pb "github.com/twitchtv/twirpexample/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
}

This meets the Haberdasher interface because it implements the MakeHat method, so we're ready to serve this over Twirp!

Mount and run the server

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

func NewHaberdasherServer(svc Haberdasher, hooks *twirp.ServerHooks) TwirpServer

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

It's easy to serve a http.Handler straight away with the standard library. Just call http.ListenAndServe. For example, you might write the following in cmd/server/main.go:

package main

import (
    "net/http"

    "github.com/twitchtv/twirpexample/internal/haberdasherserver"
    "github.com/twitchtv/twirpexample/rpc/haberdasher"
)

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

  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.

The JSONClient is included as reference, to show that a Twirp server can handle JSON requests, but you should really use ProtobufClient (see Protobuf vs JSON).

To use the Haberdasher service from another Go project, just import the auto-generated client and then use it. You might do this, in cmd/client/main.go:

package main

import (
    "context"
    "net/http"
    "os"
    "fmt"
    "github.com/twitchtv/twirpexample/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!