github.com/vseinstrumentiru/lego@v1.0.2/internal/lego/transport/event/eventmanager.go (about)

     1  package event
     2  
     3  import (
     4  	"context"
     5  	"emperror.dev/emperror"
     6  	"emperror.dev/errors"
     7  	"fmt"
     8  	"github.com/ThreeDotsLabs/watermill-nats/pkg/nats"
     9  	"github.com/ThreeDotsLabs/watermill/components/cqrs"
    10  	"github.com/ThreeDotsLabs/watermill/message"
    11  	"github.com/ThreeDotsLabs/watermill/message/router/middleware"
    12  	"github.com/ThreeDotsLabs/watermill/pubsub/gochannel"
    13  	originNats "github.com/nats-io/nats.go"
    14  	"github.com/nats-io/stan.go"
    15  	"github.com/sagikazarmark/kitx/correlation"
    16  	"github.com/vseinstrumentiru/lego/internal/lego"
    17  	"github.com/vseinstrumentiru/lego/internal/lego/transport/event/metrics"
    18  	"github.com/vseinstrumentiru/lego/pkg/eventtools/cloudevent"
    19  	watermilllog "logur.dev/integration/watermill"
    20  	"os"
    21  	"regexp"
    22  	"sync"
    23  	"time"
    24  )
    25  
    26  type publishers struct {
    27  	sync.Mutex
    28  	defaultName string
    29  	defaultPub  message.Publisher
    30  	items       map[string]message.Publisher
    31  }
    32  
    33  func (em *publishers) add(key, name string, publisher message.Publisher) (err error) {
    34  	var pubErr error
    35  	publisher, pubErr = metrics.DecoratePublisher(publisher)
    36  	err = errors.Append(err, pubErr)
    37  	publisher, pubErr = message.MessageTransformPublisherDecorator(func(msg *message.Message) {
    38  		if cid, ok := correlation.FromContext(msg.Context()); ok {
    39  			middleware.SetCorrelationID(cid, msg)
    40  		}
    41  	})(publisher)
    42  
    43  	if key == em.defaultName {
    44  		em.defaultPub = publisher
    45  	}
    46  
    47  	em.items[key] = publisher
    48  
    49  	return
    50  }
    51  
    52  func (em *publishers) Publish(topic string, messages ...*message.Message) error {
    53  	var pub message.Publisher
    54  	var ok bool
    55  
    56  	em.Lock()
    57  	if pub, ok = em.items[topic]; !ok {
    58  		pub = em.defaultPub
    59  	}
    60  	em.Unlock()
    61  
    62  	if pub == nil {
    63  		return errors.New("undefined publisher")
    64  	}
    65  
    66  	return pub.Publish(topic, messages...)
    67  }
    68  
    69  func (em *publishers) Close() (err error) {
    70  	for _, pub := range em.items {
    71  		err = errors.Append(err, pub.Close())
    72  	}
    73  
    74  	return
    75  }
    76  
    77  type subscribers struct {
    78  	sync.Mutex
    79  	defaultName string
    80  	defaultSub  message.Subscriber
    81  	items       map[string]message.Subscriber
    82  }
    83  
    84  func (em *subscribers) add(key, name string, subscriber message.Subscriber) (err error) {
    85  	var subErr error
    86  	subscriber, subErr = metrics.DecorateSubscriber(subscriber)
    87  	err = errors.Append(err, subErr)
    88  	subscriber, subErr = message.MessageTransformSubscriberDecorator(func(msg *message.Message) {
    89  		if cid := middleware.MessageCorrelationID(msg); cid != "" {
    90  			msg.SetContext(correlation.ToContext(msg.Context(), cid))
    91  		}
    92  	})(subscriber)
    93  
    94  	if key == em.defaultName {
    95  		em.defaultSub = subscriber
    96  	}
    97  
    98  	em.items[key] = subscriber
    99  
   100  	return
   101  }
   102  
   103  func (em *subscribers) Subscribe(ctx context.Context, topic string) (<-chan *message.Message, error) {
   104  	var sub message.Subscriber
   105  	var ok bool
   106  
   107  	em.Lock()
   108  	if sub, ok = em.items[topic]; !ok {
   109  		sub = em.defaultSub
   110  	}
   111  	em.Unlock()
   112  
   113  	return sub.Subscribe(ctx, topic)
   114  }
   115  
   116  func (em *subscribers) Close() (err error) {
   117  	for _, pub := range em.items {
   118  		err = errors.Append(err, pub.Close())
   119  	}
   120  
   121  	return
   122  }
   123  
   124  type eventManager struct {
   125  	lego.LogErr
   126  	*publishers
   127  	*subscribers
   128  	router *message.Router
   129  }
   130  
   131  func newEventManager(logErr lego.LogErr, config Config) (_ *eventManager, err error) {
   132  	em := &eventManager{
   133  		LogErr: logErr,
   134  		publishers: &publishers{
   135  			defaultName: config.DefaultProvider,
   136  			items:       make(map[string]message.Publisher),
   137  		},
   138  		subscribers: &subscribers{
   139  			defaultName: config.DefaultProvider,
   140  			items:       make(map[string]message.Subscriber),
   141  		},
   142  	}
   143  
   144  	for name, cfg := range config.Providers.Nats {
   145  		var marshaller nats.MarshalerUnmarshaler
   146  		if cfg.CloudEvent.Enabled {
   147  			marshaller = cloudevent.Marshaller{
   148  				SpecVersion: "0.3",
   149  				Source:      cfg.CloudEvent.Source,
   150  			}
   151  		} else {
   152  			marshaller = nats.GobMarshaler{}
   153  		}
   154  
   155  		var suffixer func(string) string
   156  
   157  		switch cfg.ClientIDSuffixGen {
   158  		case natsClientIDSuffixHost:
   159  			suffixer = hostSuffix
   160  		default:
   161  			suffixer = withoutSuffix
   162  		}
   163  
   164  		if cfg.Pub {
   165  			var pub message.Publisher
   166  			var pubErr error
   167  			natsConn, conErr := originNats.Connect(cfg.Addr, natsConnOptions(logErr, cfg.PanicOnLost)...)
   168  
   169  			if err != nil {
   170  				err = errors.Append(err, conErr)
   171  			}
   172  			pub, pubErr = nats.NewStreamingPublisher(
   173  				nats.StreamingPublisherConfig{
   174  					ClusterID: cfg.ClusterID,
   175  					ClientID:  suffixer(cfg.ClientID + "_pub"),
   176  					StanOptions: []stan.Option{
   177  						stan.NatsConn(natsConn),
   178  						stan.Pings(20, 10),
   179  					},
   180  					Marshaler: marshaller,
   181  				},
   182  				watermilllog.New(logErr.WithFields(map[string]interface{}{"component": "events.nats.pub." + name})),
   183  			)
   184  
   185  			err = errors.Append(err, pubErr)
   186  			err = errors.Append(err, em.publishers.add(name, "events.nats.pub."+name, pub))
   187  		}
   188  
   189  		if cfg.Sub {
   190  			natsConn, conErr := originNats.Connect(cfg.Addr, natsConnOptions(logErr, cfg.PanicOnLost)...)
   191  
   192  			if err != nil {
   193  				err = errors.Append(err, conErr)
   194  			}
   195  
   196  			sub, subErr := nats.NewStreamingSubscriber(
   197  				nats.StreamingSubscriberConfig{
   198  					ClusterID:        cfg.ClusterID,
   199  					ClientID:         suffixer(cfg.ClientID + "_sub"),
   200  					QueueGroup:       cfg.QueueGroup,
   201  					DurableName:      cfg.DurableName,
   202  					SubscribersCount: cfg.SubscribersCount, // how many goroutines should consume messages
   203  					CloseTimeout:     cfg.CloseTimeout,
   204  					AckWaitTimeout:   cfg.AckWaitTimeout,
   205  					StanOptions: []stan.Option{
   206  						stan.NatsConn(natsConn),
   207  						stan.Pings(20, 10),
   208  						stan.SetConnectionLostHandler(func(conn stan.Conn, err error) {
   209  							em.Info("stan: connection lost")
   210  							_ = em.router.Close()
   211  						}),
   212  					},
   213  					StanSubscriptionOptions: []stan.SubscriptionOption{
   214  						stan.DeliverAllAvailable(),
   215  					},
   216  					Unmarshaler: marshaller,
   217  				},
   218  				watermilllog.New(logErr.WithFields(map[string]interface{}{"component": "events.nats.sub." + name})),
   219  			)
   220  
   221  			err = errors.Append(err, subErr)
   222  
   223  			err = errors.Append(err, subErr)
   224  			err = errors.Append(err, em.subscribers.add(name, "events.nats.sub."+name, sub))
   225  		}
   226  	}
   227  
   228  	for name, cfg := range config.Providers.Channel {
   229  		pubsub := gochannel.NewGoChannel(
   230  			gochannel.Config{
   231  				OutputChannelBuffer:            cfg.OutputChannelBuffer,
   232  				Persistent:                     cfg.Persistent,
   233  				BlockPublishUntilSubscriberAck: cfg.BlockPublishUntilSubscriberAck,
   234  			},
   235  			watermilllog.New(logErr.WithFields(map[string]interface{}{"component": "events.channel.pubsub." + name})),
   236  		)
   237  		err = errors.Append(err, em.publishers.add(name, "events.channel.pub."+name, pubsub))
   238  		err = errors.Append(err, em.subscribers.add(name, "events.channel.sub."+name, pubsub))
   239  	}
   240  
   241  	{
   242  		router, routerErr := message.NewRouter(
   243  			message.RouterConfig{
   244  				CloseTimeout: config.RouterConfig.CloseTimeout,
   245  			},
   246  			watermilllog.New(logErr.WithFields(map[string]interface{}{"component": "events.router"})),
   247  		)
   248  
   249  		err = errors.Append(err, routerErr)
   250  
   251  		retryMiddleware := middleware.Retry{}
   252  		retryMiddleware.MaxRetries = config.RouterConfig.MaxRetries
   253  		retryMiddleware.MaxInterval = config.RouterConfig.MaxRetryInterval
   254  
   255  		router.AddMiddleware(
   256  			// if retries limit was exceeded, message is sent to poison queue (poison_queue topic)
   257  			retryMiddleware.Middleware,
   258  
   259  			// correlation ID middleware adds to every produced message correlation id of consumed message,
   260  			// useful for debugging
   261  			middleware.CorrelationID,
   262  		)
   263  
   264  		em.router = router
   265  	}
   266  
   267  	return em, err
   268  }
   269  
   270  func (e *eventManager) AddHandlers(
   271  	handlers []cqrs.EventHandler,
   272  	generateTopic func(eventName string) string,
   273  	marshaler cqrs.CommandEventMarshaler,
   274  ) error {
   275  	processor, err := cqrs.NewEventProcessor(
   276  		handlers,
   277  		generateTopic,
   278  		func(handlerName string) (message.Subscriber, error) { return e.subscribers, nil },
   279  		marshaler,
   280  		watermilllog.New(e.LogErr.WithFields(map[string]interface{}{"component": "events.processor"})),
   281  	)
   282  
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	return processor.AddHandlersToRouter(e.router)
   288  }
   289  
   290  func (e *eventManager) Publisher() message.Publisher {
   291  	return e.publishers
   292  }
   293  
   294  func (e *eventManager) Run(ctx context.Context) (err error) {
   295  	return e.router.Run(ctx)
   296  }
   297  
   298  func (e *eventManager) Close() (err error) {
   299  	err = errors.Append(err, e.subscribers.Close())
   300  	err = errors.Append(err, e.publishers.Close())
   301  	return
   302  }
   303  
   304  func (e *eventManager) Subscriber() message.Subscriber {
   305  	return e.subscribers
   306  }
   307  
   308  func withoutSuffix(str string) string {
   309  	return str
   310  }
   311  
   312  func hostSuffix(str string) string {
   313  	name, err := os.Hostname()
   314  	if err != nil {
   315  		emperror.Panic(err)
   316  	}
   317  
   318  	return str + "_" + regexp.MustCompile(`[^a-zA-Z0-9_\-]`).ReplaceAllString(name, "_")
   319  }
   320  
   321  func natsConnOptions(log lego.LogErr, panicOnLostConn bool, opts ...originNats.Option) []originNats.Option {
   322  	totalWait := 10 * time.Minute
   323  	reconnectDelay := time.Second
   324  
   325  	opts = append(opts, originNats.ReconnectWait(reconnectDelay))
   326  	opts = append(opts, originNats.MaxReconnects(int(totalWait/reconnectDelay)))
   327  	if panicOnLostConn {
   328  		opts = append(opts, originNats.DisconnectErrHandler(func(nc *originNats.Conn, err error) {
   329  			emperror.Panic(errors.Wrap(err, "nats disconnected"))
   330  		}))
   331  		opts = append(opts, originNats.ClosedHandler(func(nc *originNats.Conn) {
   332  			log.Error("Exiting, no servers available")
   333  		}))
   334  	} else {
   335  		opts = append(opts, originNats.DisconnectErrHandler(func(nc *originNats.Conn, err error) {
   336  			log.Info(fmt.Sprintf("Disconnected: will attempt reconnects for %.0fm", totalWait.Minutes()))
   337  		}))
   338  		opts = append(opts, originNats.ReconnectHandler(func(nc *originNats.Conn) {
   339  			log.Info(fmt.Sprintf("Reconnected [%s]", nc.ConnectedUrl()))
   340  		}))
   341  		opts = append(opts, originNats.ClosedHandler(func(nc *originNats.Conn) {
   342  			log.Error("Exiting, no servers available")
   343  		}))
   344  	}
   345  
   346  	return opts
   347  }