Skip to content

Commit

Permalink
add notification message and publish to mediatr
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdihadeli committed Aug 3, 2022
1 parent dac2b83 commit c4f8d3f
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 7 deletions.
70 changes: 66 additions & 4 deletions mediatr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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
}
Expand All @@ -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)
Expand All @@ -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
}
48 changes: 48 additions & 0 deletions mediatr_benchmarks_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
103 changes: 100 additions & 3 deletions mediatr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

0 comments on commit c4f8d3f

Please sign in to comment.