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 }