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

     1  package requestreply
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/pkg/errors"
     7  
     8  	"github.com/wfusion/gofusion/common/infra/watermill/components/cqrs"
     9  	"github.com/wfusion/gofusion/common/infra/watermill/message"
    10  )
    11  
    12  // NewCommandHandler creates a new CommandHandler which supports the request-reply pattern.
    13  // The result handler is handler compatible with cqrs.CommandHandler.
    14  //
    15  // The logic if a command should be acked or not is based on the logic of the Backend.
    16  // For example, for the PubSubBackend, it depends on the `PubSubBackendConfig.AckCommandErrors` option.
    17  func NewCommandHandler[Command any](
    18  	handlerName string,
    19  	backend Backend[struct{}],
    20  	handleFunc func(ctx context.Context, cmd *Command) error,
    21  ) cqrs.CommandHandler {
    22  	return cqrs.NewCommandHandler(handlerName, func(ctx context.Context, cmd *Command) error {
    23  		handlerErr := handleFunc(ctx, cmd)
    24  
    25  		originalMessage, err := originalCommandMsgFromCtx(ctx)
    26  		if err != nil {
    27  			return err
    28  		}
    29  
    30  		return backend.OnCommandProcessed(ctx, BackendOnCommandProcessedParams[struct{}]{
    31  			Command:        cmd,
    32  			CommandMessage: originalMessage,
    33  			HandleErr:      handlerErr,
    34  		})
    35  	})
    36  }
    37  
    38  // NewCommandHandlerWithResult creates a new CommandHandler which supports the request-reply pattern with a result.
    39  // The result handler is handler compatible with cqrs.CommandHandler.
    40  //
    41  // In addition to cqrs.CommandHandler, it also allows returninga result from the handler.
    42  // The result is passed to the Backend implementation and sent to the caller.
    43  //
    44  // The logic if a command should be acked or not is based on the logic of the Backend.
    45  // For example, for the PubSubBackend, it depends on the `PubSubBackendConfig.AckCommandErrors` option.
    46  //
    47  // The reply is sent to the caller, even if the handler returns an error.
    48  func NewCommandHandlerWithResult[Command any, Result any](
    49  	handlerName string,
    50  	backend Backend[Result],
    51  	handleFunc func(ctx context.Context, cmd *Command) (Result, error),
    52  ) cqrs.CommandHandler {
    53  	return cqrs.NewCommandHandler(handlerName, func(ctx context.Context, cmd *Command) error {
    54  		resp, handlerErr := handleFunc(ctx, cmd)
    55  
    56  		originalMessage, err := originalCommandMsgFromCtx(ctx)
    57  		if err != nil {
    58  			return err
    59  		}
    60  
    61  		return backend.OnCommandProcessed(ctx, BackendOnCommandProcessedParams[Result]{
    62  			Command:        cmd,
    63  			CommandMessage: originalMessage,
    64  			HandlerResult:  resp,
    65  			HandleErr:      handlerErr,
    66  		})
    67  	})
    68  }
    69  
    70  func originalCommandMsgFromCtx(ctx context.Context) (*message.Message, error) {
    71  	originalMessage := cqrs.OriginalMessageFromCtx(ctx)
    72  	if originalMessage == nil {
    73  		// This should not happen, as long as cqrs.CommandProcessor is used - but it's not mandatory.
    74  		// In this case, it's enough to use cqrs.CtxWithOriginalMessage
    75  		return nil, errors.New(
    76  			"original message not found in context, did you pass context correctly everywhere? " +
    77  				"did you use cqrs.CommandProcessor? " +
    78  				"if you are using custom implementation, please call cqrs.CtxWithOriginalMessage on the context passed to the handler",
    79  		)
    80  	}
    81  	return originalMessage, nil
    82  }