From 0d50b2eb3214c2d85836a396aad9ac2964fb4c88 Mon Sep 17 00:00:00 2001 From: Asim Date: Fri, 1 Jan 2016 01:16:21 +0000 Subject: [PATCH] Update top level init --- cmd/cmd.go | 156 ++++++++++++++++----------- cmd/options.go | 61 +++++++++++ examples/service/README.md | 38 +++++++ examples/service/main.go | 93 ++++++++++++++++ examples/service/proto/greeter.pb.go | 124 +++++++++++++++++++++ examples/service/proto/greeter.proto | 13 +++ go-micro.go | 3 + options.go | 83 ++++++++++---- service.go | 29 ++++- 9 files changed, 515 insertions(+), 85 deletions(-) create mode 100644 cmd/options.go create mode 100644 examples/service/README.md create mode 100644 examples/service/main.go create mode 100644 examples/service/proto/greeter.pb.go create mode 100644 examples/service/proto/greeter.proto diff --git a/cmd/cmd.go b/cmd/cmd.go index 776eb6a3..30fd85c7 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -3,15 +3,12 @@ package cmd import ( "flag" "fmt" - "io" "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 +17,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) + // 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", @@ -127,19 +141,19 @@ 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, } ) @@ -148,37 +162,68 @@ func init() { rand.Seed(time.Now().Unix()) } -func Setup(c *cli.Context) error { +func newCmd(opts ...Option) Cmd { + options := Options{ + Brokers: DefaultBrokers, + Registries: DefaultRegistries, + Selectors: DefaultSelectors, + Transports: DefaultTransports, + } + + for _, o := range opts { + o(&options) + } + + 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) {} + 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 b, ok := c.opts.Brokers[ctx.String("broker")]; ok { + broker.DefaultBroker = b(strings.Split(ctx.String("broker_address"), ",")) } - if r, ok := Registries[c.String("registry")]; ok { - registry.DefaultRegistry = r(strings.Split(c.String("registry_address"), ",")) + if r, ok := c.opts.Registries[ctx.String("registry")]; ok { + registry.DefaultRegistry = r(strings.Split(ctx.String("registry_address"), ",")) } - if s, ok := Selectors[c.String("selector")]; ok { + if s, ok := c.opts.Selectors[ctx.String("selector")]; ok { selector.DefaultSelector = s(selector.Registry(registry.DefaultRegistry)) } - if t, ok := Transports[c.String("transport")]; ok { - transport.DefaultTransport = t(strings.Split(c.String("transport_address"), ",")) + if t, ok := c.opts.Transports[ctx.String("transport")]; ok { + transport.DefaultTransport = t(strings.Split(ctx.String("transport_address"), ",")) } 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] @@ -189,11 +234,11 @@ func Setup(c *cli.Context) error { } 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.Name(ctx.String("server_name")), + server.Version(ctx.String("server_version")), + server.Id(ctx.String("server_id")), + server.Address(ctx.String("server_address")), + server.Advertise(ctx.String("server_advertise")), server.Metadata(metadata), ) @@ -202,33 +247,20 @@ func Setup(c *cli.Context) error { 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) { + 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() +} + +func Init(opts ...Option) { + 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..70879daf --- /dev/null +++ b/cmd/options.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/selector" + "github.com/micro/go-micro/transport" +) + +type Options struct { + Name string + Description string + Version string + + 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 +} + +func Name(n string) Option { + return func(o *Options) { + o.Name = n + } +} + +func Description(d string) Option { + return func(o *Options) { + o.Description = d + } +} + +func Version(v string) Option { + return func(o *Options) { + o.Version = v + } +} + +func Broker(name string, b func([]string, ...broker.Option) broker.Broker) Option { + return func(o *Options) { + o.Brokers[name] = b + } +} + +func Registry(name string, r func([]string, ...registry.Option) registry.Registry) Option { + return func(o *Options) { + o.Registries[name] = r + } +} + +func Selector(name string, s func(...selector.Option) selector.Selector) Option { + return func(o *Options) { + o.Selectors[name] = s + } +} + +func Transport(name string, t func([]string, ...transport.Option) transport.Transport) Option { + return func(o *Options) { + o.Transports[name] = t + } +} 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..7ae76519 --- /dev/null +++ b/examples/service/main.go @@ -0,0 +1,93 @@ +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) +} + +// Setup some command line flags +func flags(service micro.Service) { + app := service.Cmd().App() + app.Flags = append(app.Flags, + &cli.BoolFlag{ + Name: "server", + Usage: "Launch the server", + }, + &cli.BoolFlag{ + Name: "client", + Usage: "Launch the client", + }, + ) + + // Let's launch the server or the client + app.Action = func(c *cli.Context) { + if c.Bool("client") { + client(service) + os.Exit(0) + } + } +} + +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 + flags(service) + + // 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() + + // 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 index 384f862b..94425bd6 100644 --- a/go-micro.go +++ b/go-micro.go @@ -21,6 +21,7 @@ package gomicro import ( "github.com/micro/go-micro/client" + "github.com/micro/go-micro/cmd" "github.com/micro/go-micro/server" ) @@ -28,6 +29,8 @@ import ( // within go-micro. Its a convenience method for building // and initialising services. type Service interface { + Init(...Option) + Cmd() cmd.Cmd Client() client.Client Server() server.Server Run() error diff --git a/options.go b/options.go index ccb3ee07..159873b4 100644 --- a/options.go +++ b/options.go @@ -3,6 +3,7 @@ package gomicro import ( "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" @@ -10,38 +11,35 @@ import ( 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 { - var opt 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) } - if opt.Broker == nil { - opt.Broker = broker.DefaultBroker - } - - if opt.Client == nil { - opt.Client = client.DefaultClient - } - - if opt.Server == nil { - opt.Server = server.DefaultServer - } - - if opt.Registry == nil { - opt.Registry = registry.DefaultRegistry - } - - if opt.Transport == nil { - opt.Transport = transport.DefaultTransport - } - return opt } @@ -51,6 +49,12 @@ func Broker(b broker.Broker) Option { } } +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 @@ -74,3 +78,40 @@ func Transport(t transport.Transport) Option { 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)) + } +} + +// 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/service.go b/service.go index 4b2d8d91..151f056d 100644 --- a/service.go +++ b/service.go @@ -6,6 +6,7 @@ import ( "syscall" "github.com/micro/go-micro/client" + "github.com/micro/go-micro/cmd" "github.com/micro/go-micro/context" "github.com/micro/go-micro/server" ) @@ -20,7 +21,7 @@ func newService(opts ...Option) Service { options.Client = &clientWrapper{ options.Client, context.Metadata{ - HeaderPrefix + "From-Service": options.Server.Config().Name(), + HeaderPrefix + "From-Service": options.Server.Options().Name, }, } @@ -29,6 +30,15 @@ func newService(opts ...Option) Service { } } +func (s *service) Init(opts ...Option) { + s.opts.Cmd.Init() + s = newService(opts...).(*service) +} + +func (s *service) Cmd() cmd.Cmd { + return s.opts.Cmd +} + func (s *service) Client() client.Client { return s.opts.Client } @@ -42,6 +52,12 @@ func (s *service) String() string { } 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 } @@ -62,7 +78,16 @@ func (s *service) Stop() error { return err } - return nil + 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 {