Files
upstream-watermill/components/cqrs/command_bus.go
2026-09-15 21:27:46 +05:00

169 lines
4.0 KiB
Go

package cqrs
import (
"context"
stdErrors "errors"
"github.com/ThreeDotsLabs/watermill"
"github.com/pkg/errors"
"github.com/ThreeDotsLabs/watermill/message"
)
type CommandBusConfig struct {
// GeneratePublishTopic is used to generate topic for publishing command.
GeneratePublishTopic CommandBusGeneratePublishTopicFn
// OnSend is called before publishing the command.
// The *message.Message can be modified.
//
// This option is not required.
OnSend CommandBusOnSendFn
// Marshaler is used to marshal and unmarshal commands.
// It is required.
Marshaler CommandEventMarshaler
// Logger instance used to log.
// If not provided, watermill.NopLogger is used.
Logger watermill.LoggerAdapter
}
func (c *CommandBusConfig) setDefaults() {
if c.Logger == nil {
c.Logger = watermill.NopLogger{}
}
}
func (c CommandBusConfig) Validate() error {
var err error
if c.Marshaler == nil {
err = stdErrors.Join(err, errors.New("missing Marshaler"))
}
if c.GeneratePublishTopic == nil {
err = stdErrors.Join(err, errors.New("missing GeneratePublishTopic"))
}
return err
}
type CommandBusGeneratePublishTopicFn func(CommandBusGeneratePublishTopicParams) (string, error)
type CommandBusGeneratePublishTopicParams struct {
CommandName string
Command any
}
type CommandBusOnSendFn func(params CommandBusOnSendParams) error
type CommandBusOnSendParams struct {
CommandName string
Command any
// Message is never nil and can be modified.
Message *message.Message
}
// CommandBus transports commands to command handlers.
type CommandBus struct {
publisher message.Publisher
config CommandBusConfig
}
// NewCommandBusWithConfig creates a new CommandBus.
func NewCommandBusWithConfig(publisher message.Publisher, config CommandBusConfig) (*CommandBus, error) {
if publisher == nil {
return nil, errors.New("missing publisher")
}
config.setDefaults()
if err := config.Validate(); err != nil {
return nil, errors.Wrap(err, "invalid config")
}
return &CommandBus{publisher, config}, nil
}
// NewCommandBus creates a new CommandBus.
// Deprecated: use NewCommandBusWithConfig instead.
func NewCommandBus(
publisher message.Publisher,
generateTopic func(commandName string) string,
marshaler CommandEventMarshaler,
) (*CommandBus, error) {
if publisher == nil {
return nil, errors.New("missing publisher")
}
if generateTopic == nil {
return nil, errors.New("missing generateTopic")
}
if marshaler == nil {
return nil, errors.New("missing marshaler")
}
return &CommandBus{publisher, CommandBusConfig{
GeneratePublishTopic: func(params CommandBusGeneratePublishTopicParams) (string, error) {
return generateTopic(params.CommandName), nil
},
Marshaler: marshaler,
}}, nil
}
// Send sends command to the command bus.
func (c CommandBus) Send(ctx context.Context, cmd any) error {
return c.SendWithModifiedMessage(ctx, cmd, nil)
}
func (c CommandBus) SendWithModifiedMessage(ctx context.Context, cmd any, modify func(*message.Message) error) error {
msg, topicName, err := c.newMessage(ctx, cmd)
if err != nil {
return err
}
if modify != nil {
if err := modify(msg); err != nil {
return errors.Wrap(err, "cannot modify message")
}
}
if err := c.publisher.Publish(topicName, msg); err != nil {
return err
}
return nil
}
func (c CommandBus) newMessage(ctx context.Context, command any) (*message.Message, string, error) {
msg, err := c.config.Marshaler.Marshal(command)
if err != nil {
return nil, "", err
}
commandName := c.config.Marshaler.Name(command)
topicName, err := c.config.GeneratePublishTopic(CommandBusGeneratePublishTopicParams{
CommandName: commandName,
Command: command,
})
if err != nil {
return nil, "", errors.Wrap(err, "cannot generate topic name")
}
msg.SetContext(ctx)
if c.config.OnSend != nil {
err := c.config.OnSend(CommandBusOnSendParams{
CommandName: commandName,
Command: command,
Message: msg,
})
if err != nil {
return nil, "", errors.Wrap(err, "cannot execute OnSend")
}
}
return msg, topicName, nil
}