many improvements with options and noop stuff

* add many options helpers
* fix noop client to allow publish messages to topic in broker
* fix noop server to allow registering in registry
* fix noop server to allow subscribe to topic in broker
* fix new service initialization

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2020-10-16 09:38:57 +03:00
parent a59aae760f
commit 14c97d59c1
39 changed files with 1384 additions and 432 deletions

View File

@ -21,13 +21,8 @@ type httpServer struct {
} }
func NewServer(address string, opts ...server.Option) server.Server { func NewServer(address string, opts ...server.Option) server.Server {
var options server.Options
for _, o := range opts {
o(&options)
}
return &httpServer{ return &httpServer{
opts: options, opts: server.NewOptions(opts...),
mux: http.NewServeMux(), mux: http.NewServeMux(),
address: address, address: address,
exit: make(chan chan error), exit: make(chan chan error),

View File

@ -21,6 +21,14 @@ type Options struct {
Wrappers []Wrapper Wrappers []Wrapper
} }
func NewOptions(opts ...Option) Options {
options := Options{}
for _, o := range opts {
o(&options)
}
return options
}
type Wrapper func(h http.Handler) http.Handler type Wrapper func(h http.Handler) http.Handler
func WrapHandler(w ...Wrapper) Option { func WrapHandler(w ...Wrapper) Option {

View File

@ -17,7 +17,7 @@ const (
) )
var ( var (
DefaultAuth Auth = newAuth() DefaultAuth Auth = &NoopAuth{opts: NewOptions()}
// ErrInvalidToken is when the token provided is not valid // ErrInvalidToken is when the token provided is not valid
ErrInvalidToken = errors.New("invalid token provided") ErrInvalidToken = errors.New("invalid token provided")
// ErrForbidden is when a user does not have the necessary scope to access a resource // ErrForbidden is when a user does not have the necessary scope to access a resource

View File

@ -4,40 +4,29 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
func newAuth(opts ...Option) Auth { type NoopAuth struct {
var options Options
for _, o := range opts {
o(&options)
}
return &noop{
opts: options,
}
}
type noop struct {
opts Options opts Options
} }
// String returns the name of the implementation // String returns the name of the implementation
func (n *noop) String() string { func (n *NoopAuth) String() string {
return "noop" return "noop"
} }
// Init the auth // Init the auth
func (n *noop) Init(opts ...Option) { func (n *NoopAuth) Init(opts ...Option) {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
} }
} }
// Options set for auth // Options set for auth
func (n *noop) Options() Options { func (n *NoopAuth) Options() Options {
return n.opts return n.opts
} }
// Generate a new account // Generate a new account
func (n *noop) Generate(id string, opts ...GenerateOption) (*Account, error) { func (n *NoopAuth) Generate(id string, opts ...GenerateOption) (*Account, error) {
options := NewGenerateOptions(opts...) options := NewGenerateOptions(opts...)
return &Account{ return &Account{
@ -50,27 +39,27 @@ func (n *noop) Generate(id string, opts ...GenerateOption) (*Account, error) {
} }
// Grant access to a resource // Grant access to a resource
func (n *noop) Grant(rule *Rule) error { func (n *NoopAuth) Grant(rule *Rule) error {
return nil return nil
} }
// Revoke access to a resource // Revoke access to a resource
func (n *noop) Revoke(rule *Rule) error { func (n *NoopAuth) Revoke(rule *Rule) error {
return nil return nil
} }
// Rules used to verify requests // Rules used to verify requests
func (n *noop) Rules(opts ...RulesOption) ([]*Rule, error) { func (n *NoopAuth) Rules(opts ...RulesOption) ([]*Rule, error) {
return []*Rule{}, nil return []*Rule{}, nil
} }
// Verify an account has access to a resource // Verify an account has access to a resource
func (n *noop) Verify(acc *Account, res *Resource, opts ...VerifyOption) error { func (n *NoopAuth) Verify(acc *Account, res *Resource, opts ...VerifyOption) error {
return nil return nil
} }
// Inspect a token // Inspect a token
func (n *noop) Inspect(token string) (*Account, error) { func (n *NoopAuth) Inspect(token string) (*Account, error) {
uid, err := uuid.NewRandom() uid, err := uuid.NewRandom()
if err != nil { if err != nil {
return nil, err return nil, err
@ -79,6 +68,6 @@ func (n *noop) Inspect(token string) (*Account, error) {
} }
// Token generation using an account id and secret // Token generation using an account id and secret
func (n *noop) Token(opts ...TokenOption) (*Token, error) { func (n *NoopAuth) Token(opts ...TokenOption) (*Token, error) {
return &Token{}, nil return &Token{}, nil
} }

View File

@ -1,8 +1,10 @@
// Package broker is an interface used for asynchronous messaging // Package broker is an interface used for asynchronous messaging
package broker package broker
import "context"
var ( var (
DefaultBroker Broker = newBroker() DefaultBroker Broker = &NoopBroker{opts: NewOptions()}
) )
// Broker is an interface used for asynchronous messaging. // Broker is an interface used for asynchronous messaging.
@ -10,10 +12,10 @@ type Broker interface {
Init(...Option) error Init(...Option) error
Options() Options Options() Options
Address() string Address() string
Connect() error Connect(context.Context) error
Disconnect() error Disconnect(context.Context) error
Publish(topic string, m *Message, opts ...PublishOption) error Publish(context.Context, string, *Message, ...PublishOption) error
Subscribe(topic string, h Handler, opts ...SubscribeOption) (Subscriber, error) Subscribe(context.Context, string, Handler, ...SubscribeOption) (Subscriber, error)
String() string String() string
} }
@ -39,5 +41,5 @@ type Message struct {
type Subscriber interface { type Subscriber interface {
Options() SubscribeOptions Options() SubscribeOptions
Topic() string Topic() string
Unsubscribe() error Unsubscribe(context.Context) error
} }

View File

@ -1,6 +1,8 @@
package broker package broker
type noopBroker struct { import "context"
type NoopBroker struct {
opts Options opts Options
} }
@ -9,7 +11,7 @@ type noopSubscriber struct {
opts SubscribeOptions opts SubscribeOptions
} }
func (n *noopBroker) Init(opts ...Option) error { func (n *NoopBroker) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
} }
@ -17,27 +19,27 @@ func (n *noopBroker) Init(opts ...Option) error {
return nil return nil
} }
func (n *noopBroker) Options() Options { func (n *NoopBroker) Options() Options {
return n.opts return n.opts
} }
func (n *noopBroker) Address() string { func (n *NoopBroker) Address() string {
return "" return ""
} }
func (n *noopBroker) Connect() error { func (n *NoopBroker) Connect(ctx context.Context) error {
return nil return nil
} }
func (n *noopBroker) Disconnect() error { func (n *NoopBroker) Disconnect(ctx context.Context) error {
return nil return nil
} }
func (n *noopBroker) Publish(topic string, m *Message, opts ...PublishOption) error { func (n *NoopBroker) Publish(ctx context.Context, topic string, m *Message, opts ...PublishOption) error {
return nil return nil
} }
func (n *noopBroker) Subscribe(topic string, h Handler, opts ...SubscribeOption) (Subscriber, error) { func (n *NoopBroker) Subscribe(ctx context.Context, topic string, h Handler, opts ...SubscribeOption) (Subscriber, error) {
options := NewSubscribeOptions() options := NewSubscribeOptions()
for _, o := range opts { for _, o := range opts {
@ -47,7 +49,7 @@ func (n *noopBroker) Subscribe(topic string, h Handler, opts ...SubscribeOption)
return &noopSubscriber{topic: topic, opts: options}, nil return &noopSubscriber{topic: topic, opts: options}, nil
} }
func (n *noopBroker) String() string { func (n *NoopBroker) String() string {
return "noop" return "noop"
} }
@ -59,16 +61,6 @@ func (n *noopSubscriber) Topic() string {
return n.topic return n.topic
} }
func (n *noopSubscriber) Unsubscribe() error { func (n *noopSubscriber) Unsubscribe(ctx context.Context) error {
return nil return nil
} }
// newBroker returns a new noop broker
func newBroker(opts ...Option) Broker {
options := NewOptions()
for _, o := range opts {
o(&options)
}
return &noopBroker{opts: options}
}

View File

@ -39,6 +39,12 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
type PublishOptions struct { type PublishOptions struct {
// Other options for implementations of the interface // Other options for implementations of the interface
// can be stored in a context // can be stored in a context

View File

@ -9,7 +9,7 @@ import (
) )
var ( var (
DefaultClient Client = newClient() DefaultClient Client = &NoopClient{opts: NewOptions()}
) )
// Client is the interface used to make requests to services. // Client is the interface used to make requests to services.

View File

@ -2,17 +2,16 @@ package client
import ( import (
"context" "context"
"sync/atomic"
raw "github.com/unistack-org/micro-codec-bytes" raw "github.com/unistack-org/micro-codec-bytes"
"github.com/unistack-org/micro/v3/broker" "github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/codec" "github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/codec/json"
"github.com/unistack-org/micro/v3/errors" "github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/metadata" "github.com/unistack-org/micro/v3/metadata"
) )
type noopClient struct { type NoopClient struct {
once atomic.Value
opts Options opts Options
} }
@ -119,30 +118,30 @@ func (n *noopMessage) ContentType() string {
return n.opts.ContentType return n.opts.ContentType
} }
func (n *noopClient) Init(opts ...Option) error { func (n *NoopClient) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
} }
return nil return nil
} }
func (n *noopClient) Options() Options { func (n *NoopClient) Options() Options {
return n.opts return n.opts
} }
func (n *noopClient) String() string { func (n *NoopClient) String() string {
return "noop" return "noop"
} }
func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error { func (n *NoopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error {
return nil return nil
} }
func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request { func (n *NoopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request {
return &noopRequest{} return &noopRequest{}
} }
func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message { func (n *NoopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message {
options := MessageOptions{} options := MessageOptions{}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
@ -151,25 +150,18 @@ func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOp
return &noopMessage{topic: topic, payload: msg, opts: options} return &noopMessage{topic: topic, payload: msg, opts: options}
} }
func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) { func (n *NoopClient) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) {
return &noopStream{}, nil return &noopStream{}, nil
} }
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error { func (n *NoopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error {
var options PublishOptions
var body []byte var body []byte
// fail early on connect error if err := n.opts.Broker.Connect(ctx); err != nil {
if !n.once.Load().(bool) {
if err := n.opts.Broker.Connect(); err != nil {
return errors.InternalServerError("go.micro.client", err.Error()) return errors.InternalServerError("go.micro.client", err.Error())
} }
n.once.Store(true)
}
for _, o := range opts { options := NewPublishOptions(opts...)
o(&options)
}
md, ok := metadata.FromContext(ctx) md, ok := metadata.FromContext(ctx)
if !ok { if !ok {
@ -182,6 +174,11 @@ func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOpti
if d, ok := p.Payload().(*raw.Frame); ok { if d, ok := p.Payload().(*raw.Frame); ok {
body = d.Data body = d.Data
} else { } else {
cf := n.opts.Broker.Options().Codec
if cf == nil {
cf = json.Marshaler{}
}
/* /*
// use codec for payload // use codec for payload
cf, err := n.opts.Codecs[p.ContentType()] cf, err := n.opts.Codecs[p.ContentType()]
@ -190,7 +187,7 @@ func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOpti
} }
*/ */
// set the body // set the body
b, err := n.opts.Broker.Options().Codec.Marshal(p.Payload()) b, err := cf.Marshal(p.Payload())
if err != nil { if err != nil {
return errors.InternalServerError("go.micro.client", err.Error()) return errors.InternalServerError("go.micro.client", err.Error())
} }
@ -204,7 +201,7 @@ func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOpti
topic = options.Exchange topic = options.Exchange
} }
return n.opts.Broker.Publish(topic, &broker.Message{ return n.opts.Broker.Publish(ctx, topic, &broker.Message{
Header: md, Header: md,
Body: body, Body: body,
}, broker.PublishContext(options.Context)) }, broker.PublishContext(options.Context))
@ -217,5 +214,5 @@ func newClient(opts ...Option) Client {
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
return &noopClient{opts: options} return &NoopClient{opts: options}
} }

View File

@ -7,11 +7,11 @@ import (
"github.com/unistack-org/micro/v3/broker" "github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/codec" "github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/registry" "github.com/unistack-org/micro/v3/registry"
"github.com/unistack-org/micro/v3/router" "github.com/unistack-org/micro/v3/router"
"github.com/unistack-org/micro/v3/selector" "github.com/unistack-org/micro/v3/selector"
"github.com/unistack-org/micro/v3/selector/random" "github.com/unistack-org/micro/v3/selector/random"
"github.com/unistack-org/micro/v3/network/transport"
) )
type Options struct { type Options struct {
@ -48,6 +48,14 @@ type Options struct {
Context context.Context Context context.Context
} }
func NewCallOptions(opts ...CallOption) CallOptions {
options := CallOptions{}
for _, o := range opts {
o(&options)
}
return options
}
type CallOptions struct { type CallOptions struct {
// Address of remote hosts // Address of remote hosts
Address []string Address []string
@ -84,6 +92,20 @@ type CallOptions struct {
Context context.Context Context context.Context
} }
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
func NewPublishOptions(opts ...PublishOption) PublishOptions {
options := PublishOptions{}
for _, o := range opts {
o(&options)
}
return options
}
type PublishOptions struct { type PublishOptions struct {
// Exchange is the routing exchange for the message // Exchange is the routing exchange for the message
Exchange string Exchange string
@ -92,10 +114,26 @@ type PublishOptions struct {
Context context.Context Context context.Context
} }
func NewMessageOptions(opts ...MessageOption) MessageOptions {
options := MessageOptions{}
for _, o := range opts {
o(&options)
}
return options
}
type MessageOptions struct { type MessageOptions struct {
ContentType string ContentType string
} }
func NewRequestOptions(opts ...RequestOption) RequestOptions {
options := RequestOptions{}
for _, o := range opts {
o(&options)
}
return options
}
type RequestOptions struct { type RequestOptions struct {
ContentType string ContentType string
Stream bool Stream bool

View File

@ -25,12 +25,13 @@ func (c *Codec) ReadBody(b interface{}) error {
if b == nil { if b == nil {
return nil return nil
} }
if pb, ok := b.(proto.Message); ok { switch m := b.(type) {
case proto.Message:
buf, err := ioutil.ReadAll(c.Conn) buf, err := ioutil.ReadAll(c.Conn)
if err != nil { if err != nil {
return err return err
} }
return jsonpb.Unmarshal(buf, pb) return jsonpb.Unmarshal(buf, m)
} }
return c.Decoder.Decode(b) return c.Decoder.Decode(b)
} }

View File

@ -11,20 +11,20 @@ type Profile interface {
} }
var ( var (
DefaultProfile Profile = new(noop) DefaultProfile Profile = &NoopProfile{}
) )
type noop struct{} type NoopProfile struct{}
func (p *noop) Start() error { func (p *NoopProfile) Start() error {
return nil return nil
} }
func (p *noop) Stop() error { func (p *NoopProfile) Stop() error {
return nil return nil
} }
func (p *noop) String() string { func (p *NoopProfile) String() string {
return "noop" return "noop"
} }

View File

@ -9,7 +9,7 @@ var (
// Logger is a generic logging interface // Logger is a generic logging interface
type Logger interface { type Logger interface {
// Init initialises options // Init initialises options
Init(options ...Option) error Init(opts ...Option) error
// V compare provided verbosity level with current log level // V compare provided verbosity level with current log level
V(level Level) bool V(level Level) bool
// The Logger options // The Logger options

View File

@ -20,6 +20,14 @@ type Options struct {
Context context.Context Context context.Context
} }
func NewOptions(opts ...Option) Options {
options := Options{}
for _, o := range opts {
o(&options)
}
return options
}
// WithFields set default fields for the logger // WithFields set default fields for the logger
func WithFields(fields map[string]interface{}) Option { func WithFields(fields map[string]interface{}) Option {
return func(args *Options) { return func(args *Options) {

View File

@ -4,6 +4,7 @@ package micro
import ( import (
"context" "context"
"github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/client" "github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/server" "github.com/unistack-org/micro/v3/server"
) )
@ -17,13 +18,15 @@ type Service interface {
// The service name // The service name
Name() string Name() string
// Init initialises options // Init initialises options
Init(...Option) Init(...Option) error
// Options returns the current options // Options returns the current options
Options() Options Options() Options
// Client is used to call services // Client is used to call services
Client() client.Client Client() client.Client
// Server is for handling requests and events // Server is for handling requests and events
Server() server.Server Server() server.Server
// Broker is for broker usage
Broker() broker.Broker
// Run the service // Run the service
Run() error Run() error
// The service implementation // The service implementation

70
network/transport/noop.go Normal file
View File

@ -0,0 +1,70 @@
package transport
type NoopTransport struct {
opts Options
}
func (t *NoopTransport) Init(opts ...Option) error {
for _, o := range opts {
o(&t.opts)
}
return nil
}
func (t *NoopTransport) Options() Options {
return t.opts
}
func (t *NoopTransport) Dial(addr string, opts ...DialOption) (Client, error) {
options := NewDialOptions(opts...)
return &noopClient{opts: options}, nil
}
func (t *NoopTransport) Listen(addr string, opts ...ListenOption) (Listener, error) {
options := NewListenOptions(opts...)
return &noopListener{opts: options}, nil
}
func (t *NoopTransport) String() string {
return "noop"
}
type noopClient struct {
opts DialOptions
}
func (c *noopClient) Close() error {
return nil
}
func (c *noopClient) Local() string {
return ""
}
func (c *noopClient) Remote() string {
return ""
}
func (c *noopClient) Recv(*Message) error {
return nil
}
func (c *noopClient) Send(*Message) error {
return nil
}
type noopListener struct {
opts ListenOptions
}
func (l *noopListener) Addr() string {
return ""
}
func (l *noopListener) Accept(fn func(Socket)) error {
return nil
}
func (l *noopListener) Close() error {
return nil
}

View File

@ -106,6 +106,12 @@ func Logger(l logger.Logger) Option {
} }
} }
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Codec sets the codec used for encoding where the transport // Codec sets the codec used for encoding where the transport
// does not support message headers // does not support message headers
func Codec(c codec.Marshaler) Option { func Codec(c codec.Marshaler) Option {

View File

@ -6,7 +6,7 @@ import (
) )
var ( var (
DefaultTransport Transport DefaultTransport Transport = &NoopTransport{opts: NewOptions()}
) )
// Transport is an interface which is used for communication between // Transport is an interface which is used for communication between

View File

@ -49,15 +49,15 @@ func (t *tunBroker) Address() string {
return t.tunnel.Address() return t.tunnel.Address()
} }
func (t *tunBroker) Connect() error { func (t *tunBroker) Connect(ctx context.Context) error {
return t.tunnel.Connect() return t.tunnel.Connect()
} }
func (t *tunBroker) Disconnect() error { func (t *tunBroker) Disconnect(ctx context.Context) error {
return t.tunnel.Close() return t.tunnel.Close()
} }
func (t *tunBroker) Publish(topic string, m *broker.Message, opts ...broker.PublishOption) error { func (t *tunBroker) Publish(ctx context.Context, topic string, m *broker.Message, opts ...broker.PublishOption) error {
// TODO: this is probably inefficient, we might want to just maintain an open connection // TODO: this is probably inefficient, we might want to just maintain an open connection
// it may be easier to add broadcast to the tunnel // it may be easier to add broadcast to the tunnel
c, err := t.tunnel.Dial(topic, tunnel.DialMode(tunnel.Multicast)) c, err := t.tunnel.Dial(topic, tunnel.DialMode(tunnel.Multicast))
@ -72,21 +72,16 @@ func (t *tunBroker) Publish(topic string, m *broker.Message, opts ...broker.Publ
}) })
} }
func (t *tunBroker) Subscribe(topic string, h broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) { func (t *tunBroker) Subscribe(ctx context.Context, topic string, h broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
l, err := t.tunnel.Listen(topic, tunnel.ListenMode(tunnel.Multicast)) l, err := t.tunnel.Listen(topic, tunnel.ListenMode(tunnel.Multicast))
if err != nil { if err != nil {
return nil, err return nil, err
} }
var options broker.SubscribeOptions
for _, o := range opts {
o(&options)
}
tunSub := &tunSubscriber{ tunSub := &tunSubscriber{
topic: topic, topic: topic,
handler: h, handler: h,
opts: options, opts: broker.NewSubscribeOptions(opts...),
closed: make(chan bool), closed: make(chan bool),
listener: l, listener: l,
} }
@ -150,7 +145,7 @@ func (t *tunSubscriber) Topic() string {
return t.topic return t.topic
} }
func (t *tunSubscriber) Unsubscribe() error { func (t *tunSubscriber) Unsubscribe(ctx context.Context) error {
select { select {
case <-t.closed: case <-t.closed:
return nil return nil
@ -177,12 +172,7 @@ func (t *tunEvent) Error() error {
} }
func NewBroker(opts ...broker.Option) (broker.Broker, error) { func NewBroker(opts ...broker.Option) (broker.Broker, error) {
options := broker.Options{ options := broker.NewOptions(opts...)
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
t, ok := options.Context.Value(tunnelKey{}).(tunnel.Tunnel) t, ok := options.Context.Value(tunnelKey{}).(tunnel.Tunnel)
if !ok { if !ok {

View File

@ -1,53 +1,59 @@
package registry package registry
import "fmt" import (
"context"
"fmt"
)
type noopRegistry struct { type NoopRegistry struct {
opts Options opts Options
} }
func (n *noopRegistry) Init(opts ...Option) error { func (n *NoopRegistry) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
} }
return nil return nil
} }
func (n *noopRegistry) Options() Options { func (n *NoopRegistry) Options() Options {
return n.opts return n.opts
} }
func (n *noopRegistry) Register(*Service, ...RegisterOption) error { func (n *NoopRegistry) Connect(ctx context.Context) error {
return nil return nil
} }
func (n *noopRegistry) Deregister(*Service, ...DeregisterOption) error { func (n *NoopRegistry) Disconnect(ctx context.Context) error {
return nil return nil
} }
func (n *noopRegistry) GetService(string, ...GetOption) ([]*Service, error) { func (n *NoopRegistry) Register(*Service, ...RegisterOption) error {
return nil
}
func (n *NoopRegistry) Deregister(*Service, ...DeregisterOption) error {
return nil
}
func (n *NoopRegistry) GetService(string, ...GetOption) ([]*Service, error) {
return []*Service{}, nil return []*Service{}, nil
} }
func (n *noopRegistry) ListServices(...ListOption) ([]*Service, error) { func (n *NoopRegistry) ListServices(...ListOption) ([]*Service, error) {
return []*Service{}, nil return []*Service{}, nil
} }
func (n *noopRegistry) Watch(...WatchOption) (Watcher, error) { func (n *NoopRegistry) Watch(...WatchOption) (Watcher, error) {
return nil, fmt.Errorf("not implemented") return nil, fmt.Errorf("not implemented")
} }
func (n *noopRegistry) String() string { func (n *NoopRegistry) String() string {
return "noop" return "noop"
} }
// newRegistry returns a new noop registry // NewRegistry returns a new noop registry
func newRegistry(opts ...Option) Registry { func NewRegistry(opts ...Option) Registry {
options := NewOptions() options := NewOptions(opts...)
return &NoopRegistry{opts: options}
for _, o := range opts {
o(&options)
}
return &noopRegistry{opts: options}
} }

View File

@ -37,6 +37,8 @@ type RegisterOptions struct {
Context context.Context Context context.Context
// Domain to register the service in // Domain to register the service in
Domain string Domain string
// Attempts specify attempts for register
Attempts int
} }
type WatchOptions struct { type WatchOptions struct {
@ -54,6 +56,8 @@ type DeregisterOptions struct {
Context context.Context Context context.Context
// Domain the service was registered in // Domain the service was registered in
Domain string Domain string
// Atempts specify max attempts for deregister
Attempts int
} }
type GetOptions struct { type GetOptions struct {
@ -95,6 +99,13 @@ func Logger(l logger.Logger) Option {
} }
} }
// Context sets the context
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Specify TLS Config // Specify TLS Config
func TLSConfig(t *tls.Config) Option { func TLSConfig(t *tls.Config) Option {
return func(o *Options) { return func(o *Options) {
@ -102,6 +113,12 @@ func TLSConfig(t *tls.Config) Option {
} }
} }
func RegisterAttempts(t int) RegisterOption {
return func(o *RegisterOptions) {
o.Attempts = t
}
}
func RegisterTTL(t time.Duration) RegisterOption { func RegisterTTL(t time.Duration) RegisterOption {
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
o.TTL = t o.TTL = t
@ -139,6 +156,12 @@ func WatchDomain(d string) WatchOption {
} }
} }
func DeregisterTimeout(t int) DeregisterOption {
return func(o *DeregisterOptions) {
o.Attempts = t
}
}
func DeregisterContext(ctx context.Context) DeregisterOption { func DeregisterContext(ctx context.Context) DeregisterOption {
return func(o *DeregisterOptions) { return func(o *DeregisterOptions) {
o.Context = ctx o.Context = ctx

View File

@ -2,6 +2,7 @@
package registry package registry
import ( import (
"context"
"errors" "errors"
) )
@ -13,7 +14,7 @@ const (
) )
var ( var (
DefaultRegistry Registry = newRegistry() DefaultRegistry Registry = NewRegistry()
// ErrNotFound returned when GetService is called and no services found // ErrNotFound returned when GetService is called and no services found
ErrNotFound = errors.New("service not found") ErrNotFound = errors.New("service not found")
// ErrWatcherStopped returned when when watcher is stopped // ErrWatcherStopped returned when when watcher is stopped
@ -26,6 +27,8 @@ var (
type Registry interface { type Registry interface {
Init(...Option) error Init(...Option) error
Options() Options Options() Options
Connect(context.Context) error
Disconnect(context.Context) error
Register(*Service, ...RegisterOption) error Register(*Service, ...RegisterOption) error
Deregister(*Service, ...DeregisterOption) error Deregister(*Service, ...DeregisterOption) error
GetService(string, ...GetOption) ([]*Service, error) GetService(string, ...GetOption) ([]*Service, error)

View File

@ -65,7 +65,7 @@ func QueryLink(link string) QueryOption {
// NewQuery creates new query and returns it // NewQuery creates new query and returns it
func NewQuery(opts ...QueryOption) QueryOptions { func NewQuery(opts ...QueryOption) QueryOptions {
// default options // default options
qopts := QueryOptions{ options := QueryOptions{
Service: "*", Service: "*",
Address: "*", Address: "*",
Gateway: "*", Gateway: "*",
@ -75,8 +75,8 @@ func NewQuery(opts ...QueryOption) QueryOptions {
} }
for _, o := range opts { for _, o := range opts {
o(&qopts) o(&options)
} }
return qopts return options
} }

View File

@ -1,94 +1,59 @@
package server package server
import "context" import (
"reflect"
type HandlerOption func(*HandlerOptions) "github.com/unistack-org/micro/v3/registry"
)
type HandlerOptions struct { type rpcHandler struct {
Internal bool name string
Metadata map[string]map[string]string handler interface{}
Context context.Context endpoints []*registry.Endpoint
opts HandlerOptions
} }
func NewHandlerOptions(opts ...HandlerOption) HandlerOptions { func newRpcHandler(handler interface{}, opts ...HandlerOption) Handler {
options := HandlerOptions{ options := NewHandlerOptions(opts...)
Context: context.Background(),
typ := reflect.TypeOf(handler)
hdlr := reflect.ValueOf(handler)
name := reflect.Indirect(hdlr).Type().Name()
var endpoints []*registry.Endpoint
for m := 0; m < typ.NumMethod(); m++ {
if e := registry.ExtractEndpoint(typ.Method(m)); e != nil {
e.Name = name + "." + e.Name
for k, v := range options.Metadata[e.Name] {
e.Metadata[k] = v
} }
for _, o := range opts { endpoints = append(endpoints, e)
o(&options)
}
return options
}
type SubscriberOption func(*SubscriberOptions)
type SubscriberOptions struct {
// AutoAck defaults to true. When a handler returns
// with a nil error the message is acked.
AutoAck bool
Queue string
Internal bool
Context context.Context
}
func NewSubscriberOptions(opts ...SubscriberOption) SubscriberOptions {
options := SubscriberOptions{
AutoAck: true,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// EndpointMetadata is a Handler option that allows metadata to be added to
// individual endpoints.
func EndpointMetadata(name string, md map[string]string) HandlerOption {
return func(o *HandlerOptions) {
o.Metadata[name] = md
} }
} }
// Internal Handler options specifies that a handler is not advertised return &rpcHandler{
// to the discovery system. In the future this may also limit request name: name,
// to the internal network or authorised user. handler: handler,
func InternalHandler(b bool) HandlerOption { endpoints: endpoints,
return func(o *HandlerOptions) { opts: options,
o.Internal = b
} }
} }
// Internal Subscriber options specifies that a subscriber is not advertised func (r *rpcHandler) Name() string {
// to the discovery system. return r.name
func InternalSubscriber(b bool) SubscriberOption {
return func(o *SubscriberOptions) {
o.Internal = b
}
} }
// DisableAutoAck will disable auto acking of messages func (r *rpcHandler) Handler() interface{} {
// after they have been handled. return r.handler
func DisableAutoAck() SubscriberOption {
return func(o *SubscriberOptions) {
o.AutoAck = false
}
} }
// Shared queue name distributed messages across subscribers func (r *rpcHandler) Endpoints() []*registry.Endpoint {
func SubscriberQueue(n string) SubscriberOption { return r.endpoints
return func(o *SubscriberOptions) {
o.Queue = n
}
} }
// SubscriberContext set context options to allow broker SubscriberOption passed func (r *rpcHandler) Options() HandlerOptions {
func SubscriberContext(ctx context.Context) SubscriberOption { return r.opts
return func(o *SubscriberOptions) {
o.Context = ctx
}
} }

View File

@ -1,108 +1,462 @@
package server package server
import "github.com/unistack-org/micro/v3/registry" import (
"bytes"
"fmt"
"sort"
"sync"
"time"
type noopServer struct { craw "github.com/unistack-org/micro-codec-bytes"
"github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/codec"
cjson "github.com/unistack-org/micro/v3/codec/json"
cjsonrpc "github.com/unistack-org/micro/v3/codec/jsonrpc"
cproto "github.com/unistack-org/micro/v3/codec/proto"
cprotorpc "github.com/unistack-org/micro/v3/codec/protorpc"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/registry"
)
var (
DefaultCodecs = map[string]codec.NewCodec{
"application/json": cjson.NewCodec,
"application/json-rpc": cjsonrpc.NewCodec,
"application/protobuf": cproto.NewCodec,
"application/proto-rpc": cprotorpc.NewCodec,
"application/octet-stream": craw.NewCodec,
}
)
const (
defaultContentType = "application/json"
)
type NoopServer struct {
h Handler h Handler
opts Options opts Options
rsvc *registry.Service
handlers map[string]Handler
subscribers map[*subscriber][]broker.Subscriber
registered bool
started bool
exit chan chan error
wg *sync.WaitGroup
sync.RWMutex
} }
type noopHandler struct { func (n *NoopServer) newCodec(contentType string) (codec.NewCodec, error) {
opts HandlerOptions if cf, ok := n.opts.Codecs[contentType]; ok {
h interface{} return cf, nil
}
if cf, ok := DefaultCodecs[contentType]; ok {
return cf, nil
}
return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
} }
type noopSubscriber struct { func (n *NoopServer) Handle(handler Handler) error {
topic string
opts SubscriberOptions
h interface{}
}
func (n *noopSubscriber) Topic() string {
return n.topic
}
func (n *noopSubscriber) Subscriber() interface{} {
return n.h
}
func (n *noopSubscriber) Endpoints() []*registry.Endpoint {
return nil
}
func (n *noopSubscriber) Options() SubscriberOptions {
return n.opts
}
func (n *noopHandler) Endpoints() []*registry.Endpoint {
return nil
}
func (n *noopHandler) Handler() interface{} {
return nil
}
func (n *noopHandler) Options() HandlerOptions {
return n.opts
}
func (n *noopHandler) Name() string {
return "noop"
}
func (n *noopServer) Handle(handler Handler) error {
n.h = handler n.h = handler
return nil return nil
} }
func (n *noopServer) Subscribe(subscriber Subscriber) error { func (n *NoopServer) Subscribe(sb Subscriber) error {
// n.s = handler sub, ok := sb.(*subscriber)
if !ok {
return fmt.Errorf("invalid subscriber: expected *subscriber")
}
if len(sub.handlers) == 0 {
return fmt.Errorf("invalid subscriber: no handler functions")
}
if err := ValidateSubscriber(sb); err != nil {
return err
}
n.Lock()
if _, ok = n.subscribers[sub]; ok {
n.Unlock()
return fmt.Errorf("subscriber %v already exists", sub)
}
n.subscribers[sub] = nil
n.Unlock()
return nil return nil
} }
func (n *noopServer) NewHandler(h interface{}, opts ...HandlerOption) Handler { func (n *NoopServer) NewHandler(h interface{}, opts ...HandlerOption) Handler {
options := NewHandlerOptions() return newRpcHandler(h, opts...)
for _, o := range opts {
o(&options)
}
return &noopHandler{opts: options, h: h}
} }
func (n *noopServer) NewSubscriber(topic string, h interface{}, opts ...SubscriberOption) Subscriber { func (n *NoopServer) NewSubscriber(topic string, sb interface{}, opts ...SubscriberOption) Subscriber {
options := NewSubscriberOptions() return newSubscriber(topic, sb, opts...)
for _, o := range opts {
o(&options)
}
return &noopSubscriber{topic: topic, opts: options, h: h}
} }
func (n *noopServer) Init(opts ...Option) error { func (n *NoopServer) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
} }
if n.handlers == nil {
n.handlers = make(map[string]Handler)
}
if n.subscribers == nil {
n.subscribers = make(map[*subscriber][]broker.Subscriber)
}
if n.exit == nil {
n.exit = make(chan chan error)
}
return nil return nil
} }
func (n *noopServer) Start() error { func (n *NoopServer) Options() Options {
return nil
}
func (n *noopServer) Stop() error {
return nil
}
func (n *noopServer) Options() Options {
return n.opts return n.opts
} }
func (n *noopServer) String() string { func (n *NoopServer) String() string {
return "noop" return "noop"
} }
func newServer(opts ...Option) Server { func (n *NoopServer) Register() error {
options := NewOptions() n.RLock()
for _, o := range opts { rsvc := n.rsvc
o(&options) config := n.opts
n.RUnlock()
// if service already filled, reuse it and return early
if rsvc != nil {
if err := DefaultRegisterFunc(rsvc, config); err != nil {
return err
} }
return &noopServer{opts: options} return nil
}
var err error
var service *registry.Service
var cacheService bool
service, err = NewRegistryService(n)
if err != nil {
return err
}
n.RLock()
// Maps are ordered randomly, sort the keys for consistency
var handlerList []string
for n, e := range n.handlers {
// Only advertise non internal handlers
if !e.Options().Internal {
handlerList = append(handlerList, n)
}
}
sort.Strings(handlerList)
var subscriberList []*subscriber
for e := range n.subscribers {
// Only advertise non internal subscribers
if !e.Options().Internal {
subscriberList = append(subscriberList, e)
}
}
sort.Slice(subscriberList, func(i, j int) bool {
return subscriberList[i].topic > subscriberList[j].topic
})
endpoints := make([]*registry.Endpoint, 0, len(handlerList)+len(subscriberList))
for _, h := range handlerList {
endpoints = append(endpoints, n.handlers[h].Endpoints()...)
}
for _, e := range subscriberList {
endpoints = append(endpoints, e.Endpoints()...)
}
n.RUnlock()
service.Nodes[0].Metadata["protocol"] = "noop"
service.Nodes[0].Metadata["transport"] = "noop"
service.Endpoints = endpoints
n.RLock()
registered := n.registered
n.RUnlock()
if !registered {
if logger.V(logger.InfoLevel) {
logger.Infof("Registry [%s] Registering node: %s", config.Registry.String(), service.Nodes[0].Id)
}
}
// register the service
if err := DefaultRegisterFunc(service, config); err != nil {
return err
}
// already registered? don't need to register subscribers
if registered {
return nil
}
n.Lock()
defer n.Unlock()
cx := config.Context
for sb := range n.subscribers {
handler := n.createSubHandler(sb, config)
var opts []broker.SubscribeOption
if queue := sb.Options().Queue; len(queue) > 0 {
opts = append(opts, broker.SubscribeGroup(queue))
}
if sb.Options().Context != nil {
cx = sb.Options().Context
}
opts = append(opts, broker.SubscribeContext(cx), broker.SubscribeAutoAck(sb.Options().AutoAck))
if logger.V(logger.InfoLevel) {
logger.Infof("Subscribing to topic: %s", sb.Topic())
}
sub, err := config.Broker.Subscribe(cx, sb.Topic(), handler, opts...)
if err != nil {
return err
}
n.subscribers[sb] = []broker.Subscriber{sub}
}
n.registered = true
if cacheService {
n.rsvc = service
}
return nil
}
func (n *NoopServer) Deregister() error {
var err error
n.RLock()
config := n.opts
n.RUnlock()
service, err := NewRegistryService(n)
if err != nil {
return err
}
if logger.V(logger.InfoLevel) {
logger.Infof("Deregistering node: %s", service.Nodes[0].Id)
}
if err := DefaultDeregisterFunc(service, config); err != nil {
return err
}
n.Lock()
n.rsvc = nil
if !n.registered {
n.Unlock()
return nil
}
n.registered = false
cx := config.Context
wg := sync.WaitGroup{}
for sb, subs := range n.subscribers {
for _, sub := range subs {
if sb.Options().Context != nil {
cx = sb.Options().Context
}
wg.Add(1)
go func(s broker.Subscriber) {
defer wg.Done()
if logger.V(logger.InfoLevel) {
logger.Infof("Unsubscribing from topic: %s", s.Topic())
}
if err := s.Unsubscribe(cx); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Errorf("Unsubscribing from topic: %s err: %v", s.Topic(), err)
}
}
}(sub)
}
n.subscribers[sb] = nil
}
wg.Wait()
n.Unlock()
return nil
}
func (n *NoopServer) Start() error {
n.RLock()
if n.started {
n.RUnlock()
return nil
}
config := n.Options()
n.RUnlock()
if logger.V(logger.InfoLevel) {
logger.Infof("Server [noop] Listening on %s", config.Address)
}
n.Lock()
if len(config.Advertise) == 0 {
config.Advertise = config.Address
}
n.Unlock()
// only connect if we're subscribed
if len(n.subscribers) > 0 {
// connect to the broker
if err := config.Broker.Connect(config.Context); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Errorf("Broker [%s] connect error: %v", config.Broker.String(), err)
}
return err
}
if logger.V(logger.InfoLevel) {
logger.Infof("Broker [%s] Connected to %s", config.Broker.String(), config.Broker.Address())
}
}
// use RegisterCheck func before register
if err := config.RegisterCheck(config.Context); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Errorf("Server %s-%s register check error: %s", config.Name, config.Id, err)
}
} else {
// announce self to the world
if err := n.Register(); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Errorf("Server register error: %v", err)
}
}
}
go func() {
t := new(time.Ticker)
// only process if it exists
if config.RegisterInterval > time.Duration(0) {
// new ticker
t = time.NewTicker(config.RegisterInterval)
}
// return error chan
var ch chan error
Loop:
for {
select {
// register self on interval
case <-t.C:
n.RLock()
registered := n.registered
n.RUnlock()
rerr := config.RegisterCheck(config.Context)
if rerr != nil && registered {
if logger.V(logger.ErrorLevel) {
logger.Errorf("Server %s-%s register check error: %s, deregister it", config.Name, config.Id, rerr)
}
// deregister self in case of error
if err := n.Deregister(); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Errorf("Server %s-%s deregister error: %s", config.Name, config.Id, err)
}
}
} else if rerr != nil && !registered {
if logger.V(logger.ErrorLevel) {
logger.Errorf("Server %s-%s register check error: %s", config.Name, config.Id, rerr)
}
continue
}
if err := n.Register(); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Errorf("Server %s-%s register error: %s", config.Name, config.Id, err)
}
}
// wait for exit
case ch = <-n.exit:
break Loop
}
}
// deregister self
if err := n.Deregister(); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error("Server deregister error: ", err)
}
}
// wait for waitgroup
if n.wg != nil {
n.wg.Wait()
}
// stop the grpc server
exit := make(chan bool)
go func() {
close(exit)
}()
select {
case <-exit:
}
// close transport
ch <- nil
if logger.V(logger.InfoLevel) {
logger.Infof("Broker [%s] Disconnected from %s", config.Broker.String(), config.Broker.Address())
}
// disconnect broker
if err := config.Broker.Disconnect(config.Context); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Errorf("Broker [%s] disconnect error: %v", config.Broker.String(), err)
}
}
}()
// mark the server as started
n.Lock()
n.started = true
n.Unlock()
return nil
}
func (n *NoopServer) Stop() error {
n.RLock()
if !n.started {
n.RUnlock()
return nil
}
n.RUnlock()
ch := make(chan error)
n.exit <- ch
err := <-ch
n.Lock()
n.rsvc = nil
n.started = false
n.Unlock()
return err
}
type noopcodec struct {
*bytes.Buffer
}
func (c noopcodec) Close() error {
return nil
} }

View File

@ -39,6 +39,10 @@ type Options struct {
RegisterTTL time.Duration RegisterTTL time.Duration
// The interval on which to register // The interval on which to register
RegisterInterval time.Duration RegisterInterval time.Duration
// RegisterAttempts specify how many times try to register
RegisterAttempts int
// DeegisterAttempts specify how many times try to deregister
DeregisterAttempts int
// The router for requests // The router for requests
Router Router Router Router
@ -61,6 +65,8 @@ func NewOptions(opts ...Option) Options {
RegisterInterval: DefaultRegisterInterval, RegisterInterval: DefaultRegisterInterval,
RegisterTTL: DefaultRegisterTTL, RegisterTTL: DefaultRegisterTTL,
RegisterCheck: DefaultRegisterCheck, RegisterCheck: DefaultRegisterCheck,
Logger: logger.DefaultLogger,
Tracer: tracer.DefaultTracer,
Broker: broker.DefaultBroker, Broker: broker.DefaultBroker,
Registry: registry.DefaultRegistry, Registry: registry.DefaultRegistry,
Address: DefaultAddress, Address: DefaultAddress,
@ -255,3 +261,94 @@ func WrapSubscriber(w SubscriberWrapper) Option {
o.SubWrappers = append(o.SubWrappers, w) o.SubWrappers = append(o.SubWrappers, w)
} }
} }
type HandlerOption func(*HandlerOptions)
type HandlerOptions struct {
Internal bool
Metadata map[string]map[string]string
Context context.Context
}
func NewHandlerOptions(opts ...HandlerOption) HandlerOptions {
options := HandlerOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
type SubscriberOption func(*SubscriberOptions)
type SubscriberOptions struct {
// AutoAck defaults to true. When a handler returns
// with a nil error the message is acked.
AutoAck bool
Queue string
Internal bool
Context context.Context
}
func NewSubscriberOptions(opts ...SubscriberOption) SubscriberOptions {
options := SubscriberOptions{
AutoAck: true,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// EndpointMetadata is a Handler option that allows metadata to be added to
// individual endpoints.
func EndpointMetadata(name string, md map[string]string) HandlerOption {
return func(o *HandlerOptions) {
o.Metadata[name] = md
}
}
// Internal Handler options specifies that a handler is not advertised
// to the discovery system. In the future this may also limit request
// to the internal network or authorised user.
func InternalHandler(b bool) HandlerOption {
return func(o *HandlerOptions) {
o.Internal = b
}
}
// Internal Subscriber options specifies that a subscriber is not advertised
// to the discovery system.
func InternalSubscriber(b bool) SubscriberOption {
return func(o *SubscriberOptions) {
o.Internal = b
}
}
// DisableAutoAck will disable auto acking of messages
// after they have been handled.
func DisableAutoAck() SubscriberOption {
return func(o *SubscriberOptions) {
o.AutoAck = false
}
}
// Shared queue name distributed messages across subscribers
func SubscriberQueue(n string) SubscriberOption {
return func(o *SubscriberOptions) {
o.Queue = n
}
}
// SubscriberContext set context options to allow broker SubscriberOption passed
func SubscriberContext(ctx context.Context) SubscriberOption {
return func(o *SubscriberOptions) {
o.Context = ctx
}
}

89
server/registry.go Normal file
View File

@ -0,0 +1,89 @@
package server
import (
"net"
"time"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/registry"
"github.com/unistack-org/micro/v3/util/addr"
"github.com/unistack-org/micro/v3/util/backoff"
)
var (
// DefaultRegisterFunc uses backoff to register service
DefaultRegisterFunc = func(service *registry.Service, config Options) error {
var err error
opts := []registry.RegisterOption{
registry.RegisterTTL(config.RegisterTTL),
registry.RegisterDomain(config.Namespace),
}
for i := 0; i <= config.RegisterAttempts; i++ {
err = config.Registry.Register(service, opts...)
if err == nil {
break
}
// backoff then retry
time.Sleep(backoff.Do(i + 1))
continue
}
return err
}
// DefaultDeregisterFunc uses backoff to deregister service
DefaultDeregisterFunc = func(service *registry.Service, config Options) error {
var err error
opts := []registry.DeregisterOption{
registry.DeregisterDomain(config.Namespace),
}
for i := 0; i <= config.DeregisterAttempts; i++ {
err = config.Registry.Deregister(service, opts...)
if err == nil {
break
}
// backoff then retry
time.Sleep(backoff.Do(i + 1))
continue
}
return err
}
)
func NewRegistryService(s Server) (*registry.Service, error) {
opts := s.Options()
advt := opts.Address
if len(opts.Advertise) > 0 {
advt = opts.Advertise
}
host, port, err := net.SplitHostPort(advt)
if err != nil {
return nil, err
}
addr, err := addr.Extract(host)
if err != nil {
addr = host
}
node := &registry.Node{
Id: opts.Name + "-" + opts.Id,
Address: net.JoinHostPort(addr, port),
}
node.Metadata = metadata.Copy(opts.Metadata)
node.Metadata["server"] = s.String()
node.Metadata["broker"] = opts.Broker.String()
node.Metadata["registry"] = opts.Registry.String()
return &registry.Service{
Name: opts.Name,
Version: opts.Version,
Nodes: []*registry.Node{node},
Metadata: metadata.New(0),
}, nil
}

90
server/request.go Normal file
View File

@ -0,0 +1,90 @@
package server
import (
raw "github.com/unistack-org/micro-codec-bytes"
"github.com/unistack-org/micro/v3/codec"
)
type rpcRequest struct {
service string
method string
contentType string
codec codec.Codec
header map[string]string
body []byte
stream bool
payload interface{}
}
type rpcMessage struct {
topic string
contentType string
payload interface{}
header map[string]string
body []byte
codec codec.Codec
}
func (r *rpcRequest) ContentType() string {
return r.contentType
}
func (r *rpcRequest) Service() string {
return r.service
}
func (r *rpcRequest) Method() string {
return r.method
}
func (r *rpcRequest) Endpoint() string {
return r.method
}
func (r *rpcRequest) Codec() codec.Reader {
return r.codec
}
func (r *rpcRequest) Header() map[string]string {
return r.header
}
func (r *rpcRequest) Read() ([]byte, error) {
f := &raw.Frame{}
if err := r.codec.ReadBody(f); err != nil {
return nil, err
}
return f.Data, nil
}
func (r *rpcRequest) Stream() bool {
return r.stream
}
func (r *rpcRequest) Body() interface{} {
return r.payload
}
func (r *rpcMessage) ContentType() string {
return r.contentType
}
func (r *rpcMessage) Topic() string {
return r.topic
}
func (r *rpcMessage) Payload() interface{} {
return r.payload
}
func (r *rpcMessage) Header() map[string]string {
return r.header
}
func (r *rpcMessage) Body() []byte {
return r.body
}
func (r *rpcMessage) Codec() codec.Reader {
return r.codec
}

View File

@ -11,7 +11,7 @@ import (
) )
var ( var (
DefaultServer Server = newServer() DefaultServer Server = &NoopServer{opts: NewOptions()}
) )
// Server is a simple micro server abstraction // Server is a simple micro server abstraction

View File

@ -1,10 +1,20 @@
package server package server
import ( import (
"bytes"
"context"
"fmt" "fmt"
"reflect" "reflect"
"runtime/debug"
"strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/registry"
) )
const ( const (
@ -17,6 +27,22 @@ var (
typeOfError = reflect.TypeOf((*error)(nil)).Elem() typeOfError = reflect.TypeOf((*error)(nil)).Elem()
) )
type handler struct {
method reflect.Value
reqType reflect.Type
ctxType reflect.Type
}
type subscriber struct {
topic string
rcvr reflect.Value
typ reflect.Type
subscriber interface{}
handlers []*handler
endpoints []*registry.Endpoint
opts SubscriberOptions
}
// Is this an exported - upper case - name? // Is this an exported - upper case - name?
func isExported(name string) bool { func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name) rune, _ := utf8.DecodeRuneInString(name)
@ -86,3 +112,200 @@ func ValidateSubscriber(sub Subscriber) error {
return nil return nil
} }
func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
var endpoints []*registry.Endpoint
var handlers []*handler
options := NewSubscriberOptions(opts...)
if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
h := &handler{
method: reflect.ValueOf(sub),
}
switch typ.NumIn() {
case 1:
h.reqType = typ.In(0)
case 2:
h.ctxType = typ.In(0)
h.reqType = typ.In(1)
}
handlers = append(handlers, h)
endpoints = append(endpoints, &registry.Endpoint{
Name: "Func",
Request: registry.ExtractSubValue(typ),
Metadata: map[string]string{
"topic": topic,
"subscriber": "true",
},
})
} else {
hdlr := reflect.ValueOf(sub)
name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
h := &handler{
method: method.Func,
}
switch method.Type.NumIn() {
case 2:
h.reqType = method.Type.In(1)
case 3:
h.ctxType = method.Type.In(1)
h.reqType = method.Type.In(2)
}
handlers = append(handlers, h)
endpoints = append(endpoints, &registry.Endpoint{
Name: name + "." + method.Name,
Request: registry.ExtractSubValue(method.Type),
Metadata: map[string]string{
"topic": topic,
"subscriber": "true",
},
})
}
}
return &subscriber{
rcvr: reflect.ValueOf(sub),
typ: reflect.TypeOf(sub),
topic: topic,
subscriber: sub,
handlers: handlers,
endpoints: endpoints,
opts: options,
}
}
func (n *NoopServer) createSubHandler(sb *subscriber, opts Options) broker.Handler {
return func(p broker.Event) (err error) {
defer func() {
if r := recover(); r != nil {
if logger.V(logger.ErrorLevel) {
logger.Error("panic recovered: ", r)
logger.Error(string(debug.Stack()))
}
err = errors.InternalServerError(n.opts.Name+".subscriber", "panic recovered: %v", r)
}
}()
msg := p.Message()
// if we don't have headers, create empty map
if msg.Header == nil {
msg.Header = make(map[string]string)
}
ct := msg.Header["Content-Type"]
if len(ct) == 0 {
msg.Header["Content-Type"] = defaultContentType
ct = defaultContentType
}
cf, err := n.newCodec(ct)
if err != nil {
return err
}
hdr := make(map[string]string, len(msg.Header))
for k, v := range msg.Header {
hdr[k] = v
}
delete(hdr, "Content-Type")
ctx := metadata.NewContext(sb.opts.Context, hdr)
results := make(chan error, len(sb.handlers))
for i := 0; i < len(sb.handlers); i++ {
handler := sb.handlers[i]
var isVal bool
var req reflect.Value
if handler.reqType.Kind() == reflect.Ptr {
req = reflect.New(handler.reqType.Elem())
} else {
req = reflect.New(handler.reqType)
isVal = true
}
if isVal {
req = req.Elem()
}
if err = cf(noopcodec{bytes.NewBuffer(msg.Body)}).ReadBody(req.Interface()); err != nil {
return err
}
fn := func(ctx context.Context, msg Message) error {
var vals []reflect.Value
if sb.typ.Kind() != reflect.Func {
vals = append(vals, sb.rcvr)
}
if handler.ctxType != nil {
vals = append(vals, reflect.ValueOf(ctx))
}
vals = append(vals, reflect.ValueOf(msg.Payload()))
returnValues := handler.method.Call(vals)
if rerr := returnValues[0].Interface(); rerr != nil {
return rerr.(error)
}
return nil
}
for i := len(opts.SubWrappers); i > 0; i-- {
fn = opts.SubWrappers[i-1](fn)
}
if n.wg != nil {
n.wg.Add(1)
}
go func() {
if n.wg != nil {
defer n.wg.Done()
}
err := fn(ctx, &rpcMessage{
topic: sb.topic,
contentType: ct,
payload: req.Interface(),
header: msg.Header,
body: msg.Body,
})
results <- err
}()
}
var errors []string
for i := 0; i < len(sb.handlers); i++ {
if rerr := <-results; rerr != nil {
errors = append(errors, rerr.Error())
}
}
if len(errors) > 0 {
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
}
return err
}
}
func (s *subscriber) Topic() string {
return s.topic
}
func (s *subscriber) Subscriber() interface{} {
return s.subscriber
}
func (s *subscriber) Endpoints() []*registry.Endpoint {
return s.endpoints
}
func (s *subscriber) Options() SubscriberOptions {
return s.opts
}

View File

@ -5,9 +5,13 @@ import (
"sync" "sync"
cmd "github.com/unistack-org/micro-config-cmd" cmd "github.com/unistack-org/micro-config-cmd"
"github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/client" "github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/registry"
"github.com/unistack-org/micro/v3/server" "github.com/unistack-org/micro/v3/server"
"github.com/unistack-org/micro/v3/store"
) )
type service struct { type service struct {
@ -31,13 +35,12 @@ func (s *service) Name() string {
// Init initialises options. Additionally it calls cmd.Init // Init initialises options. Additionally it calls cmd.Init
// which parses command line flags. cmd.Init is only called // which parses command line flags. cmd.Init is only called
// on first Init. // on first Init.
func (s *service) Init(opts ...Option) { func (s *service) Init(opts ...Option) error {
// process options // process options
for _, o := range opts { for _, o := range opts {
o(&s.opts) o(&s.opts)
} }
s.once.Do(func() {
if s.opts.Cmd != nil { if s.opts.Cmd != nil {
// set cmd name // set cmd name
if len(s.opts.Cmd.App().Name) == 0 { if len(s.opts.Cmd.App().Name) == 0 {
@ -57,58 +60,69 @@ func (s *service) Init(opts ...Option) {
cmd.Store(&s.opts.Store), cmd.Store(&s.opts.Store),
cmd.Profile(&s.opts.Profile), cmd.Profile(&s.opts.Profile),
); err != nil { ); err != nil {
logger.Fatalf("[cmd] init failed: %v", err) return err
} }
} }
})
if s.opts.Registry != nil { if s.opts.Registry != nil {
if err := s.opts.Registry.Init(); err != nil { if err := s.opts.Registry.Init(
logger.Fatalf("[cmd] init failed: %v", err) registry.Context(s.opts.Context),
); err != nil {
return err
} }
} }
if s.opts.Broker != nil { if s.opts.Broker != nil {
if err := s.opts.Broker.Init(); err != nil { if err := s.opts.Broker.Init(
logger.Fatalf("[cmd] init failed: %v", err) broker.Context(s.opts.Context),
); err != nil {
return err
} }
} }
if s.opts.Transport != nil { if s.opts.Transport != nil {
if err := s.opts.Transport.Init(); err != nil { if err := s.opts.Transport.Init(
logger.Fatalf("[cmd] init failed: %v", err) transport.Context(s.opts.Context),
); err != nil {
return err
} }
} }
if s.opts.Store != nil { if s.opts.Store != nil {
if err := s.opts.Store.Init(s.opts.Context); err != nil { if err := s.opts.Store.Init(
logger.Fatalf("[cmd] init failed: %v", err) store.Context(s.opts.Context),
); err != nil {
return err
} }
} }
if s.opts.Server != nil { if s.opts.Server != nil {
if err := s.opts.Server.Init(); err != nil { if err := s.opts.Server.Init(
logger.Fatalf("[cmd] init failed: %v", err) server.Context(s.opts.Context),
); err != nil {
return err
} }
} }
if s.opts.Client != nil { if s.opts.Client != nil {
if err := s.opts.Client.Init(); err != nil { if err := s.opts.Client.Init(
logger.Fatalf("[cmd] init failed: %v", err) client.Context(s.opts.Context),
); err != nil {
return err
} }
} }
// execute the command return nil
// TODO: do this in service.Run()
if err := s.opts.Cmd.Run(); err != nil {
logger.Fatalf("[cmd] run failed: %v", err)
}
} }
func (s *service) Options() Options { func (s *service) Options() Options {
return s.opts return s.opts
} }
func (s *service) Broker() broker.Broker {
return s.opts.Broker
}
func (s *service) Client() client.Client { func (s *service) Client() client.Client {
return s.opts.Client return s.opts.Client
} }
@ -185,6 +199,12 @@ func (s *service) Run() error {
defer s.opts.Profile.Stop() defer s.opts.Profile.Stop()
} }
if s.opts.Cmd != nil {
if err := s.opts.Cmd.Run(); err != nil {
return err
}
}
if err := s.Start(); err != nil { if err := s.Start(); err != nil {
return err return err
} }

View File

@ -2,49 +2,45 @@ package store
import "context" import "context"
type noopStore struct { type NoopStore struct {
opts Options opts Options
} }
func newStore(opts ...Option) Store { func (n *NoopStore) Init(opts ...Option) error {
options := NewOptions()
for _, o := range opts {
o(&options)
}
return &noopStore{opts: options}
}
func (n *noopStore) Init(ctx context.Context, opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
} }
return nil return nil
} }
func (n *noopStore) Options() Options { func (n *NoopStore) Options() Options {
return n.opts return n.opts
} }
func (n *noopStore) String() string { func (n *NoopStore) String() string {
return "noop" return "noop"
} }
func (n *noopStore) Read(ctx context.Context, key string, opts ...ReadOption) ([]*Record, error) { func (n *NoopStore) Read(ctx context.Context, key string, opts ...ReadOption) ([]*Record, error) {
return []*Record{}, nil return []*Record{}, nil
} }
func (n *noopStore) Write(ctx context.Context, r *Record, opts ...WriteOption) error { func (n *NoopStore) Write(ctx context.Context, r *Record, opts ...WriteOption) error {
return nil return nil
} }
func (n *noopStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error { func (n *NoopStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
return nil return nil
} }
func (n *noopStore) List(ctx context.Context, opts ...ListOption) ([]string, error) { func (n *NoopStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
return []string{}, nil return []string{}, nil
} }
func (n *noopStore) Close(ctx context.Context) error { func (n *NoopStore) Connect(ctx context.Context) error {
return nil
}
func (n *NoopStore) Disconnect(ctx context.Context) error {
return nil return nil
} }

View File

@ -37,6 +37,12 @@ func NewOptions(opts ...Option) Options {
// Option sets values in Options // Option sets values in Options
type Option func(o *Options) type Option func(o *Options)
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Logger sets the logger // Logger sets the logger
func Logger(l logger.Logger) Option { func Logger(l logger.Logger) Option {
return func(o *Options) { return func(o *Options) {

View File

@ -11,13 +11,14 @@ import (
var ( var (
// ErrNotFound is returned when a key doesn't exist // ErrNotFound is returned when a key doesn't exist
ErrNotFound = errors.New("not found") ErrNotFound = errors.New("not found")
DefaultStore Store = newStore() DefaultStore Store = &NoopStore{opts: NewOptions()}
) )
// Store is a data storage interface // Store is a data storage interface
type Store interface { type Store interface {
// Init initialises the store. It must perform any required setup on the backing storage implementation and check that it is ready for use, returning any errors. // Init initialises the store. It must perform any required setup on the backing storage implementation and check that it is ready for use, returning any errors.
Init(ctx context.Context, opts ...Option) error Init(opts ...Option) error
Connect(ctx context.Context) error
// Options allows you to view the current options. // Options allows you to view the current options.
Options() Options Options() Options
// Read takes a single key name and optional ReadOptions. It returns matching []*Record or an error. // Read takes a single key name and optional ReadOptions. It returns matching []*Record or an error.
@ -28,8 +29,8 @@ type Store interface {
Delete(ctx context.Context, key string, opts ...DeleteOption) error Delete(ctx context.Context, key string, opts ...DeleteOption) error
// List returns any keys that match, or an empty list with no error if none matched. // List returns any keys that match, or an empty list with no error if none matched.
List(ctx context.Context, opts ...ListOption) ([]string, error) List(ctx context.Context, opts ...ListOption) ([]string, error)
// Close the store // Disconnect the store
Close(ctx context.Context) error Disconnect(ctx context.Context) error
// String returns the name of the implementation. // String returns the name of the implementation.
String() string String() string
} }

36
tracer/context.go Normal file
View File

@ -0,0 +1,36 @@
// Package trace provides an interface for distributed tracing
package tracer
import (
"context"
"github.com/unistack-org/micro/v3/metadata"
)
const (
traceIDKey = "Micro-Trace-Id"
spanIDKey = "Micro-Span-Id"
)
// FromContext returns a span from context
func FromContext(ctx context.Context) (traceID string, parentSpanID string, isFound bool) {
traceID, traceOk := metadata.Get(ctx, traceIDKey)
microID, microOk := metadata.Get(ctx, "Micro-Id")
if !traceOk && !microOk {
isFound = false
return
}
if !traceOk {
traceID = microID
}
parentSpanID, ok := metadata.Get(ctx, spanIDKey)
return traceID, parentSpanID, ok
}
// NewContext saves the trace and span ids in the context
func NewContext(ctx context.Context, traceID, parentSpanID string) context.Context {
return metadata.MergeContext(ctx, map[string]string{
traceIDKey: traceID,
spanIDKey: parentSpanID,
}, true)
}

View File

@ -2,24 +2,24 @@ package tracer
import "context" import "context"
type noop struct{} type NoopTracer struct{}
func (n *noop) Init(...Option) error { func (n *NoopTracer) Init(...Option) error {
return nil return nil
} }
func (n *noop) Start(ctx context.Context, name string) (context.Context, *Span) { func (n *NoopTracer) Start(ctx context.Context, name string) (context.Context, *Span) {
return nil, nil return nil, nil
} }
func (n *noop) Finish(*Span) error { func (n *NoopTracer) Finish(*Span) error {
return nil return nil
} }
func (n *noop) Read(...ReadOption) ([]*Span, error) { func (n *NoopTracer) Read(...ReadOption) ([]*Span, error) {
return nil, nil return nil, nil
} }
func newTracer(opts ...Option) Tracer { func NewTracer(opts ...Option) Tracer {
return &noop{} return &NoopTracer{}
} }

View File

@ -4,12 +4,10 @@ package tracer
import ( import (
"context" "context"
"time" "time"
"github.com/unistack-org/micro/v3/metadata"
) )
var ( var (
DefaultTracer Tracer = newTracer() DefaultTracer Tracer = NewTracer()
) )
// Tracer is an interface for distributed tracing // Tracer is an interface for distributed tracing
@ -51,31 +49,3 @@ type Span struct {
// Type // Type
Type SpanType Type SpanType
} }
const (
traceIDKey = "Micro-Trace-Id"
spanIDKey = "Micro-Span-Id"
)
// FromContext returns a span from context
func FromContext(ctx context.Context) (traceID string, parentSpanID string, isFound bool) {
traceID, traceOk := metadata.Get(ctx, traceIDKey)
microID, microOk := metadata.Get(ctx, "Micro-Id")
if !traceOk && !microOk {
isFound = false
return
}
if !traceOk {
traceID = microID
}
parentSpanID, ok := metadata.Get(ctx, spanIDKey)
return traceID, parentSpanID, ok
}
// ToContext saves the trace and span ids in the context
func ToContext(ctx context.Context, traceID, parentSpanID string) context.Context {
return metadata.MergeContext(ctx, map[string]string{
traceIDKey: traceID,
spanIDKey: parentSpanID,
}, true)
}

View File

@ -1,12 +1,7 @@
package registry package registry
import ( import (
"net"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/registry" "github.com/unistack-org/micro/v3/registry"
"github.com/unistack-org/micro/v3/server"
"github.com/unistack-org/micro/v3/util/addr"
) )
func addNodes(old, neu []*registry.Node) []*registry.Node { func addNodes(old, neu []*registry.Node) []*registry.Node {
@ -60,13 +55,13 @@ func delNodes(old, del []*registry.Node) []*registry.Node {
// CopyService make a copy of service // CopyService make a copy of service
func CopyService(service *registry.Service) *registry.Service { func CopyService(service *registry.Service) *registry.Service {
// copy service // copy service
s := new(registry.Service) s := &registry.Service{}
*s = *service *s = *service
// copy nodes // copy nodes
nodes := make([]*registry.Node, len(service.Nodes)) nodes := make([]*registry.Node, len(service.Nodes))
for j, node := range service.Nodes { for j, node := range service.Nodes {
n := new(registry.Node) n := &registry.Node{}
*n = *node *n = *node
nodes[j] = n nodes[j] = n
} }
@ -75,7 +70,7 @@ func CopyService(service *registry.Service) *registry.Service {
// copy endpoints // copy endpoints
eps := make([]*registry.Endpoint, len(service.Endpoints)) eps := make([]*registry.Endpoint, len(service.Endpoints))
for j, ep := range service.Endpoints { for j, ep := range service.Endpoints {
e := new(registry.Endpoint) e := &registry.Endpoint{}
*e = *ep *e = *ep
eps[j] = e eps[j] = e
} }
@ -100,7 +95,7 @@ func Merge(olist []*registry.Service, nlist []*registry.Service) []*registry.Ser
var seen bool var seen bool
for _, o := range olist { for _, o := range olist {
if o.Version == n.Version { if o.Version == n.Version {
sp := new(registry.Service) sp := &registry.Service{}
// make copy // make copy
*sp = *o *sp = *o
// set nodes // set nodes
@ -111,7 +106,7 @@ func Merge(olist []*registry.Service, nlist []*registry.Service) []*registry.Ser
srv = append(srv, sp) srv = append(srv, sp)
break break
} else { } else {
sp := new(registry.Service) sp := &registry.Service{}
// make copy // make copy
*sp = *o *sp = *o
srv = append(srv, sp) srv = append(srv, sp)
@ -129,7 +124,7 @@ func Remove(old, del []*registry.Service) []*registry.Service {
var services []*registry.Service var services []*registry.Service
for _, o := range old { for _, o := range old {
srv := new(registry.Service) srv := &registry.Service{}
*srv = *o *srv = *o
var rem bool var rem bool
@ -151,38 +146,3 @@ func Remove(old, del []*registry.Service) []*registry.Service {
return services return services
} }
func NewService(s server.Server) (*registry.Service, error) {
opts := s.Options()
advt := opts.Address
if len(opts.Advertise) > 0 {
advt = opts.Advertise
}
host, port, err := net.SplitHostPort(advt)
if err != nil {
return nil, err
}
addr, err := addr.Extract(host)
if err != nil {
addr = host
}
node := &registry.Node{
Id: opts.Name + "-" + opts.Id,
Address: net.JoinHostPort(addr, port),
}
node.Metadata = metadata.Copy(opts.Metadata)
node.Metadata["server"] = s.String()
node.Metadata["broker"] = opts.Broker.String()
node.Metadata["registry"] = opts.Registry.String()
return &registry.Service{
Name: opts.Name,
Version: opts.Version,
Nodes: []*registry.Node{node},
}, nil
}

View File

@ -42,12 +42,20 @@ func NewSync(opts ...Option) Sync {
return c return c
} }
func (c *syncStore) Connect(ctx context.Context) error {
return nil
}
func (c *syncStore) Disconnect(ctx context.Context) error {
return nil
}
func (c *syncStore) Close(ctx context.Context) error { func (c *syncStore) Close(ctx context.Context) error {
return nil return nil
} }
// Init initialises the storeOptions // Init initialises the storeOptions
func (c *syncStore) Init(ctx context.Context, opts ...store.Option) error { func (c *syncStore) Init(opts ...store.Option) error {
for _, o := range opts { for _, o := range opts {
o(&c.storeOpts) o(&c.storeOpts)
} }
@ -55,7 +63,7 @@ func (c *syncStore) Init(ctx context.Context, opts ...store.Option) error {
return fmt.Errorf("the sync has no stores") return fmt.Errorf("the sync has no stores")
} }
for _, s := range c.syncOpts.Stores { for _, s := range c.syncOpts.Stores {
if err := s.Init(ctx); err != nil { if err := s.Init(); err != nil {
return fmt.Errorf("Store %s failed to Init(): %w", s.String(), err) return fmt.Errorf("Store %s failed to Init(): %w", s.String(), err)
} }
} }