Explanation for Bootstrap

Let’s take a look at the bootstrap process, main.go:

package main



import (

"github.com/micro/go-micro/v2"

log "github.com/micro/go-micro/v2/logger"



"hello/handler"

"hello/subscriber"



hello "hello/proto/hello"

)



func main() {

// New Service

service := micro.NewService(

micro.Name("com.foo.service.hello"),

micro.Version("latest"),

)



// Initialise service

service.Init()



// Register Handler

hello.RegisterHelloHandler(service.Server(), new(handler.Hello))



// Register Struct as Subscriber

micro.RegisterSubscriber("com.foo.service.hello", service.Server(), new(subscriber.Hello))



// Run service

if err := service.Run(); err != nil {

log.Fatal(err)

}

}

The code is roughly divided into 4 parts, which are importing dependencies, creating and initializing service, registering business processing handlers, and running service.

Importing Dependencies

Only one line of code in this part is worth explaining separately:

hello "hello/proto/hello"

we set an explicit package name hello to hello/proto/hello . This is also a convention of Micro: set explicit package names for all interface import packages. This avoids relying on the original package name of the imported code. In practice, if you do not make special settings, the package name of the automatically generated code will be relatively long. Take hello.pb.go as an example, its package name is com_foo_srv_hello . Obviously setting an explicit package name is a better choice.

Creating and Initializing services

// New Service

service := micro.NewService(

micro.Name("com.foo.srv.hello"),

micro.Version("latest"),

)

We use the method micro.NewService(opts …Option) Service to create a service. This method accepts multiply micro.Option as its parameter，then creates and returns an instance of micro.Service

Apparently that micro.Option is the key to controlling the service. The sample code above uses options to specify the service name and version number, respectively.

There are currently 29 options available to control all aspects of the service. Some options can be specified multiple times to form an overlay effect (will be described later).

However, there is not any documentation for such important options. If you want to learn, digging source code is the only way. And most Option source code does not have any comments, which further increases the difficulty of learning.

Although this article is not intended to be an ultimate reference manual for Micro, I decide to list all 29 options of Micro v2.4.0 and explain them one by one as below. Since these options are so important for understanding and using Micro, and there is no other material to refer to.

micro.Name(n string) Option ， Specify the service name. Generally, the naming convention is $namespace. $type. $name . $namespace represents the namespace of the project, and $type represents the service type (such as gRPC and web). The gRPC service type is usually abbreviated to srv . After the service instance starts up this name will be automatically registered in the Registry , which becomes the basis for service discovery. The default is go.micro.server . Note: Therefore, this option must be specified, otherwise all nodes use the same default name, which will lead to confusion micro.Version(v string) Option，Specify the service version. The default value is a formatted string from startup time. with proper selection of the version number, combined with the right Selector , we can implement elegant rotation upgrade, grayscale release, A / B testing and many other operations. micro.Address(addr string) Option，Specifies the gRPC service address. The default is localhost address combined with a random port. Because clients discover services through the Registry , the random port does not affect that discovery. However, in practice, a fixed port number is often specified, which will be beneficial to operation and security control. micro.RegisterTTL(t time.Duration) Option，Specify TTL of the service registration information in the registry. The default value is 1-minute micro.RegisterInterval(t time.Duration) Option，Specifies the interval at which the service reports its status to the registry. The default value is 30 seconds. These two registry-related options help avoid “invalid registration information” when service encounters unexpected downtime. micro.WrapHandler(w …server.HandlerWrapper) Option，Wrap service handler. It is conceptually similar to Gin Middleware and controls the behavior of a handler centrally. The wrapper can be applied in multiple layers, and the execution order is from outside to inside (an example will be provided in a later article) micro.WrapSubscriber(w …server.SubscriberWrapper) Option，Similar to WrapHandler, except that it is used to wrap Subscribers in asynchronous messaging. micro.WrapCall(w …client.CallWrapper) Option，Wrap every method call from client. micro.WrapClient(w …client.Wrapper) Option，Wrap service client, can be applied in multiple layers, and the execution order is from inside to outside. micro.BeforeStart(fn func() error) Option，Set multiple callback functions before the service starts. micro.BeforeStop(fn func() error) Option，Set multiple callback functions before the service stop. micro.AfterStart(fn func() error) Option，Set multiple callback functions after the service starts. micro.AfterStop(fn func() error) Option，Set multiple callback functions after the service stop micro.Action(a func(*cli.Context)) Option，Handle command line arguments. Supports sub-commands and flags. See micro/cli for details micro.Flags(flags …cli.Flag) Option，Handle command-line flags. See micro/cli for details micro.Cmd(c cmd.Cmd) Option， Specifies the command line processing object. Generated by newCmd by default, this object supports several default environment variables and command-line parameters. In essence, it’s a built-in collection of cli.Flag. Note: As for command line support, Micro has both advantages and disadvantages at the same time. The advantage is that it provides a lot of default options, which can save developers time. The disadvantage is that this design is strongly invasive to user programs: the framework requires developers to handle their command-line arguments in a uniform way according to micro/cli. Otherwise, the program will report an error and cannot run. For example, if we run ./hello-service --foo=bar, we will get and error: Incorrect Usage. Flag provided but not defined: --foo=bar . Fortunately, micro.Cmd can make up for the problem caused by intrusiveness. If an existing project wants to introduce Micro and it already has its own command-line processing mechanism, then you need to use micro.Cmd to override the default behavior (while losing the default command line processing capabilities). Regarding command-line processing, further explanation will be provided later in this article. micro.Metadata(md map[string]string) Option，Specify service metadata. Metadata is often used to tag and group services, implement custom load balance strategy, etc. micro.Transport(t transport.Transport) Option，Specify the transport protocol, default is HTTP micro.Selector(s selector.Selector) Option ，Specify node selector to implement different load strategies. Default is Random Selector micro.Registry(r registry.Registry) Option，Specifiy the Registry used for service discovery. The default is the mDNS-based Registry micro.Server(s server.Server) Option，Specify custom Server if the default one does not meet your requirement.。Default is rpcServer micro.HandleSignal(b bool) Option， Toggles automatic installation of the signal handler that traps TERM, INT, and QUIT. The default is true micro.Context(ctx context.Context) Option，Specifiy the initial context of the service. The default is context.BackGround() , which can be used to control the service lifetime and more micro.Client(c client.Client) Option，Specify the service client. The default is rpcClient micro.Broker(b broker.Broker) Option， Specify the message broker used by pub/sub 。Default is HTTP broker micro.Profile(p profile.Profile) Option，Profile to be used for debug profile micro.Tracer(t trace.Tracer) Option，Tracer sets the tracer for the service micro.Auth(a auth.Auth) Option，Auth sets the auth for the service。 （according the official Slask this API is not stable enough, not ready for production at v2.4.0） micro.Config(c config.Config) Option，Config sets the config for the service

