diff --git a/README.md b/README.md index 22f68aec..3dbce10a 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ By default go-micro only provides a single implementation of each interface. Plu ## Prerequisites -Consul is the default discovery mechanism provided in go-micro. Discovery is however pluggable. +Consul is the default discovery mechanism provided in go-micro. Discovery is however pluggable so you can used etcd, kubernetes, zookeeper, etc. ### Install Consul [https://www.consul.io/intro/getting-started/install.html](https://www.consul.io/intro/getting-started/install.html) @@ -41,104 +41,141 @@ $ consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul ### Run Service ``` -$ go run examples/server/main.go --logtostderr -I1108 11:08:19.926071 11358 server.go:96] Starting server go.micro.srv.example id go.micro.srv.example-04de4cf0-8609-11e5-bf3a-68a86d0d36b6 -I1108 11:08:19.926407 11358 rpc_server.go:233] Listening on [::]:54080 -I1108 11:08:19.926500 11358 http_broker.go:80] Broker Listening on [::]:54081 -I1108 11:08:19.926632 11358 rpc_server.go:158] Registering node: go.micro.srv.example-04de4cf0-8609-11e5-bf3a-68a86d0d36b6 +$ go run examples/service/main.go --logtostderr +I0102 00:22:26.413467 12018 rpc_server.go:297] Listening on [::]:62492 +I0102 00:22:26.413803 12018 http_broker.go:115] Broker Listening on [::]:62493 +I0102 00:22:26.414009 12018 rpc_server.go:212] Registering node: greeter-e6b2fc6f-b0e6-11e5-a42f-68a86d0d36b6 ``` ### Test Service ``` -$ go run examples/client/main.go -go.micro.srv.example-59b6e0ab-0300-11e5-b696-68a86d0d36b6: Hello John +$ go run examples/service/main.go --client +Hello John ``` ## Writing a service ### Create request/response proto -`go-micro/examples/server/proto/example/example.proto`: +`go-micro/examples/service/proto/greeter.proto`: ``` syntax = "proto3"; -message Request { - string name = 1; +service Greeter { + rpc Hello(HelloRequest) returns (HelloResponse) {} } -message Response { - string msg = 1; +message HelloRequest { + string name = 1; +} + +message HelloResponse { + string greeting = 2; } ``` -Compile proto `protoc -I$GOPATH/src --go_out=$GOPATH/src $GOPATH/src/github.com/micro/go-micro/examples/server/proto/example/example.proto` +### Install protobuf for code generation -### Create request handler -`go-micro/examples/server/handler/example.go`: +We use a protobuf plugin for code generation. This is completely optional. Look at [examples/server](https://github.com/micro/go-micro/blob/master/examples/server/main.go) +and [examples/client](https://github.com/micro/go-micro/blob/master/examples/client/main.go) for examples without code generation. -```go -package handler - -import ( - log "github.com/golang/glog" - c "github.com/micro/go-micro/context" - example "github.com/micro/go-micro/examples/server/proto/example" - "github.com/micro/go-micro/server" - - "golang.org/x/net/context" -) - -type Example struct{} - -func (e *Example) Call(ctx context.Context, req *example.Request, rsp *example.Response) error { - md, _ := c.GetMetadata(ctx) - log.Infof("Received Example.Call request with metadata: %v", md) - rsp.Msg = server.Options().Id + ": Hello " + req.Name - return nil -} +```shell +go get github.com/micro/protobuf ``` -### Init server -`go-micro/examples/server/main.go`: +Compile proto `protoc -I$GOPATH/src --go_out=plugins=micro:$GOPATH/src $GOPATH/src/github.com/micro/go-micro/examples/service/proto/greeter.proto` + +### Define the service +`go-micro/examples/service/main.go`: ```go package main import ( - log "github.com/golang/glog" - "github.com/micro/go-micro/cmd" - "github.com/micro/go-micro/examples/server/handler" - "github.com/micro/go-micro/server" + "fmt" + + micro "github.com/micro/go-micro" + proto "github.com/micro/go-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() { - // optionally setup command line usage - cmd.Init() - - // Initialise Server - server.Init( - server.Name("go.micro.srv.example"), + // Create a new service. Optionally include some options here. + service := micro.NewService( + micro.Name("greeter"), + micro.Version("latest"), + micro.Metadata(map[string]string{ + "type": "helloworld", + }), ) - // Register Handlers - server.Handle( - server.NewHandler( - new(handler.Example), - ), - ) + // Init will parse the command line flags. Any flags set will + // override the above settings. Options defined here will + // override anything set on the command line. + service.Init() - // Run server - if err := server.Run(); err != nil { - log.Fatal(err) + // Register handler + proto.RegisterGreeterHandler(service.Server(), new(Greeter)) + + // Run the server + if err := service.Run(); err != nil { + fmt.Println(err) } } ``` ### Run service ``` -$ go run examples/server/main.go --logtostderr -I1108 11:08:19.926071 11358 server.go:96] Starting server go.micro.srv.example id go.micro.srv.example-04de4cf0-8609-11e5-bf3a-68a86d0d36b6 -I1108 11:08:19.926407 11358 rpc_server.go:233] Listening on [::]:54080 -I1108 11:08:19.926500 11358 http_broker.go:80] Broker Listening on [::]:54081 -I1108 11:08:19.926632 11358 rpc_server.go:158] Registering node: go.micro.srv.example-04de4cf0-8609-11e5-bf3a-68a86d0d36b6 +go run examples/service/main.go --logtostderr +I0102 00:22:26.413467 12018 rpc_server.go:297] Listening on [::]:62492 +I0102 00:22:26.413803 12018 http_broker.go:115] Broker Listening on [::]:62493 +I0102 00:22:26.414009 12018 rpc_server.go:212] Registering node: greeter-e6b2fc6f-b0e6-11e5-a42f-68a86d0d36b6 +``` + +### Define a client + +`client.go` + +```go +package main + +import ( + "fmt" + + micro "github.com/micro/go-micro" + proto "github.com/micro/go-micro/examples/service/proto" + "golang.org/x/net/context" +) + + +func main() { + // Create a new service. Optionally include some options here. + service := micro.NewService(micro.Name("greeter.client")) + + // Create new greeter client + greeter := proto.NewGreeterClient("greeter", service.Client()) + + // Call the greeter + rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "John"}) + if err != nil { + fmt.Println(err) + } + + // Print response + fmt.Println(rsp.Greeting) +} +``` + +### Run the client + +```shell +go run client.go +Hello John ``` diff --git a/client/client.go b/client/client.go index 906e399f..7c1c940e 100644 --- a/client/client.go +++ b/client/client.go @@ -26,6 +26,8 @@ import ( ) type Client interface { + Init(...Option) error + Options() Options NewPublication(topic string, msg interface{}) Publication NewRequest(service, method string, req interface{}, reqOpts ...RequestOption) Request NewProtoRequest(service, method string, req interface{}, reqOpts ...RequestOption) Request diff --git a/client/rpc_client.go b/client/rpc_client.go index 8fcd573f..77622c35 100644 --- a/client/rpc_client.go +++ b/client/rpc_client.go @@ -149,6 +149,17 @@ func (r *rpcClient) stream(ctx context.Context, address string, req Request) (St return stream, err } +func (r *rpcClient) Init(opts ...Option) error { + for _, o := range opts { + o(&r.opts) + } + return nil +} + +func (r *rpcClient) Options() Options { + return r.opts +} + func (r *rpcClient) CallRemote(ctx context.Context, address string, request Request, response interface{}, opts ...CallOption) error { return r.call(ctx, address, request, response) } diff --git a/cmd/cmd.go b/cmd/cmd.go index 776eb6a3..824f13fb 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -7,11 +7,9 @@ import ( "math/rand" "os" "strings" - "text/tabwriter" - "text/template" "time" - "github.com/codegangsta/cli" + "github.com/micro/cli" "github.com/micro/go-micro/broker" "github.com/micro/go-micro/client" "github.com/micro/go-micro/registry" @@ -20,10 +18,27 @@ import ( "github.com/micro/go-micro/transport" ) -var ( - Actions = []func(*cli.Context){} +type Cmd interface { + // The cli app within this cmd + App() *cli.App + // Adds options, parses flags and initialise + // exits on error + Init(opts ...Option) error + // Options set within this command + Options() Options +} - Flags = []cli.Flag{ +type cmd struct { + opts Options + app *cli.App +} + +type Option func(o *Options) + +var ( + DefaultCmd = newCmd() + + DefaultFlags = []cli.Flag{ cli.StringFlag{ Name: "server_name", EnvVar: "MICRO_SERVER_NAME", @@ -42,7 +57,6 @@ var ( cli.StringFlag{ Name: "server_address", EnvVar: "MICRO_SERVER_ADDRESS", - Value: ":0", Usage: "Bind address for the server. 127.0.0.1:8080", }, cli.StringFlag{ @@ -59,7 +73,6 @@ var ( cli.StringFlag{ Name: "broker", EnvVar: "MICRO_BROKER", - Value: "http", Usage: "Broker for pub/sub. http, nats, rabbitmq", }, cli.StringFlag{ @@ -70,7 +83,6 @@ var ( cli.StringFlag{ Name: "registry", EnvVar: "MICRO_REGISTRY", - Value: "consul", Usage: "Registry for discovery. memory, consul, etcd, kubernetes", }, cli.StringFlag{ @@ -81,13 +93,11 @@ var ( cli.StringFlag{ Name: "selector", EnvVar: "MICRO_SELECTOR", - Value: "selector", Usage: "Selector used to pick nodes for querying. random, roundrobin, blacklist", }, cli.StringFlag{ Name: "transport", EnvVar: "MICRO_TRANSPORT", - Value: "http", Usage: "Transport mechanism used; http, rabbitmq, nats", }, cli.StringFlag{ @@ -127,58 +137,151 @@ var ( }, } - Brokers = map[string]func([]string, ...broker.Option) broker.Broker{ + DefaultBrokers = map[string]func([]string, ...broker.Option) broker.Broker{ "http": broker.NewBroker, } - Registries = map[string]func([]string, ...registry.Option) registry.Registry{ + DefaultRegistries = map[string]func([]string, ...registry.Option) registry.Registry{ "consul": registry.NewRegistry, } - Selectors = map[string]func(...selector.Option) selector.Selector{ + DefaultSelectors = map[string]func(...selector.Option) selector.Selector{ "random": selector.NewSelector, } - Transports = map[string]func([]string, ...transport.Option) transport.Transport{ + DefaultTransports = map[string]func([]string, ...transport.Option) transport.Transport{ "http": transport.NewTransport, } ) func init() { rand.Seed(time.Now().Unix()) + help := cli.HelpPrinter + cli.HelpPrinter = func(writer io.Writer, templ string, data interface{}) { + help(writer, templ, data) + os.Exit(0) + } } -func Setup(c *cli.Context) error { +func newCmd(opts ...Option) Cmd { + options := Options{ + Broker: &broker.DefaultBroker, + Client: &client.DefaultClient, + Registry: ®istry.DefaultRegistry, + Server: &server.DefaultServer, + Selector: &selector.DefaultSelector, + Transport: &transport.DefaultTransport, + + Brokers: DefaultBrokers, + Registries: DefaultRegistries, + Selectors: DefaultSelectors, + Transports: DefaultTransports, + } + + for _, o := range opts { + o(&options) + } + + if len(options.Description) == 0 { + options.Description = "a go-micro service" + } + + cmd := new(cmd) + cmd.opts = options + cmd.app = cli.NewApp() + cmd.app.Name = cmd.opts.Name + cmd.app.Version = cmd.opts.Version + cmd.app.Usage = cmd.opts.Description + cmd.app.Before = cmd.Before + cmd.app.Flags = DefaultFlags + cmd.app.Action = func(c *cli.Context) {} + + if len(options.Version) == 0 { + cmd.app.HideVersion = true + } + + return cmd +} + +func (c *cmd) App() *cli.App { + return c.app +} + +func (c *cmd) Options() Options { + return c.opts +} + +func (c *cmd) Before(ctx *cli.Context) error { + // Due to logger issues with glog, we need to do this os.Args = os.Args[:1] - - flag.Set("logtostderr", fmt.Sprintf("%v", c.Bool("logtostderr"))) - flag.Set("alsologtostderr", fmt.Sprintf("%v", c.Bool("alsologtostderr"))) - flag.Set("stderrthreshold", c.String("stderrthreshold")) - flag.Set("log_backtrace_at", c.String("log_backtrace_at")) - flag.Set("log_dir", c.String("log_dir")) - flag.Set("vmodule", c.String("vmodule")) - flag.Set("v", c.String("v")) - + flag.Set("logtostderr", fmt.Sprintf("%v", ctx.Bool("logtostderr"))) + flag.Set("alsologtostderr", fmt.Sprintf("%v", ctx.Bool("alsologtostderr"))) + flag.Set("stderrthreshold", ctx.String("stderrthreshold")) + flag.Set("log_backtrace_at", ctx.String("log_backtrace_at")) + flag.Set("log_dir", ctx.String("log_dir")) + flag.Set("vmodule", ctx.String("vmodule")) + flag.Set("v", ctx.String("v")) flag.Parse() - if b, ok := Brokers[c.String("broker")]; ok { - broker.DefaultBroker = b(strings.Split(c.String("broker_address"), ",")) + // If flags are set then use them otherwise do nothing + var serverOpts []server.Option + var clientOpts []client.Option + + // Set the broker + if len(ctx.String("broker")) > 0 { + if b, ok := c.opts.Brokers[ctx.String("broker")]; ok { + n := b(strings.Split(ctx.String("broker_address"), ",")) + c.opts.Broker = &n + } else { + return fmt.Errorf("Broker %s not found", ctx.String("broker")) + } + + serverOpts = append(serverOpts, server.Broker(*c.opts.Broker)) + clientOpts = append(clientOpts, client.Broker(*c.opts.Broker)) } - if r, ok := Registries[c.String("registry")]; ok { - registry.DefaultRegistry = r(strings.Split(c.String("registry_address"), ",")) + // Set the registry + if len(ctx.String("registry")) > 0 { + if r, ok := c.opts.Registries[ctx.String("registry")]; ok { + n := r(strings.Split(ctx.String("registry_address"), ",")) + c.opts.Registry = &n + } else { + return fmt.Errorf("Registry %s not found", ctx.String("registry")) + } + + serverOpts = append(serverOpts, server.Registry(*c.opts.Registry)) + clientOpts = append(clientOpts, client.Registry(*c.opts.Registry)) } - if s, ok := Selectors[c.String("selector")]; ok { - selector.DefaultSelector = s(selector.Registry(registry.DefaultRegistry)) + // Set the selector + if len(ctx.String("selector")) > 0 { + if s, ok := c.opts.Selectors[ctx.String("selector")]; ok { + n := s(selector.Registry(*c.opts.Registry)) + c.opts.Selector = &n + } else { + return fmt.Errorf("Selector %s not found", ctx.String("selector")) + } + + // No server option here. Should there be? + clientOpts = append(clientOpts, client.Selector(*c.opts.Selector)) } - if t, ok := Transports[c.String("transport")]; ok { - transport.DefaultTransport = t(strings.Split(c.String("transport_address"), ",")) + // Set the transport + if len(ctx.String("transport")) > 0 { + if t, ok := c.opts.Transports[ctx.String("transport")]; ok { + n := t(strings.Split(ctx.String("transport_address"), ",")) + c.opts.Transport = &n + } else { + return fmt.Errorf("Transport %s not found", ctx.String("transport")) + } + + serverOpts = append(serverOpts, server.Transport(*c.opts.Transport)) + clientOpts = append(clientOpts, client.Transport(*c.opts.Transport)) } + // Parse the server options metadata := make(map[string]string) - for _, d := range c.StringSlice("server_metadata") { + for _, d := range ctx.StringSlice("server_metadata") { var key, val string parts := strings.Split(d, "=") key = parts[0] @@ -188,47 +291,59 @@ func Setup(c *cli.Context) error { metadata[key] = val } - server.DefaultServer = server.NewServer( - server.Name(c.String("server_name")), - server.Version(c.String("server_version")), - server.Id(c.String("server_id")), - server.Address(c.String("server_address")), - server.Advertise(c.String("server_advertise")), - server.Metadata(metadata), - ) + if len(metadata) > 0 { + serverOpts = append(serverOpts, server.Metadata(metadata)) + } - client.DefaultClient = client.NewClient() + if len(ctx.String("server_name")) > 0 { + serverOpts = append(serverOpts, server.Name(ctx.String("server_name"))) + } + + if len(ctx.String("server_version")) > 0 { + serverOpts = append(serverOpts, server.Version(ctx.String("server_version"))) + } + + if len(ctx.String("server_id")) > 0 { + serverOpts = append(serverOpts, server.Id(ctx.String("server_id"))) + } + + if len(ctx.String("server_address")) > 0 { + serverOpts = append(serverOpts, server.Address(ctx.String("server_address"))) + } + + if len(ctx.String("server_advertise")) > 0 { + serverOpts = append(serverOpts, server.Advertise(ctx.String("server_advertise"))) + } + + // We have some command line opts for the server. + // Lets set it up + if len(serverOpts) > 0 { + (*c.opts.Server).Init(serverOpts...) + } + + // Use an init option? + if len(clientOpts) > 0 { + (*c.opts.Client).Init(clientOpts...) + } return nil } -func Init() { - cli.AppHelpTemplate = ` -GLOBAL OPTIONS: - {{range .Flags}}{{.}} - {{end}} -` - - cli.HelpPrinter = func(writer io.Writer, templ string, data interface{}) { - w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) - t := template.Must(template.New("help").Parse(templ)) - err := t.Execute(w, data) - if err != nil { - panic(err) - } - w.Flush() - os.Exit(2) +func (c *cmd) Init(opts ...Option) error { + for _, o := range opts { + o(&c.opts) } - - app := cli.NewApp() - app.HideVersion = true - app.Usage = "a go micro app" - app.Action = func(c *cli.Context) { - for _, action := range Actions { - action(c) - } - } - app.Before = Setup - app.Flags = Flags - app.RunAndExitOnError() + c.app.Name = c.opts.Name + c.app.Version = c.opts.Version + c.app.Usage = c.opts.Description + c.app.RunAndExitOnError() + return nil +} + +func Init(opts ...Option) error { + return DefaultCmd.Init(opts...) +} + +func NewCmd(opts ...Option) Cmd { + return newCmd(opts...) } diff --git a/cmd/options.go b/cmd/options.go new file mode 100644 index 00000000..cd237afe --- /dev/null +++ b/cmd/options.go @@ -0,0 +1,115 @@ +package cmd + +import ( + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/selector" + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/transport" +) + +type Options struct { + // For the Command Line itself + Name string + Description string + Version string + + // We need pointers to things so we can swap them out if needed. + Broker *broker.Broker + Registry *registry.Registry + Selector *selector.Selector + Transport *transport.Transport + Client *client.Client + Server *server.Server + + Brokers map[string]func([]string, ...broker.Option) broker.Broker + Registries map[string]func([]string, ...registry.Option) registry.Registry + Selectors map[string]func(...selector.Option) selector.Selector + Transports map[string]func([]string, ...transport.Option) transport.Transport +} + +// Command line Name +func Name(n string) Option { + return func(o *Options) { + o.Name = n + } +} + +// Command line Description +func Description(d string) Option { + return func(o *Options) { + o.Description = d + } +} + +// Command line Version +func Version(v string) Option { + return func(o *Options) { + o.Version = v + } +} + +func Broker(b *broker.Broker) Option { + return func(o *Options) { + o.Broker = b + } +} + +func Selector(s *selector.Selector) Option { + return func(o *Options) { + o.Selector = s + } +} + +func Registry(r *registry.Registry) Option { + return func(o *Options) { + o.Registry = r + } +} + +func Transport(t *transport.Transport) Option { + return func(o *Options) { + o.Transport = t + } +} + +func Client(c *client.Client) Option { + return func(o *Options) { + o.Client = c + } +} + +func Server(s *server.Server) Option { + return func(o *Options) { + o.Server = s + } +} + +// New broker func +func NewBroker(name string, b func([]string, ...broker.Option) broker.Broker) Option { + return func(o *Options) { + o.Brokers[name] = b + } +} + +// New registry func +func NewRegistry(name string, r func([]string, ...registry.Option) registry.Registry) Option { + return func(o *Options) { + o.Registries[name] = r + } +} + +// New selector func +func NewSelector(name string, s func(...selector.Option) selector.Selector) Option { + return func(o *Options) { + o.Selectors[name] = s + } +} + +// New transport func +func NewTransport(name string, t func([]string, ...transport.Option) transport.Transport) Option { + return func(o *Options) { + o.Transports[name] = t + } +} diff --git a/examples/server/main.go b/examples/server/main.go index ad9485fa..bb45e759 100644 --- a/examples/server/main.go +++ b/examples/server/main.go @@ -6,40 +6,12 @@ import ( "github.com/micro/go-micro/examples/server/handler" "github.com/micro/go-micro/examples/server/subscriber" "github.com/micro/go-micro/server" - "golang.org/x/net/context" ) -func logWrapper(fn server.HandlerFunc) server.HandlerFunc { - return func(ctx context.Context, req server.Request, rsp interface{}) error { - log.Infof("[Log Wrapper] Before serving request method: %v", req.Method()) - err := fn(ctx, req, rsp) - log.Infof("[Log Wrapper] After serving request") - return err - } -} - -func logSubWrapper(fn server.SubscriberFunc) server.SubscriberFunc { - return func(ctx context.Context, req server.Publication) error { - log.Infof("[Log Sub Wrapper] Before serving publication topic: %v", req.Topic()) - err := fn(ctx, req) - log.Infof("[Log Sub Wrapper] After serving publication") - return err - } -} - func main() { // optionally setup command line usage cmd.Init() - md := server.DefaultOptions().Metadata - md["datacenter"] = "local" - - server.DefaultServer = server.NewServer( - server.WrapHandler(logWrapper), - server.WrapSubscriber(logSubWrapper), - server.Metadata(md), - ) - // Initialise Server server.Init( server.Name("go.micro.srv.example"), diff --git a/examples/server/wrapper/main.go b/examples/server/wrapper/main.go new file mode 100644 index 00000000..ad9485fa --- /dev/null +++ b/examples/server/wrapper/main.go @@ -0,0 +1,78 @@ +package main + +import ( + log "github.com/golang/glog" + "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/examples/server/handler" + "github.com/micro/go-micro/examples/server/subscriber" + "github.com/micro/go-micro/server" + "golang.org/x/net/context" +) + +func logWrapper(fn server.HandlerFunc) server.HandlerFunc { + return func(ctx context.Context, req server.Request, rsp interface{}) error { + log.Infof("[Log Wrapper] Before serving request method: %v", req.Method()) + err := fn(ctx, req, rsp) + log.Infof("[Log Wrapper] After serving request") + return err + } +} + +func logSubWrapper(fn server.SubscriberFunc) server.SubscriberFunc { + return func(ctx context.Context, req server.Publication) error { + log.Infof("[Log Sub Wrapper] Before serving publication topic: %v", req.Topic()) + err := fn(ctx, req) + log.Infof("[Log Sub Wrapper] After serving publication") + return err + } +} + +func main() { + // optionally setup command line usage + cmd.Init() + + md := server.DefaultOptions().Metadata + md["datacenter"] = "local" + + server.DefaultServer = server.NewServer( + server.WrapHandler(logWrapper), + server.WrapSubscriber(logSubWrapper), + server.Metadata(md), + ) + + // Initialise Server + server.Init( + server.Name("go.micro.srv.example"), + ) + + // Register Handlers + server.Handle( + server.NewHandler( + new(handler.Example), + ), + ) + + // Register Subscribers + if err := server.Subscribe( + server.NewSubscriber( + "topic.go.micro.srv.example", + new(subscriber.Example), + ), + ); err != nil { + log.Fatal(err) + } + + if err := server.Subscribe( + server.NewSubscriber( + "topic.go.micro.srv.example", + subscriber.Handler, + ), + ); err != nil { + log.Fatal(err) + } + + // Run server + if err := server.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/examples/service/README.md b/examples/service/README.md new file mode 100644 index 00000000..f474d65f --- /dev/null +++ b/examples/service/README.md @@ -0,0 +1,38 @@ +# Service + +This is an example of creating a micro service using the top level interface. + +## Prereqs + +Micro services need a discovery system so they can find each other. Micro uses consul by default but +its easily swapped out with etcd, kubernetes, or various other systems. We'll run consul for convenience. + +1. Follow the install instructions - [https://www.consul.io/intro/getting-started/install.html](https://www.consul.io/intro/getting-started/install.html) + +2. Run Consul + +```shell +$ consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul +``` + +## Run the example + +1. Get the service + +```shell +go get github.com/micro/go-micro/examples/service +``` + +2. Run the server + +```shell +$GOPATH/bin/service +``` + +3. Run the client + +```shell +$GOPATH/bin/service --client +``` + +And that's all there is to it. diff --git a/examples/service/main.go b/examples/service/main.go new file mode 100644 index 00000000..d8f54e7c --- /dev/null +++ b/examples/service/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "fmt" + "os" + + "github.com/micro/cli" + micro "github.com/micro/go-micro" + proto "github.com/micro/go-micro/examples/service/proto" + "golang.org/x/net/context" +) + +/* + +Example usage of top level service initialisation + +*/ + +type Greeter struct{} + +func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error { + rsp.Greeting = "Hello " + req.Name + return nil +} + +// Setup and the client +func client(service micro.Service) { + // Create new greeter client + greeter := proto.NewGreeterClient("greeter", service.Client()) + + // Call the greeter + rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "John"}) + if err != nil { + fmt.Println(err) + } + + // Print response + fmt.Println(rsp.Greeting) +} + +func main() { + // Create a new service. Optionally include some options here. + service := micro.NewService( + micro.Name("greeter"), + micro.Version("latest"), + micro.Metadata(map[string]string{ + "type": "helloworld", + }), + + // Setup some flags. Specify --client to run the client + + // Add runtime flags + // We could do this below too + micro.Flags(cli.BoolFlag{ + Name: "client", + Usage: "Launch the client", + }), + ) + + // Init will parse the command line flags. Any flags set will + // override the above settings. Options defined here will + // override anything set on the command line. + service.Init( + // Add runtime action + // We could actually do this above + micro.Action(func(c *cli.Context) { + if c.Bool("client") { + client(service) + os.Exit(0) + } + }), + ) + + // By default we'll run the server unless the flags catch us + + // Setup the server + + // Register handler + proto.RegisterGreeterHandler(service.Server(), new(Greeter)) + + // Run the server + if err := service.Run(); err != nil { + fmt.Println(err) + } +} diff --git a/examples/service/proto/greeter.pb.go b/examples/service/proto/greeter.pb.go new file mode 100644 index 00000000..20fd7cf1 --- /dev/null +++ b/examples/service/proto/greeter.pb.go @@ -0,0 +1,124 @@ +// Code generated by protoc-gen-go. +// source: go-micro/examples/service/proto/greeter.proto +// DO NOT EDIT! + +/* +Package greeter is a generated protocol buffer package. + +It is generated from these files: + go-micro/examples/service/proto/greeter.proto + +It has these top-level messages: + HelloRequest + HelloResponse +*/ +package greeter + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + client "github.com/micro/go-micro/client" + server "github.com/micro/go-micro/server" + context "golang.org/x/net/context" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type HelloRequest struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *HelloRequest) Reset() { *m = HelloRequest{} } +func (m *HelloRequest) String() string { return proto.CompactTextString(m) } +func (*HelloRequest) ProtoMessage() {} +func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +type HelloResponse struct { + Greeting string `protobuf:"bytes,2,opt,name=greeting" json:"greeting,omitempty"` +} + +func (m *HelloResponse) Reset() { *m = HelloResponse{} } +func (m *HelloResponse) String() string { return proto.CompactTextString(m) } +func (*HelloResponse) ProtoMessage() {} +func (*HelloResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func init() { + proto.RegisterType((*HelloRequest)(nil), "HelloRequest") + proto.RegisterType((*HelloResponse)(nil), "HelloResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ client.Option +var _ server.Option + +// 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})) +} + +type Greeter struct { + GreeterHandler +} + +func (h *Greeter) Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error { + return h.GreeterHandler.Hello(ctx, in, out) +} + +var fileDescriptor0 = []byte{ + // 153 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4d, 0xcf, 0xd7, 0xcd, + 0xcd, 0x4c, 0x2e, 0xca, 0xd7, 0x4f, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0x2d, 0xd6, 0x2f, 0x4e, + 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x4f, 0x2f, 0x4a, 0x4d, + 0x2d, 0x49, 0x2d, 0xd2, 0x03, 0xf3, 0x94, 0x64, 0xb8, 0x78, 0x3c, 0x52, 0x73, 0x72, 0xf2, 0x83, + 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x78, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, + 0x15, 0x18, 0x35, 0x38, 0x95, 0x14, 0xb9, 0x78, 0xa1, 0xb2, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, + 0x42, 0x02, 0x5c, 0x1c, 0x60, 0xfd, 0x99, 0x79, 0xe9, 0x12, 0x4c, 0x20, 0x25, 0x46, 0xc6, 0x5c, + 0xec, 0xee, 0x10, 0x13, 0x85, 0x34, 0xb8, 0x58, 0xc1, 0xaa, 0x85, 0x78, 0xf5, 0x90, 0xcd, 0x94, + 0xe2, 0xd3, 0x43, 0x31, 0x44, 0x89, 0x21, 0x89, 0x0d, 0x6c, 0xb9, 0x31, 0x20, 0x00, 0x00, 0xff, + 0xff, 0x0f, 0xa9, 0x59, 0xb3, 0xad, 0x00, 0x00, 0x00, +} diff --git a/examples/service/proto/greeter.proto b/examples/service/proto/greeter.proto new file mode 100644 index 00000000..4ff347af --- /dev/null +++ b/examples/service/proto/greeter.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +service Greeter { + rpc Hello(HelloRequest) returns (HelloResponse) {} +} + +message HelloRequest { + string name = 1; +} + +message HelloResponse { + string greeting = 2; +} diff --git a/go-micro.go b/go-micro.go new file mode 100644 index 00000000..b8ac7fe9 --- /dev/null +++ b/go-micro.go @@ -0,0 +1,47 @@ +/* +Go micro provides a pluggable library to build microservices. + + import ( + micro "github.com/micro/go-micro" + ) + + service := micro.NewService() + h := service.Server().NewHandler(&Greeter{}) + service.Server().Handle(h) + service.Run() + + + req := service.Client().NewRequest(service, method, request) + rsp := response{} + err := service.Client().Call(req, rsp) + +*/ + +package micro + +import ( + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/server" +) + +// Service is an interface that wraps the lower level libraries +// within go-micro. Its a convenience method for building +// and initialising services. +type Service interface { + Init(...Option) + Options() Options + Client() client.Client + Server() server.Server + Run() error + String() string +} + +type Option func(*Options) + +var ( + HeaderPrefix = "X-Micro-" +) + +func NewService(opts ...Option) Service { + return newService(opts...) +} diff --git a/options.go b/options.go new file mode 100644 index 00000000..8c347def --- /dev/null +++ b/options.go @@ -0,0 +1,130 @@ +package micro + +import ( + "github.com/micro/cli" + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/cmd" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/transport" +) + +type Options struct { + Broker broker.Broker + Cmd cmd.Cmd + Client client.Client + Server server.Server + Registry registry.Registry + Transport transport.Transport + + // Before and After funcs + BeforeStart []func() error + AfterStop []func() error + + // Alternative options for those implementing the interface + Options map[string]string +} + +func newOptions(opts ...Option) Options { + opt := Options{ + Broker: broker.DefaultBroker, + Cmd: cmd.DefaultCmd, + Client: client.DefaultClient, + Server: server.DefaultServer, + Registry: registry.DefaultRegistry, + Transport: transport.DefaultTransport, + Options: map[string]string{}, + } + + for _, o := range opts { + o(&opt) + } + + return opt +} + +func Broker(b broker.Broker) Option { + return func(o *Options) { + o.Broker = b + } +} + +func Cmd(c cmd.Cmd) Option { + return func(o *Options) { + o.Cmd = c + } +} + +func Client(c client.Client) Option { + return func(o *Options) { + o.Client = c + } +} + +func Server(s server.Server) Option { + return func(o *Options) { + o.Server = s + } +} + +func Registry(r registry.Registry) Option { + return func(o *Options) { + o.Registry = r + } +} + +func Transport(t transport.Transport) Option { + return func(o *Options) { + o.Transport = t + } +} + +// Convenience options + +// Name of the service +func Name(n string) Option { + return func(o *Options) { + o.Server.Init(server.Name(n)) + } +} + +// Version of the service +func Version(v string) Option { + return func(o *Options) { + o.Server.Init(server.Version(v)) + } +} + +// Metadata associated with the service +func Metadata(md map[string]string) Option { + return func(o *Options) { + o.Server.Init(server.Metadata(md)) + } +} + +func Flags(flags ...cli.Flag) Option { + return func(o *Options) { + o.Cmd.App().Flags = append(o.Cmd.App().Flags, flags...) + } +} + +func Action(a func(*cli.Context)) Option { + return func(o *Options) { + o.Cmd.App().Action = a + } +} + +// Before and Afters + +func BeforeStart(fn func() error) Option { + return func(o *Options) { + o.BeforeStart = append(o.BeforeStart, fn) + } +} + +func AfterStop(fn func() error) Option { + return func(o *Options) { + o.AfterStop = append(o.AfterStop, fn) + } +} diff --git a/selector/blacklist/black_list_selector.go b/selector/blacklist/black_list_selector.go index 3769c0a4..689710f6 100644 --- a/selector/blacklist/black_list_selector.go +++ b/selector/blacklist/black_list_selector.go @@ -27,7 +27,7 @@ type blackListSelector struct { } func init() { - cmd.Selectors["blacklist"] = NewSelector + cmd.DefaultSelectors["blacklist"] = NewSelector rand.Seed(time.Now().Unix()) } diff --git a/selector/random/random.go b/selector/random/random.go index 57e325cc..a0fcefbd 100644 --- a/selector/random/random.go +++ b/selector/random/random.go @@ -6,7 +6,7 @@ import ( ) func init() { - cmd.Selectors["random"] = NewSelector + cmd.DefaultSelectors["random"] = NewSelector } func NewSelector(opts ...selector.Option) selector.Selector { diff --git a/selector/roundrobin/round_robin_selector.go b/selector/roundrobin/round_robin_selector.go index 48a89874..7ee019dd 100644 --- a/selector/roundrobin/round_robin_selector.go +++ b/selector/roundrobin/round_robin_selector.go @@ -13,7 +13,7 @@ type roundRobinSelector struct { } func init() { - cmd.Selectors["roundrobin"] = NewSelector + cmd.DefaultSelectors["roundrobin"] = NewSelector } func (r *roundRobinSelector) Select(service string, opts ...selector.SelectOption) (selector.Next, error) { diff --git a/server/rpc_server.go b/server/rpc_server.go index 6b65b0ed..14e035d7 100644 --- a/server/rpc_server.go +++ b/server/rpc_server.go @@ -105,15 +105,13 @@ func (s *rpcServer) Options() Options { return opts } -func (s *rpcServer) Init(opts ...Option) { +func (s *rpcServer) Init(opts ...Option) error { s.Lock() for _, opt := range opts { opt(&s.opts) } - if len(s.opts.Id) == 0 { - s.opts.Id = s.opts.Name + "-" + DefaultId - } s.Unlock() + return nil } func (s *rpcServer) NewHandler(h interface{}) Handler { @@ -187,7 +185,7 @@ func (s *rpcServer) Register() error { // register service node := ®istry.Node{ - Id: config.Id, + Id: config.Name + "-" + config.Id, Address: addr, Port: port, Metadata: config.Metadata, @@ -260,7 +258,7 @@ func (s *rpcServer) Deregister() error { } node := ®istry.Node{ - Id: config.Id, + Id: config.Name + "-" + config.Id, Address: addr, Port: port, } diff --git a/server/server.go b/server/server.go index 65449b18..0fff3afb 100644 --- a/server/server.go +++ b/server/server.go @@ -40,7 +40,7 @@ import ( type Server interface { Options() Options - Init(...Option) + Init(...Option) error Handle(Handler) error NewHandler(interface{}) Handler NewSubscriber(string, interface{}) Subscriber diff --git a/service.go b/service.go new file mode 100644 index 00000000..118614af --- /dev/null +++ b/service.go @@ -0,0 +1,120 @@ +package micro + +import ( + "os" + "os/signal" + "syscall" + + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/context" + "github.com/micro/go-micro/server" +) + +type service struct { + opts Options +} + +func newService(opts ...Option) Service { + options := newOptions(opts...) + + options.Client = &clientWrapper{ + options.Client, + context.Metadata{ + HeaderPrefix + "From-Service": options.Server.Options().Name, + }, + } + + return &service{ + opts: options, + } +} + +func (s *service) Init(opts ...Option) { + // We might get more command flags or the action here + // This is pretty ugly, find a better way + options := newOptions() + options.Cmd = s.opts.Cmd + for _, o := range opts { + o(&options) + } + s.opts.Cmd = options.Cmd + + // Initialise the command flags, overriding new service + s.opts.Cmd.Init() + + // Update any options to override command flags + for _, o := range opts { + o(&s.opts) + } +} + +func (s *service) Options() Options { + return s.opts +} + +func (s *service) Client() client.Client { + return s.opts.Client +} + +func (s *service) Server() server.Server { + return s.opts.Server +} + +func (s *service) String() string { + return "go-micro" +} + +func (s *service) Start() error { + for _, fn := range s.opts.BeforeStart { + if err := fn(); err != nil { + return err + } + } + + if err := s.opts.Server.Start(); err != nil { + return err + } + + if err := s.opts.Server.Register(); err != nil { + return err + } + + return nil +} + +func (s *service) Stop() error { + if err := s.opts.Server.Deregister(); err != nil { + return err + } + + if err := s.opts.Server.Stop(); err != nil { + return err + } + + var gerr error + for _, fn := range s.opts.AfterStop { + if err := fn(); err != nil { + // should we bail if it fails? + // other funcs will not be executed + // seems wrong + gerr = err + } + } + return gerr +} + +func (s *service) Run() error { + if err := s.Start(); err != nil { + return err + } + + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) + <-ch + + if err := s.Stop(); err != nil { + return err + } + + return nil +} diff --git a/wrapper.go b/wrapper.go new file mode 100644 index 00000000..c75dbbc5 --- /dev/null +++ b/wrapper.go @@ -0,0 +1,28 @@ +package micro + +import ( + "github.com/micro/go-micro/client" + cx "github.com/micro/go-micro/context" + + "golang.org/x/net/context" +) + +type clientWrapper struct { + client.Client + headers cx.Metadata +} + +func (c *clientWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { + ctx = cx.WithMetadata(ctx, c.headers) + return c.Client.Call(ctx, req, rsp, opts...) +} + +func (c *clientWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Streamer, error) { + ctx = cx.WithMetadata(ctx, c.headers) + return c.Client.Stream(ctx, req, opts...) +} + +func (c *clientWrapper) Publish(ctx context.Context, p client.Publication, opts ...client.PublishOption) error { + ctx = cx.WithMetadata(ctx, c.headers) + return c.Client.Publish(ctx, p, opts...) +}