Making requests

With Gohandlers provided request builders and response parsers making requests between Go services should feel like RPC. But it is not. You are actually using the good old http package provided DefaultClient underneath and your choice of body encoding, like the json or x-www-form-urlencoded.

type Desk struct {
  pets *pets.Client
}

func (d *Desk) NewPet(w http.ResponseWriter, r *http.Request) {
  // ...

  bq, err := d.pets.Create(&handlers.CreateRequest{
    Name: "Cookie",
    Tag:  "Fluffy"
  })
  // ...
  fmt.Println(bq.ID)

  // ...
}

Host pool

Client constructor expects a Pool value. Pool is an interface of types implement Host method. A Pool value is expected to return the next available host’s address at each method call. By providing your implementation of the Pool to the constructor you are abstracting the load balancing logic from rest of your service codebase will make requests. One simple implementation of a Pool type might be like the below. It either returns an error in case of there is no available host, or returns a random one at each call, impersonating the round robin method.

type MyPool []string

func (p MyPool) Host() (string, error) {
  if len(p) == 0 {
    return "", fmt.Errorf("no hosts available")
  }
  return p[rand.IntN(len(p))], nil
}

Initialization

Start with importing the client package. Construct your pool. Then pass the client to your handler’s or app’s struct as a dependency. After that, your handlers must be able to perform service-to-service requests by calling the methods on the client.

package main

import (
  pets "petstore/cmd/pets/client"
  "petstore/cmd/desk/handlers"
)

func main() {
  pool := MyPool([]string{"127.0.0.1:8081"})
  pets := pets.NewClient(pool)
  _ := handlers.NewDesk(pets)
  // ...
}

For the unit testing

Instead of using the concrete Client implementation, use the Interface type to declare dependency type to your consumer service handler. Then you can construct and provide the actual Client in main and the Mock in the unit tests. This will enable you to keep testing methods in isolation.

import pets "petstore/cmd/pets/client"

type Desk struct {
  pets pets.Interface
}
import pets "petstore/cmd/pets/client"

func TestDesk_NewPet(t *testing.T) {
  d := Desk{
    Pets: &pets.Mock{}
  }
  // ...
}