Therefore, by specifying an appropriate Option , the behavior of the service can be highly customized. For example, to modify the TTL of the service registration information:

...

// New Service

service := micro.NewService(

micro.Name("foo.bar"),

micro.Version("v1.0"),

// change default TTL value

micro.RegisterTTL(5 * time.Minute),

...

)

...

Note: Most of the above options can be specified in multiple approaches. Hard-coding in the source file is just one way among them. In fact, Micro recommends that users specify options through environment variables, as this provides more flexibility. Taking micro.RegisterTTL as an example, we can specify it at runtime through the environment variable $ MICRO_REGISTER_TTL=value or the command line flag —-register_ttl=value (value in second). There is no documentation at present for these built-in options. Run ./hello-service -h will show a brief description of them. If you want to learn the complete details, dig the source of newCmd yourself. Subsequent articles in this series will explain this topic further.

Once created, you can initialize the service:

// Initialize service

service.Init()

The service.Init method can accept the same parameters as micro.NewService . So the above 29 options can also be used in service.Init . They have the same effect but only differ in timing.

Since the service has been created at this point, we can retrieve information from the service instance. For example, you can read the random port:

// Initialize service

service.Init(

// print log after start

micro.AfterStart(func() error {

log.Infof("service listening on %s!",

service.Options().Server.Options().Address,

)

return nil

}),

)

Registering business handlers

// Register Handler

hello.RegisterHelloHandler(service.Server(), new(handler.Hello))



// Register Struct as Subscriber

micro.RegisterSubscriber("com.foo.service.hello", service.Server(), new(subscriber.Hello))

Only after the Handler registration can our business code truly provide services to the outside world. Here are two typical registration operations:

Register the gRPC handler. Create a handler.Hello object and register it on the Server . Because handler.Hello implements the HelloHandler interface, it can be passed to hello.RegisterHelloHandler , otherwise an error will occur. Multiple Handlers can be registered to a Server to provide different business functions. Register the message processing object. The first parameter is the message topic, the second parameter is the Server , and the third parameter is the message processing object.

For more details on asynchronous messaging, we will discuss it in a dedicated article later.

Run the service

if err := service.Run(); err != nil {

log.Fatal(err)

}

At this point, the service is running up finally