• Stars
    star
    207
  • Rank 189,769 (Top 4 %)
  • Language
    Go
  • License
    MIT License
  • Created over 2 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

๐Ÿšƒ A library for handling mediator patterns and simplified CQRS patterns within an event-driven architecture, inspired by csharp MediatR library.
go-mediatr
build-status go report license build-status Coverage Status build-status

This package is a Mediator Pattern implementation in golang, and inspired by great jbogard/mediatr library in .Net.

For decoupling some objects in a system we could use Mediator object as an interface, for decrease coupling between the objects. Mostly I uses this pattern when I use CQRS in my system.

There are some samples for using this package here, also I used this packages widely in this microservices sample

๐Ÿงฐ Installation

go get github.com/mehdihadeli/go-mediatr

๐Ÿ”ฅ Features

โœ… Handling Request/Response message for delivering message to only one handler (Commands, Queries)

โœ… Handling Notification message for delivering message to multiple handlers (Events)

โœ… Pipelenes Behaviours for handling some cross cutting concerns before or after executing handlers

๐Ÿ›ก๏ธ Strategies

Mediatr has two strategies for dispatching messages:

  1. Request/Response messages, dispatched to a single handler.
  2. Notification messages, dispatched to all (multiple) handlers and they don't have any response.

Request/Response Strategy

The request/response message, has just one handler, and can handle both command and query scenarios in CQRS Pattern.

Creating a Request/Response Message

For creating a request (command or query) that has just one handler, we could create a command message or query message as a request like this:

// Command (Request)
type CreateProductCommand struct {
    ProductID   uuid.UUID `validate:"required"`
    Name        string    `validate:"required,gte=0,lte=255"`
    Description string    `validate:"required,gte=0,lte=5000"`
    Price       float64   `validate:"required,gte=0"`
    CreatedAt   time.Time `validate:"required"`
}

// Query (Request)
type GetProdctByIdQuery struct {
    ProductID uuid.UUID `validate:"required"`
}

And for response of these requests, we could create response messages as a response like this:

// Command (Response)
type CreateProductCommandResponse struct {
    ProductID uuid.UUID `json:"productId"`
}

// Query (Response)
type GetProductByIdQueryResponse struct {
    ProductID   uuid.UUID `json:"productId"`
    Name        string    `json:"name"`
    Description string    `json:"description"`
    Price       float64   `json:"price"`
    CreatedAt   time.Time `json:"createdAt"`
}

Creating Request Handler

For handling our requests, we should create a single request handler for each request. Each handler should implement the RequestHandler interface.

type RequestHandler[TRequest any, TResponse any] interface {
	Handle(ctx context.Context, request TRequest) (TResponse, error)
}

Here we Create request handler (command handler and query handler) for our requests, that implements above interface:

// Command Handler
type CreateProductCommandHandler struct {
	productRepository *repository.InMemoryProductRepository
}

func NewCreateProductCommandHandler(productRepository *repository.InMemoryProductRepository) *CreateProductCommandHandler {
	return &CreateProductCommandHandler{productRepository: productRepository}
}

func (c *CreateProductCommandHandler) Handle(ctx context.Context, command *CreateProductCommand) (*creatingProductDtos.CreateProductCommandResponse, error) {

	product := &models.Product{
		ProductID:   command.ProductID,
		Name:        command.Name,
		Description: command.Description,
		Price:       command.Price,
		CreatedAt:   command.CreatedAt,
	}

	createdProduct, err := c.productRepository.CreateProduct(ctx, product)
	if err != nil {
		return nil, err
	}

	response := &creatingProductDtos.CreateProductCommandResponse{ProductID: createdProduct.ProductID}

	return response, nil
}
// Query Handler
type GetProductByIdQueryHandler struct {
    productRepository *repository.InMemoryProductRepository
}

func NewGetProductByIdQueryHandler(productRepository *repository.InMemoryProductRepository) *GetProductByIdQueryHandler {
    return &GetProductByIdQueryHandler{productRepository: productRepository}
}

