github.com/wfusion/gofusion@v1.1.14/common/infra/watermill/components/cqrs/command_bus.go (about)

     1  package cqrs
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/pkg/errors"
     7  	"go.uber.org/multierr"
     8  
     9  	"github.com/wfusion/gofusion/common/infra/watermill"
    10  	"github.com/wfusion/gofusion/common/infra/watermill/message"
    11  )
    12  
    13  type CommandBusConfig struct {
    14  	// GeneratePublishTopic is used to generate topic for publishing command.
    15  	GeneratePublishTopic CommandBusGeneratePublishTopicFn
    16  
    17  	// OnSend is called before publishing the command.
    18  	// The *message.Message can be modified.
    19  	//
    20  	// This option is not required.
    21  	OnSend CommandBusOnSendFn
    22  
    23  	// Marshaler is used to marshal and unmarshal commands.
    24  	// It is required.
    25  	Marshaler CommandEventMarshaler
    26  
    27  	// Logger instance used to log.
    28  	// If not provided, watermill.NopLogger is used.
    29  	Logger watermill.LoggerAdapter
    30  }
    31  
    32  func (c *CommandBusConfig) setDefaults() {
    33  	if c.Logger == nil {
    34  		c.Logger = watermill.NopLogger{}
    35  	}
    36  }
    37  
    38  func (c CommandBusConfig) Validate() error {
    39  	var err error
    40  
    41  	if c.Marshaler == nil {
    42  		err = multierr.Append(err, errors.New("missing Marshaler"))
    43  	}
    44  
    45  	if c.GeneratePublishTopic == nil {
    46  		err = multierr.Append(err, errors.New("missing GeneratePublishTopic"))
    47  	}
    48  
    49  	return err
    50  }
    51  
    52  type CommandBusGeneratePublishTopicFn func(CommandBusGeneratePublishTopicParams) (string, error)
    53  
    54  type CommandBusGeneratePublishTopicParams struct {
    55  	CommandName string
    56  	Command     any
    57  }
    58  
    59  type CommandBusOnSendFn func(params CommandBusOnSendParams) error
    60  
    61  type CommandBusOnSendParams struct {
    62  	CommandName string
    63  	Command     any
    64  
    65  	// Message is never nil and can be modified.
    66  	Message *message.Message
    67  }
    68  
    69  // CommandBus transports commands to command handlers.
    70  type CommandBus struct {
    71  	publisher message.Publisher
    72  
    73  	config CommandBusConfig
    74  }
    75  
    76  // NewCommandBusWithConfig creates a new CommandBus.
    77  func NewCommandBusWithConfig(publisher message.Publisher, config CommandBusConfig) (*CommandBus, error) {
    78  	if publisher == nil {
    79  		return nil, errors.New("missing publisher")
    80  	}
    81  
    82  	config.setDefaults()
    83  	if err := config.Validate(); err != nil {
    84  		return nil, errors.Wrap(err, "invalid config")
    85  	}
    86  
    87  	return &CommandBus{publisher, config}, nil
    88  }
    89  
    90  // NewCommandBus creates a new CommandBus.
    91  // Deprecated: use NewCommandBusWithConfig instead.
    92  func NewCommandBus(
    93  	publisher message.Publisher,
    94  	generateTopic func(commandName string) string,
    95  	marshaler CommandEventMarshaler,
    96  ) (*CommandBus, error) {
    97  	if publisher == nil {
    98  		return nil, errors.New("missing publisher")
    99  	}
   100  	if generateTopic == nil {
   101  		return nil, errors.New("missing generateTopic")
   102  	}
   103  	if marshaler == nil {
   104  		return nil, errors.New("missing marshaler")
   105  	}
   106  
   107  	return &CommandBus{publisher, CommandBusConfig{
   108  		GeneratePublishTopic: func(params CommandBusGeneratePublishTopicParams) (string, error) {
   109  			return generateTopic(params.CommandName), nil
   110  		},
   111  		Marshaler: marshaler,
   112  	}}, nil
   113  }
   114  
   115  // Send sends command to the command bus.
   116  func (c CommandBus) Send(ctx context.Context, cmd any) error {
   117  	return c.SendWithModifiedMessage(ctx, cmd, nil)
   118  }
   119  
   120  func (c CommandBus) SendWithModifiedMessage(ctx context.Context, cmd any, modify func(*message.Message) error) error {
   121  	msg, topicName, err := c.newMessage(ctx, cmd)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	if modify != nil {
   127  		if err := modify(msg); err != nil {
   128  			return errors.Wrap(err, "cannot modify message")
   129  		}
   130  	}
   131  
   132  	if err := c.publisher.Publish(ctx, topicName, msg); err != nil {
   133  		return err
   134  	}
   135  
   136  	return nil
   137  }
   138  
   139  func (c CommandBus) newMessage(ctx context.Context, command any) (*message.Message, string, error) {
   140  	msg, err := c.config.Marshaler.Marshal(command)
   141  	if err != nil {
   142  		return nil, "", err
   143  	}
   144  
   145  	commandName := c.config.Marshaler.Name(command)
   146  	topicName, err := c.config.GeneratePublishTopic(CommandBusGeneratePublishTopicParams{
   147  		CommandName: commandName,
   148  		Command:     command,
   149  	})
   150  	if err != nil {
   151  		return nil, "", errors.Wrap(err, "cannot generate topic name")
   152  	}
   153  
   154  	msg.SetContext(ctx)
   155  
   156  	if c.config.OnSend != nil {
   157  		err := c.config.OnSend(CommandBusOnSendParams{
   158  			CommandName: commandName,
   159  			Command:     command,
   160  			Message:     msg,
   161  		})
   162  		if err != nil {
   163  			return nil, "", errors.Wrap(err, "cannot execute OnSend")
   164  		}
   165  	}
   166  
   167  	return msg, topicName, nil
   168  }