This is a high level guide to writing microservices with go-micro.
If you want to learn more about microservices check out the introductory blog post here and if you want to know more about Micro, the microservice toolkit, look here.
Let’s get to it.
Go Micro is a pluggable RPC based library which provides the fundamental building blocks for writing microservices in Go. The Micro philosophy is “batteries included” with a pluggable architecture. Out of the box, it implements service discovery using consul, communication via http and encoding using proto-rpc or json-rpc.
That’s a bit of a mouthful so let’s break it down.
Go Micro is:
Go Micro provides interfaces for:
A more detailed breakdown can be found here.
Go Micro started more than a year ago, initially serving a personal need. It was clear very soon after that it would be valuable to a broader audience also looking to write microservices. It’s based on experiences at various technology companies that operate microservice platforms at scale like Google and Hailo.
As mentioned before Go Micro is a pluggable architecture that focuses on providing Go based interfaces which when used together providing the building blocks for writing microservices. These interfaces can be satisfied by concrete implementations. For example the Registry interface for service discovery has a default implementation for Consul but can be swapped out for etcd, zookeeper, or anything else that is able to satify the interface.
The pluggable architecture means zero code rewriting if you want to swap out the underlying technologies.
Let’s get into writing a service.
If you want to get straight into reading the code, look at examples/service.
The top level Service interface is the main component for building a service. It wraps all the underlying packages of Go Micro into a single convenient interface.
type Service interface {
Init(...Option)
Options() Options
Client() client.Client
Server() server.Server
Run() error
String() string
}
A service is created like so using micro.NewService
.
import "github.com/micro/go-micro"
service := micro.NewService()
Options can be passed in during creation.
service := micro.NewService(
micro.Name("greeter"),
micro.Version("latest"),
)
All the available options can be found here.
Go Micro also provides a way to set command line flags using micro.Flags
.
import (
"github.com/micro/cli"
"github.com/micro/go-micro"
)
service := micro.NewService(
micro.Flags(
cli.StringFlag{
Name: "environment",
Usage: "The environment",
},
)
)
To parse flags use service.Init
. Additionally access flags use the micro.Action
option.
service.Init(
micro.Action(func(c *cli.Context) {
env := c.StringFlag("environment")
if len(env) > 0 {
fmt.Println("Environment set to", env)
}
}),
)
Go Micro provides predefined flags which are set and parsed if service.Init
is called. See all the flags
here.
We use protobuf files to define the service API interface. This is a very convenient way to strictly define the API and provide concrete types for both the server and a client.
Here’s an example definition.
greeter.proto
syntax = "proto3";
service Greeter {
rpc Hello(HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string greeting = 2;
}
Here we’re defining a service handler called Greeter with the method Hello which takes the parameter HelloRequest type and returns HelloResponse.
We use protoc and protoc-gen-go to generate the concrete go implementation for this definition.
Go-micro uses code generation to provide client stub methods to reduce boiler plate code much like gRPC. It’s done via a protobuf plugin which requires a fork of golang/protobuf that can be found here github.com/micro/protobuf.
go get github.com/micro/protobuf/{proto,protoc-gen-go}
protoc --go_out=plugins=micro:. greeter.proto
The types generated can now be imported and used within a handler for a server or the client when making a request.
Here’s part of the generated code.
type HelloRequest struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
type HelloResponse struct {
Greeting string `protobuf:"bytes,2,opt,name=greeting" json:"greeting,omitempty"`
}
// Client API for Greeter service
type GreeterClient interface {
Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error)
}
type greeterClient struct {
c client.Client
serviceName string
}
func NewGreeterClient(serviceName string, c client.Client) GreeterClient {
if c == nil {
c = client.NewClient()
}
if len(serviceName) == 0 {
serviceName = "greeter"
}
return &greeterClient{
c: c,
serviceName: serviceName,
}
}
func (c *greeterClient) Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) {
req := c.c.NewRequest(c.serviceName, "Greeter.Hello", in)
out := new(HelloResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Greeter service
type GreeterHandler interface {
Hello(context.Context, *HelloRequest, *HelloResponse) error
}
func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler) {
s.Handle(s.NewHandler(&Greeter{hdlr}))
}
The server requires handlers to be registered to serve requests. A handler is an public type with public methods
which conform to the signature func(ctx context.Context, req interface{}, rsp interface{}) error
.
As you can see above, a handler signature for the Greeter interface looks like so.
type GreeterHandler interface {
Hello(context.Context, *HelloRequest, *HelloResponse) error
}
Here’s an implementation of the Greeter handler.
import proto "github.com/micro/examples/service/proto"
type Greeter struct{}
func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
rsp.Greeting = "Hello " + req.Name
return nil
}
The handler is registered with your service much like a http.Handler.
service := micro.NewService(
micro.Name("greeter"),
)
proto.RegisterGreeterHandler(service.Server(), new(Greeter))
You can also create a bidirectional streaming handler but we’ll leave that for another day.
The service can be run by calling server.Run
. This causes the service to bind to the address in the config
(which defaults to the first RFC1918 interface found and a random port) and listen for requests.
This will additionally Register the service with the registry on start and Deregister when issued a kill signal.
if err := service.Run(); err != nil {
log.Fatal(err)
}
greeter.go
package main
import (
"log"
"github.com/micro/go-micro"
proto "github.com/micro/examples/service/proto"
"golang.org/x/net/context"
)
type Greeter struct{}
func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
rsp.Greeting = "Hello " + req.Name
return nil
}
func main() {
service := micro.NewService(
micro.Name("greeter"),
micro.Version("latest"),
)
service.Init()
proto.RegisterGreeterHandler(service.Server(), new(Greeter))
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
Note. The service discovery mechanism will need to be running so the service can register to be discovered by clients and other services. A quick getting started for that is here.
The client package is used to query services. When you create a Service, a Client is included which matches the initialized packages used by the server.
Querying the above service is as simple as the following.
// create the greeter client using the service name and client
greeter := proto.NewGreeterClient("greeter", service.Client())
// request the Hello method on the Greeter handler
rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{
Name: "John",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(rsp.Greeter)
proto.NewGreeterClient
takes the service name and the client used for making requests.
The full example is can be found at go-micro/examples/service.
Hopefully this blog post has been a helpful high level guide into writing microservices with Go Micro. You can find many more example services in the repo github.com/micro to help you gain a further understanding of more real world solutions.
If you want to learn more about the services we offer or microservices, checkout the website micro.mu or the github repo.
Follow us on Twitter at @MicroHQ or join the Slack community here.