func (c *GetProductByIdQueryHandler) Handle(ctx context.Context, query *GetProductByIdQuery) (*gettingProductDtos.GetProdctByIdQueryResponse, error) {

    product, err := c.productRepository.GetProductById(ctx, query.ProductID)
    if err != nil {
        return nil, err
    }

    response := &gettingProductDtos.GetProdctByIdQueryResponse{
        ProductID:   product.ProductID,
        Name:        product.Name,
        Description: product.Description,
        Price:       product.Price,
        CreatedAt:   product.CreatedAt,
    }

    return response, nil
}

Note: In the cases we don't need a response from our request handler, we can use Unit type, that actually is an empty struct:.

Registering Request Handler to the MediatR

Before sending or dispatching our requests, we should register our request handlers to the MediatR.

Here we register our request handlers (command handler and query handler) to the MediatR:

// Registering `createProductCommandHandler` request handler for `CreateProductCommand` request to the MediatR
mediatr.RegisterHandler[*creatingProduct.CreateProductCommand, *creatingProductsDtos.CreateProductCommandResponse](createProductCommandHandler)

// Registering `getProductByIdQueryHandler` request handler for `GetProductByIdQuery` request to the MediatR
mediatr.RegisterHandler[*gettingProduct.GetProductByIdQuery, *gettingProductDtos.GetProdctByIdQueryResponse](getProductByIdQueryHandler)

Sending Request to the MediatR

Finally, send a message through the mediator.

Here we send our requests to the MediatR for dispatching them to the request handlers (command handler and query handler):

// Sending `CreateProductCommand` request to mediatr for dispatching to the `CreateProductCommandHandler` request handler
command := &CreateProductCommand{
    ProductID:   uuid.NewV4(),
    Name:        request.name,
    Description: request.description,
    Price:       request.price,
    CreatedAt:   time.Now(),
}

mediatr.Send[*CreateProductCommand, *creatingProductsDtos.CreateProductCommandResponse](ctx, command)
// Sending `GetProductByIdQuery` request to mediatr for dispatching to the `GetProductByIdQueryHandler` request handler
query := &GetProdctByIdQuery{
    ProductID:   uuid.NewV4()
}

mediatr.Send[*GetProductByIdQuery, *gettingProductsDtos.GetProductByIdQueryResponse](ctx, query)

Notification Strategy

The notification message, can have multiple handlers and doesn't have any response, and it can handle an event notification or notification in event driven architecture.

Creating a Notification Message

For creating a notification (event), that has multiple handlers and doesn't have any response, we could create an event notification as a notification like this:

// Event (Notification)
type ProductCreatedEvent struct {
    ProductID uuid.UUID   `json:"productId"`
    Name        string    `json:"name"`
    Description string    `json:"description"`
    Price       float64   `json:"price"`
    CreatedAt   time.Time `json:"createdAt"`
}

This event doesn't have any response.

Creating Notification Handlers

For handling our notification, we can create multiple notification handlers for each notification event. Each handler should implement the NotificationHandler interface.

type NotificationHandler[TNotification any] interface {
    Handle(ctx context.Context, notification TNotification) error
}

Here we Create multiple notification event handler for our notification, that implements above interface:

// Notification Event Handler1
type ProductCreatedEventHandler1 struct {
}

func (c *ProductCreatedEventHandler1) Handle(ctx context.Context, event *ProductCreatedEvent) error {
//Do something with the event here !
    return nil
}
// Notification Event Handler2
type ProductCreatedEventHandler2 struct {
}

func (c *ProductCreatedEventHandler2) Handle(ctx context.Context, event *ProductCreatedEvent) error {
//Do something with the event here !
    return nil
}

Registering Notification Handlers to the MediatR

Before publishing our notifications, we should register our notification handlers to the MediatR.

Here we register our notification handlers to the MediatR:

