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  }