diff --git a/mediatr.go b/mediatr.go index 842b378..f6dd535 100644 --- a/mediatr.go +++ b/mediatr.go @@ -2,20 +2,26 @@ package mediatr import ( "context" - "github.com/pkg/errors" "reflect" + + "github.com/pkg/errors" ) type RequestHandler[TRequest any, TResponse any] interface { Handle(ctx context.Context, request TRequest) (TResponse, error) } +type NotificationHandler[TNotification any] interface { + Handle(ctx context.Context, notification TNotification) error +} + var requestHandlersRegistrations = map[reflect.Type]interface{}{} +var notificationHandlersRegistrations = map[reflect.Type][]interface{}{} type Unit struct{} // RegisterRequestHandler register the request handler to mediatr registry. -func RegisterRequestHandler[TRequest any, TResponse any](h RequestHandler[TRequest, TResponse]) error { +func RegisterRequestHandler[TRequest any, TResponse any](handler RequestHandler[TRequest, TResponse]) error { var request TRequest requestType := reflect.TypeOf(request) @@ -25,7 +31,7 @@ func RegisterRequestHandler[TRequest any, TResponse any](h RequestHandler[TReque return errors.Errorf("registered handler already exists in the registry for message %s", requestType.String()) } - requestHandlersRegistrations[requestType] = h + requestHandlersRegistrations[requestType] = handler return nil } @@ -35,7 +41,39 @@ func RegisterRequestBehavior(b interface{}) error { return nil } -// Send the request to its corresponding handler. +// RegisterNotificationHandler register the notification handler to mediatr registry. +func RegisterNotificationHandler[TEvent any](handler NotificationHandler[TEvent]) error { + var event TEvent + eventType := reflect.TypeOf(event) + + handlers, exist := notificationHandlersRegistrations[eventType] + if exist == false { + notificationHandlersRegistrations[eventType] = []interface{}{handler} + return nil + } + + notificationHandlersRegistrations[eventType] = append(handlers, handler) + + return nil +} + +// RegisterNotificationHandlers register the notification handlers to mediatr registry. +func RegisterNotificationHandlers[TEvent any](handlers ...NotificationHandler[TEvent]) error { + if len(handlers) == 0 { + return errors.New("no handlers provided") + } + + for _, handler := range handlers { + err := RegisterNotificationHandler[TEvent](handler) + if err != nil { + return err + } + } + + return nil +} + +// Send the request to its corresponding request handler. func Send[TRequest any, TResponse any](ctx context.Context, request TRequest) (TResponse, error) { requestType := reflect.TypeOf(request) @@ -57,3 +95,27 @@ func Send[TRequest any, TResponse any](ctx context.Context, request TRequest) (T return response, nil } + +// Publish the notification event to its corresponding notification handler. +func Publish[TNotification any](ctx context.Context, notification TNotification) error { + eventType := reflect.TypeOf(notification) + + handlers, ok := notificationHandlersRegistrations[eventType] + if !ok { + return errors.Errorf("no handlers for notification %T", notification) + } + + for _, handler := range handlers { + handlerValue, ok := handler.(NotificationHandler[TNotification]) + if !ok { + return errors.Errorf("handler for notification %T is not a Handler", notification) + } + + err := handlerValue.Handle(ctx, notification) + if err != nil { + return errors.Wrap(err, "error handling notification") + } + } + + return nil +} diff --git a/mediatr_benchmarks_test.go b/mediatr_benchmarks_test.go new file mode 100644 index 0000000..67bf24b --- /dev/null +++ b/mediatr_benchmarks_test.go @@ -0,0 +1,48 @@ +package mediatr + +import ( + "context" + "reflect" + "testing" +) + +func Benchmark_Send(b *testing.B) { + // because benchmark method will run multiple times, we need to reset the request handler registry before each run. + requestHandlersRegistrations = make(map[reflect.Type]interface{}) + + handler := &RequestTestHandler{} + errRegister := RegisterRequestHandler[*RequestTest, *ResponseTest](handler) + if errRegister != nil { + b.Error(errRegister) + } + + b.ResetTimer() + ctx := context.Background() + for i := 0; i < b.N; i++ { + _, err := Send[*RequestTest, *ResponseTest](ctx, &RequestTest{Data: "test"}) + if err != nil { + b.Error(err) + } + } +} + +func Benchmark_Publish(b *testing.B) { + // because benchmark method will run multiple times, we need to reset the notification handlers registry before each run. + notificationHandlersRegistrations = make(map[reflect.Type][]interface{}) + + handler := &NotificationTestHandler{} + handler2 := &NotificationTestHandler4{} + + errRegister := RegisterNotificationHandlers[*NotificationTest](handler, handler2) + if errRegister != nil { + b.Error(errRegister) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := Publish[*NotificationTest](context.Background(), &NotificationTest{Data: "test"}) + if err != nil { + b.Error(err) + } + } +} \ No newline at end of file diff --git a/mediatr_test.go b/mediatr_test.go index 223c3a4..8c4904b 100644 --- a/mediatr_test.go +++ b/mediatr_test.go @@ -38,20 +38,76 @@ func Test_Send_Should_Throw_Error_If_No_Handler_Registered(t *testing.T) { func Test_Send_Should_Return_Error_If_Handler_Returns_Error(t *testing.T) { expectedErr := "error handling request" handler3 := &RequestTestHandler3{} - _ = RegisterRequestHandler[*RequestTest2, *ResponseTest2](handler3) + errRegister := RegisterRequestHandler[*RequestTest2, *ResponseTest2](handler3) + if errRegister != nil { + t.Error(errRegister) + } _, err := Send[*RequestTest2, *ResponseTest2](context.Background(), &RequestTest2{Data: "test"}) assert.Containsf(t, err.Error(), expectedErr, "expected error containing %q, got %s", expectedErr, err) } -func Test_Send_Should_Return_Response_If_Handler_Returns_Success(t *testing.T) { +func Test_Send_Should_Dispatch_Request_To_Handler_And_Get_Response(t *testing.T) { handler := &RequestTestHandler{} - _ = RegisterRequestHandler[*RequestTest, *ResponseTest](handler) + errRegister := RegisterRequestHandler[*RequestTest, *ResponseTest](handler) + if errRegister != nil { + t.Error(errRegister) + } response, err := Send[*RequestTest, *ResponseTest](context.Background(), &RequestTest{Data: "test"}) assert.Nil(t, err) assert.IsType(t, &ResponseTest{}, response) assert.Equal(t, "test", response.Data) } +func Test_RegisterNotificationHandler_Should_Register_Multiple_Handler_For_Notification(t *testing.T) { + handler1 := &NotificationTestHandler{} + handler2 := &NotificationTestHandler{} + err1 := RegisterNotificationHandler[*NotificationTest](handler1) + err2 := RegisterNotificationHandler[*NotificationTest](handler2) + + assert.Nil(t, err1) + assert.Nil(t, err2) +} + +func Test_RegisterNotificationHandlers_Should_Register_Multiple_Handler_For_Notification(t *testing.T) { + handler1 := &NotificationTestHandler{} + handler2 := &NotificationTestHandler{} + handler3 := &NotificationTestHandler4{} + err := RegisterNotificationHandlers[*NotificationTest](handler1, handler2, handler3) + + assert.Nil(t, err) +} + +func Test_Publish_Should_Throw_Error_If_No_Handler_Registered(t *testing.T) { + expectedErr := fmt.Sprintf("no handlers for notification %T", &NotificationTest{}) + err := Publish[*NotificationTest](context.Background(), &NotificationTest{}) + assert.Containsf(t, err.Error(), expectedErr, "expected error containing %q, got %s", expectedErr, err) +} + +func Test_Publish_Should_Return_Error_If_Handler_Returns_Error(t *testing.T) { + expectedErr := "error handling notification" + handler1 := &NotificationTestHandler{} + handler2 := &NotificationTestHandler{} + handler3 := &NotificationTestHandler3{} + + errRegister := RegisterNotificationHandlers[*NotificationTest](handler1, handler2, handler3) + if errRegister != nil { + t.Error(errRegister) + } + err := Publish[*NotificationTest](context.Background(), &NotificationTest{}) + assert.Containsf(t, err.Error(), expectedErr, "expected error containing %q, got %s", expectedErr, err) +} + +func Test_Publish_Should_Dispatch_Notification_To_All_Handlers_Without_Any_Response_And_Error(t *testing.T) { + handler1 := &NotificationTestHandler{} + handler2 := &NotificationTestHandler4{} + errRegister := RegisterNotificationHandlers[*NotificationTest](handler1, handler2) + if errRegister != nil { + t.Error(errRegister) + } + err := Publish[*NotificationTest](context.Background(), &NotificationTest{}) + assert.Nil(t, err) +} + /////////////////////////////////////////////////////////////////////////////////////////////// type RequestTest struct { Data string @@ -91,3 +147,44 @@ type RequestTestHandler3 struct { func (c *RequestTestHandler3) Handle(ctx context.Context, request *RequestTest2) (*ResponseTest2, error) { return nil, errors.New("some error") } + +/////////////////////////////////////////////////////////////////////////////////////////////// +type NotificationTest struct { + Data string +} + +type NotificationTestHandler struct { +} + +func (c *NotificationTestHandler) Handle(ctx context.Context, notification *NotificationTest) error { + return nil +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +type NotificationTest2 struct { + Data string +} + +type NotificationTestHandler2 struct { +} + +func (c *NotificationTestHandler2) Handle(ctx context.Context, notification *NotificationTest2) error { + return nil +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +type NotificationTestHandler3 struct { +} + +func (c *NotificationTestHandler3) Handle(ctx context.Context, notification *NotificationTest) error { + return errors.New("some error") +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +type NotificationTestHandler4 struct { +} + +func (c *NotificationTestHandler4) Handle(ctx context.Context, notification *NotificationTest) error { + return nil +}