// Registering `notificationHandler1`, `notificationHandler2` notification handler for `ProductCreatedEvent` notification event to the MediatR
notificationHandler1 := &ProductCreatedEventHandler1{}
notificationHandler2 := &ProductCreatedEventHandler2{}

mediatr.RegisterNotificationHandlers[*events.ProductCreatedEvent](notificationHandler1, notificationHandler2)

Publishing Notification to the MediatR

Finally, publish a notification event through the mediator.

Here we publish our notification to the MediatR for dispatching them to the notification handlers:

// Publishing `ProductCreatedEvent` notification to mediatr for dispatching to the `ProductCreatedEventHandler1`, `ProductCreatedEventHandler2` notification handlers
productCreatedEvent := 	&ProductCreatedEvent {
    ProductID:   createdProduct.ProductID,
    Name:        createdProduct.Name,
    Price:       createdProduct.Price,
    CreatedAt:   createdProduct.CreatedAt,
    Description: createdProduct.Description,
}
	
mediatr.Publish[*events.ProductCreatedEvent](ctx, productCreatedEvent)

โš’๏ธ Using Pipeline Behaviors

Sometimes we need to add some cross-cutting concerns before after running our request handlers like logging, metrics, circuit breaker, retry, etc. In this case we can use PipelineBehavior. It is actually is like a middleware or decorator pattern.

These behaviors will execute before or after running our request handlers with calling Send method for a request on the mediatr.

Creating Pipeline Behavior

For creating a pipeline behaviour we should implement the PipelineBehavior interface:

type PipelineBehavior interface {
	Handle(ctx context.Context, request interface{}, next RequestHandlerFunc) (interface{}, error)
}

The request parameter is the request object passed in through Send method of mediatr, while the next parameter is a continuation for the next action in the behavior chain and its type is RequestHandlerFunc.

Here is an example of a pipeline behavior:

type RequestLoggerBehaviour struct {
}

func (r *RequestLoggerBehaviour) Handle(ctx context.Context, request interface{}, next mediatr.RequestHandlerFunc) (interface{}, error) {
	log.Printf("logging some stuff before handling the request")

	response, err := next()
	if err != nil {
		return nil, err
	}

	log.Println("logging some stuff after handling the request")

	return response, nil
}

In our defined behavior, we need to call next parameter that call next action in the behavior chain, if there aren't any other behaviours next will call our actual request handler and return the response. We can do something before of after of calling next action in the behavior chain.

Registering Pipeline Behavior to the MediatR

For registering our pipeline behavior to the MediatR, we should use RegisterPipelineBehaviors method:

loggerPipeline := &behaviours.RequestLoggerBehaviour{}
err = mediatr.RegisterRequestPipelineBehaviors(loggerPipeline)

More Repositories

1

awesome-software-architecture

๐Ÿš€ A curated list of awesome articles, videos, and other resources to learn and practice software architecture, patterns, and principles.
7,836
star
2

go-food-delivery-microservices

๐Ÿ• A practical and imaginary food delivery microservices, built with golang, domain-driven design, cqrs, event sourcing, vertical slice architecture, event-driven architecture, and the latest technologies.
Go
854
star
3

food-delivery-microservices

๐Ÿ” A practical and imaginary food delivery microservices, built with .Net 8, MassTransit, Domain-Driven Design, CQRS, Vertical Slice Architecture, Event-Driven Architecture, and the latest technologies.
C#
790
star
4

awesome-go-education

A curated list of awesome articles and resources for learning and practicing Go and its related technologies.
Go
371
star
5

food-delivery-modular-monolith

๐ŸŒญ A practical and imaginary food and grocery delivery modular monolith, built with .Net 8, Domain-Driven Design, CQRS, Vertical Slice Architecture, Event-Driven Architecture, and the latest technologies.
C#
309
star
6

ecommerce-microservices

๐Ÿ›๏ธ A practical e-commerce Microservices based on Domain Driven Design, Vertical Slice Architecture, CQRS pattern, Event Driven Architecture.
C#
164
star
7

vertical-slice-api-template

