Skip to content

Midt — is a zero dependency library that provides a mediator pattern that reduces coupling between program components by making them communicate indirectly, through a particular mediator struct.

License

Notifications You must be signed in to change notification settings

ssengalanto/midt

Repository files navigation

Midt workflow Go Report Card Coverage Status Go Reference License: MIT

Gopher Wiz

midt is a simple mediator pattern implementation in Go. It provides a mediator pattern that reduces coupling between program components by making them communicate indirectly through a particular mediator struct.

It supports request/response, commands and queries, notifications and events, and pipeline behaviours.

Install

go get -u github.com/ssengalanto/midt

Examples

See examples for more details.

Request/Response:

type CreatePersonRequest struct {
	Email     string `json:"email"`
	FirstName string `json:"firstName"`
	LastName  string `json:"lastName"`
}

type CreatePersonResponse struct {
	ID string `json:"id"`
}

type GetPersonRequest struct {
	ID string `json:"id"`
}

type GetPersonResponse struct {
	ID        string `json:"id"`
	Email     string `json:"email"`
	FirstName string `json:"firstName"`
	LastName  string `json:"lastName"`
}

Command

type CreatePersonCommand struct {
	Email     string
	FirstName string
	LastName  string
}

func NewCreatePersonCommand(email, fn, ln string) *CreatePersonCommand {
	return &CreatePersonCommand{
		Email:     email,
		FirstName: fn,
		LastName:  ln,
	}
}

Query

type GetPersonQuery struct {
	ID string
}

func NewGetPersonQuery(id string) *GetPersonQuery {
	return &GetPersonQuery{
		ID: id,
	}
}

Command(Request) Handler

type CreatePersonCommandHandler struct {
	personRepository memory.Repository
}

func NewCreatePersonCommandHandler(personRepository memory.Repository) *CreatePersonCommandHandler {
	return &CreatePersonCommandHandler{
		personRepository: personRepository,
	}
}

func (c *CreatePersonCommandHandler) Name() string {
	return fmt.Sprintf("%T", &CreatePersonCommand{})
}

func (c *CreatePersonCommandHandler) Handle(ctx context.Context, request any) (any, error) {
	cmd, ok := request.(*CreatePersonCommand)
	if !ok {
		return nil, errors.New("invalid command")
	}

	id, err := c.personRepository.Save(memory.CreatePersonRequest{
		Email:     cmd.Email,
		FirstName: cmd.FirstName,
		LastName:  cmd.LastName,
	})
	if err != nil {
		return nil, errors.New("person creation failed")
	}

	return dto.CreatePersonResponse{ID: id}, nil
}

Query(Request) Handler

type GetPersonQueryHandler struct {
	personRepository memory.Repository
}

func NewGetPersonQueryHandler(personRepository memory.Repository) *GetPersonQueryHandler {
	return &GetPersonQueryHandler{
		personRepository: personRepository,
	}
}

func (g *GetPersonQueryHandler) Name() string {
	return fmt.Sprintf("%T", &GetPersonQuery{})
}

func (g *GetPersonQueryHandler) Handle(ctx context.Context, request any) (any, error) {
	empty := dto.GetPersonResponse{}

	q, ok := request.(*GetPersonQuery)
	if !ok {
		return empty, errors.New("invalid command")
	}

	pers, err := g.personRepository.FindByID(q.ID)
	if err != nil {
		return empty, errors.New("pers creation failed")
	}

	return dto.GetPersonResponse{
		ID:        pers.ID,
		Email:     pers.Email,
		FirstName: pers.FirstName,
		LastName:  pers.LastName,
	}, nil
}

Event

type PersonCreatedEvent struct {
	ID        string
	Email     string
	FirstName string
	LastName  string
}

func NewPersonCreatedEvent(id, email, fn, ln string) *PersonCreatedEvent {
	return &PersonCreatedEvent{
		ID:        id,
		Email:     email,
		FirstName: fn,
		LastName:  ln,
	}
}

