github.com/wfusion/gofusion@v1.1.14/common/infra/watermill/components/fanin/fanin.go (about) 1 package fanin 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/pkg/errors" 9 10 "github.com/wfusion/gofusion/common/infra/watermill" 11 "github.com/wfusion/gofusion/common/infra/watermill/message" 12 ) 13 14 type Config struct { 15 // SourceTopics contains topics on which FanIn subscribes. 16 SourceTopics []string 17 18 // TargetTopic determines the topic on which messages from SourceTopics are published. 19 TargetTopic string 20 21 // CloseTimeout determines how long router should work for handlers when closing. 22 CloseTimeout time.Duration 23 } 24 25 // FanIn is a component that receives messages from 1..N topics from a subscriber and publishes them 26 // on a specified topic in the publisher. In effect, messages are "multiplexed". 27 type FanIn struct { 28 router *message.Router 29 config Config 30 logger watermill.LoggerAdapter 31 } 32 33 func (c *Config) setDefaults() { 34 if c.CloseTimeout == 0 { 35 c.CloseTimeout = time.Second * 30 36 } 37 } 38 39 func (c *Config) Validate() error { 40 if len(c.SourceTopics) == 0 { 41 return errors.New("sourceTopics must not be empty") 42 } 43 44 for _, fromTopic := range c.SourceTopics { 45 if fromTopic == "" { 46 return errors.New("sourceTopics must not be empty") 47 } 48 } 49 50 if c.TargetTopic == "" { 51 return errors.New("targetTopic must not be empty") 52 } 53 54 for _, fromTopic := range c.SourceTopics { 55 if fromTopic == c.TargetTopic { 56 return errors.New("sourceTopics must not contain targetTopic") 57 } 58 } 59 60 return nil 61 } 62 63 // NewFanIn creates a new FanIn. 64 func NewFanIn( 65 subscriber message.Subscriber, 66 publisher message.Publisher, 67 config Config, 68 logger watermill.LoggerAdapter, 69 ) (*FanIn, error) { 70 if subscriber == nil { 71 return nil, errors.New("missing subscriber") 72 } 73 if publisher == nil { 74 return nil, errors.New("missing publisher") 75 } 76 77 config.setDefaults() 78 if err := config.Validate(); err != nil { 79 return nil, err 80 } 81 if logger == nil { 82 logger = watermill.NopLogger{} 83 } 84 85 routerConfig := message.RouterConfig{CloseTimeout: config.CloseTimeout} 86 if err := routerConfig.Validate(); err != nil { 87 return nil, errors.Wrap(err, "invalid router config") 88 } 89 90 router, err := message.NewRouter(routerConfig, logger) 91 if err != nil { 92 return nil, errors.Wrap(err, "cannot create a router") 93 } 94 95 for _, topic := range config.SourceTopics { 96 router.AddHandler( 97 fmt.Sprintf("fan_in_%s", topic), 98 topic, 99 subscriber, 100 config.TargetTopic, 101 publisher, 102 func(msg *message.Message) ([]*message.Message, error) { 103 return []*message.Message{msg}, nil 104 }, 105 ) 106 } 107 108 return &FanIn{ 109 router: router, 110 config: config, 111 logger: logger, 112 }, nil 113 } 114 115 // Run runs the FanIn. 116 func (f *FanIn) Run(ctx context.Context) error { 117 return f.router.Run(ctx) 118 } 119 120 // Running is closed when FanIn is running. 121 func (f *FanIn) Running() chan struct{} { 122 return f.router.Running() 123 } 124 125 // Close gracefully closes the FanIn 126 func (f *FanIn) Close() error { 127 return f.router.Close() 128 }