An asp.net core template based on .Net 8, Vertical Slice Architecture, CQRS, Minimal APIs, API Versioning and Swagger.
C#
139
star
8

awesome-dotnet-async

A curated list of awesome articles and resources to learning and practicing about async, threading, and channels in .Net platform. ๐Ÿ˜‰
C#
91
star
9

micro-bootstrap

A Full Stack framework written in .NET Core to speed up your development process in microservices and modular monolith apps. It gathers most widely used frameworks in .NET world and pack them into a simple bootstrap package.
C#
77
star
10

go-vertical-slice-template

A Golang boilerplate template, based on Vertical Slice Architecture and CQRS pattern with using Echo, Gorm, Zap, Viper, MediatR for CQRS and sarulabs/di for Dependency Injection.
Go
71
star
11

game-leaderboard-microservices

๐ŸŽฎ Implementation of an imaginary Game Leader Board application, based on Microservices Architecture, Event Driven Architecture, Vertical Slice Architecture, Event Sourcing with EventStoreDB, Redis SortedSet, Redis Pub/Sub, SignalR and .Net 8.
C#
48
star
12

movie-search-application

๐ŸŽฌ A simple movie search app, built with .Net 8, Vertical Slice Architecture and using TMDB APIs and YouTube APIs for searching and details of the movies.
C#
36
star
13

store-modular-monolith

๐Ÿ›’ Implementing an โ€œonline storeโ€ modular monolith application with domain-driven design and CQRS with using in-memory message broker based on .Net Core.
C#
34
star
14

distributed-chat-system

A simple distributed chat system based on .net core, signalr and NATS messaging queue.
C#
11
star
15

tdd-sample

A sample project demonstrating Test-Driven Development (TDD) using .Net 8 and Vertical Slice Architecture based on Minimal APIs in .NET Core
C#
10
star
16

Jwt-Authentication-Server

This repository is an Auth Server with jwt token for .net core
C#
8
star
17

awesome-dotnet-core-education

A curated list of awesome articles and resources for learning and practicing .Net Core and its related technologies.
C#
7
star
18

.Net-Core-CQRS-Shopping

This project in a shopping app using DDD, CQRS, Clean Architecture, .Net Core, Unit, and E2E Testing.
C#
6
star
19

mehdihadeli

5
star
20

ecommerce-microservices-wolverine

๐Ÿ›๏ธ A practical e-commerce microservices, built with .Net 8, Wolverine, Domain-Driven Design, CQRS, Vertical Slice Architecture, Event-Driven Architecture, and the latest technologies.
5
star
21

game-microservices

This project is a leader board gaming microservices with using some cloud native tools and technologies based on .net core.
C#
4
star
22

blog-samples

Sample codes for mehdihadeli's blog
C#
3
star
23

thesaurus-application

This application is a thesaurus app for defining and searching some words and their meanings, synonyms built on top of a vertical slice architecture, cqrs, mongodb and unit and integration testing in backend and using angular in frontend.
C#
3
star
24

design-pattern-samples

JavaScript
2
star
25

leetcode-playground

Leetcode problems and solutions in C# and Golang
Go
2
star
26

store-microservices

Implementing an โ€œonline storeโ€ microservices application with domain-driven design and CQRS with using the latest technologies and cloud native technologies.
2
star
27

dotnet-gitpod

Shell
1
star
28

envoy-samples

Go
1
star
29

Go-Bus

1
star
30

Vertical-Slice-Architecture-Sample

C#
1
star
31

partners-management

C#
1
star
32

energy-management

C#
1
star
33

release-note

Shell
1
star
34

ProblemSolving

C#
1
star
35

blog-comments

The blog comments discussions for https://dotnetuniversity.com
1
star
36

ecommerce-microservices-aspire

๐Ÿ›๏ธ A practical e-commerce microservices, built with .Net 8, Microsoft Aspire, Domain-Driven Design, CQRS, Vertical Slice Architecture, Event-Driven Architecture, and the latest technologies.
1
star