Event(Notification) Handler

type PersonCreatedEventHandler struct{}

func NewPersonCreatedEventHandler() *PersonCreatedEventHandler {
	return &PersonCreatedEventHandler{}
}

func (p *PersonCreatedEventHandler) Name() string {
	return fmt.Sprintf("%T", &PersonCreatedEvent{})
}

func (p *PersonCreatedEventHandler) Handle(ctx context.Context, notification any) error {
	evt := notification.(*PersonCreatedEvent)

	log.Printf("person created %+v", evt)

	return nil
}

Pipeline Behaviour

type LoggerBehaviour struct{}

func NewLoggerBehaviour() *LoggerBehaviour {
	return &LoggerBehaviour{}
}

func (l *LoggerBehaviour) Handle(
	ctx context.Context,
	request any,
	next midt.RequestHandlerFunc,
) (any, error) {
	log.Printf("executing %T", request)

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

	return res, nil
}

Request Handler Registry

func registerHandlers(repo memory.Repository, m *midt.Midt) {
	createPersonCommandHandler := command.NewCreatePersonCommandHandler(repo)
	err := m.RegisterRequestHandler(createPersonCommandHandler)
	if err != nil {
		log.Fatal(err)
	}

	getPersonQueryHandler := query.NewGetPersonQueryHandler(repo)
	err = m.RegisterRequestHandler(getPersonQueryHandler)
	if err != nil {
		log.Fatal(err)
	}
}

Notification Handler Registry

func registerNotifications(m *midt.Midt) {
	personCreatedNotificationHandler := event.NewPersonCreatedEventHandler()
	err := m.RegisterNotificationHandler(personCreatedNotificationHandler)
	if err != nil {
		log.Fatal(err)
	}
}

Pipeline Behaviour Registry

func registerBehaviours(m *midt.Midt) {
	loggerBehaviour := behaviour.NewLoggerBehaviour()
	err := m.RegisterPipelineBehaviour(loggerBehaviour)
	if err != nil {
		log.Fatal(err)
	}
}

Usage (HTTP Handler)

type CreatePersonHandler struct {
	m *midt.Midt
}

func NewCreatePersonHandler(m *midt.Midt) *CreatePersonHandler {
	return &CreatePersonHandler{m: m}
}

func (c *CreatePersonHandler) Handle(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(context.Background(), constants.RequestTimeout)
	defer cancel()

	if r.Method != http.MethodPost {
		log.Println("invalid http method")
		response.Error(w, http.StatusMethodNotAllowed)
		return
	}

	var req dto.CreatePersonRequest

	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		log.Println("invalid request body")
		response.Error(w, http.StatusBadRequest)
		return
	}

	// Executing CreatePersonCommand
	cmd := command.NewCreatePersonCommand(req.Email, req.FirstName, req.LastName)
	cmdres, err := c.m.Send(ctx, cmd)
	if err != nil {
		log.Printf("%T execution failed: %+v\n", cmd, err)
		response.Error(w, http.StatusInternalServerError)
		return
	}

	// Executing GetPersonQuery
	id := cmdres.(dto.CreatePersonResponse).ID
	q := query.NewGetPersonQuery(id)
	qres, err := c.m.Send(ctx, q)
	if err != nil {
		log.Printf("%T execution failed: %+v\n", q, err)
		response.Error(w, http.StatusInternalServerError)
		return
	}

	// Executing PersonCreatedEvent
	pers := qres.(dto.GetPersonResponse)
	evt := event.NewPersonCreatedEvent(pers.ID, pers.Email, pers.FirstName, pers.LastName)
	err = c.m.Publish(ctx, evt)
	if err != nil {
		log.Printf("%T execution failed: %+v\n", evt, err)
	}

	response.Success(w, http.StatusCreated, pers)
}

About

Midt — is a zero dependency library that provides a mediator pattern that reduces coupling between program components by making them communicate indirectly, through a particular mediator struct.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages