Skip to content

04-03. Add fetching course command #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/go-hexagonal_http_api-course.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions 04-03-command-bus/cmd/api/bootstrap/boostrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bootstrap
import (
"database/sql"
"fmt"
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/fetching"

"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/creating"
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/platform/bus/inmemory"
Expand Down Expand Up @@ -30,16 +31,20 @@ func Run() error {
}

var (
commandBus = inmemory.NewCommandBus()
bus = inmemory.NewCommandBus()
)

courseRepository := mysql.NewCourseRepository(db)

creatingCourseService := creating.NewCourseService(courseRepository)
fetchingCourseService := fetching.NewCourseFetchingService(courseRepository)

createCourseCommandHandler := creating.NewCourseCommandHandler(creatingCourseService)
commandBus.Register(creating.CourseCommandType, createCourseCommandHandler)
fetchingCourseQueryHandler := fetching.NewCourseQueryHandler(fetchingCourseService)

srv := server.New(host, port, commandBus)
bus.RegisterCommandHandler(creating.CourseCommandType, createCourseCommandHandler)
bus.RegisterQueryHandler(fetching.CourseQueryType, fetchingCourseQueryHandler)

srv := server.New(host, port, bus)
return srv.Run()
}
1 change: 1 addition & 0 deletions 04-03-command-bus/internal/course.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ type Course struct {
// CourseRepository defines the expected behaviour from a course storage.
type CourseRepository interface {
Save(ctx context.Context, course Course) error
GetAll(ctx context.Context) ([]Course, error)
}

//go:generate mockery --case=snake --outpkg=storagemocks --output=platform/storage/storagemocks --name=CourseRepository
Expand Down
16 changes: 8 additions & 8 deletions 04-03-command-bus/internal/creating/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import (
"context"
"errors"

"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/command"
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/bus"
)

const CourseCommandType command.Type = "command.creating.course"
const CourseCommandType bus.Type = "bus.creating.course"

// CourseCommand is the command dispatched to create a new course.
// CourseCommand is the bus dispatched to create a new course.
type CourseCommand struct {
id string
name string
Expand All @@ -25,11 +25,11 @@ func NewCourseCommand(id, name, duration string) CourseCommand {
}
}

func (c CourseCommand) Type() command.Type {
func (c CourseCommand) Type() bus.Type {
return CourseCommandType
}

// CourseCommandHandler is the command handler
// CourseCommandHandler is the bus handler
// responsible for creating courses.
type CourseCommandHandler struct {
service CourseService
Expand All @@ -42,11 +42,11 @@ func NewCourseCommandHandler(service CourseService) CourseCommandHandler {
}
}

// Handle implements the command.Handler interface.
func (h CourseCommandHandler) Handle(ctx context.Context, cmd command.Command) error {
// Handle implements the bus.CommandHandler interface.
func (h CourseCommandHandler) Handle(ctx context.Context, cmd bus.Command) error {
createCourseCmd, ok := cmd.(CourseCommand)
if !ok {
return errors.New("unexpected command")
return errors.New("unexpected bus")
}

return h.service.CreateCourse(
Expand Down
1 change: 1 addition & 0 deletions 04-03-command-bus/internal/creating/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ func (s CourseService) CreateCourse(ctx context.Context, id, name, duration stri
}
return s.courseRepository.Save(ctx, course)
}

45 changes: 45 additions & 0 deletions 04-03-command-bus/internal/fetching/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package fetching

import (
"context"
"errors"
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/bus"
)

const CourseQueryType bus.Type = "bus.fetching.courses"

// CourseQuery is the bus dispatched to create a new course.
type CourseQuery struct {
}

// NewFetchCourseQuery creates a new CourseQuery.
func NewFetchCourseQuery() CourseQuery {
return CourseQuery{}
}

func (c CourseQuery) Type() bus.Type {
return CourseQueryType
}

// CourseQueryHandler is the bus handler
// responsible for fetching courses.
type CourseQueryHandler struct {
service FetchingCourseService
}

// NewCourseQueryHandler initializes a new NewCourseQueryHandler.
func NewCourseQueryHandler(service FetchingCourseService) CourseQueryHandler {
return CourseQueryHandler{
service: service,
}
}

// Handle implements the bus.QueryHandler interface.
func (h CourseQueryHandler) Handle(ctx context.Context, query bus.Query) (bus.QueryResponse, error) {
_, ok := query.(CourseQuery)
if !ok {
return nil, errors.New("unexpected bus")
}

return h.service.GetAll(ctx)
}
25 changes: 25 additions & 0 deletions 04-03-command-bus/internal/fetching/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package fetching

import (
"context"

mooc "github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal"
)

// CourseService is the default CourseService interface
// implementation returned by fetching.NewCourseFetchingService.
type FetchingCourseService struct {
courseRepository mooc.CourseRepository
}

// NewCourseService returns the default Service interface implementation.
func NewCourseFetchingService(courseRepository mooc.CourseRepository) FetchingCourseService {
return FetchingCourseService{
courseRepository: courseRepository,
}
}

// CreateCourse implements the creating.CourseService interface.
func (s FetchingCourseService) GetAll(ctx context.Context) ([]mooc.Course, error) {
return s.courseRepository.GetAll(ctx)
}
86 changes: 86 additions & 0 deletions 04-03-command-bus/internal/platform/bus/inmemory/bus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package inmemory

import (
"context"
"log"

"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/bus"
)

// Bus is an in-memory implementation of the bus.Bus.
type Bus struct {
commandsHandlers map[bus.Type]bus.CommandHandler
queryHandlers map[bus.Type]bus.QueryHandler
eventHandlers map[bus.Type]bus.EventHandler
}

// NewCommandBus initializes a new instance of Bus.
func NewCommandBus() *Bus {
return &Bus{
commandsHandlers: make(map[bus.Type]bus.CommandHandler),
queryHandlers: make(map[bus.Type]bus.QueryHandler),
eventHandlers: make(map[bus.Type]bus.EventHandler),
}
}

// DispatchCommand implements the bus.Bus interface.
func (b *Bus) DispatchCommand(ctx context.Context, cmd bus.Command) error {
handler, ok := b.commandsHandlers[cmd.Type()]
if !ok {
return nil
}

// Si dejo este asincronismo sucede que el endpoint del controller responde 201, cuando en verdad fallo. No tiene más sentido que el asincrnismo lo maneje el que lo llama?
/*go func() {
err := handler.Handle(ctx, cmd)
if err != nil {
log.Printf("Error while handling %s - %s\n", cmd.Type(), err)
}

}()*/

return handler.Handle(ctx, cmd)
}

// RegisterCommandHandler implements the bus.Bus interface.
func (b *Bus) RegisterCommandHandler(cmdType bus.Type, handler bus.CommandHandler) {
b.commandsHandlers[cmdType] = handler
}

// DispatchQuery implements the bus.Bus interface.
func (b *Bus) DispatchQuery(ctx context.Context, query bus.Query) (bus.QueryResponse, error) {
handler, ok := b.queryHandlers[query.Type()]
if !ok {
return nil, nil
}
// Como este es sincronico, decide el que lo usa si lo espera o no.
return handler.Handle(ctx, query)
}

// RegisterQueryHandler implements the bus.Bus interface.
func (b *Bus) RegisterQueryHandler(queryType bus.Type, handler bus.QueryHandler) {
b.queryHandlers[queryType] = handler
}

// DispatchEvent implements the bus.Bus interface.
func (b *Bus) DispatchEvent(ctx context.Context, event bus.Event) error {
handler, ok := b.eventHandlers[event.Type()]
if !ok {
return nil
}

go func() {
err := handler.Handle(ctx, event)
if err != nil {
log.Printf("Error while handling %s - %s\n", event.Type(), err)
}

}()

return nil
}

// RegisterEventHandler implements the bus.Bus interface.
func (b *Bus) RegisterEventHandler(cmdType bus.Type, handler bus.EventHandler) {
b.eventHandlers[cmdType] = handler
}
43 changes: 0 additions & 43 deletions 04-03-command-bus/internal/platform/bus/inmemory/command.go

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

mooc "github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal"
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/creating"
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/command"
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/bus"
"github.com/gin-gonic/gin"
)

Expand All @@ -17,15 +17,15 @@ type createRequest struct {
}

// CreateHandler returns an HTTP handler for courses creation.
func CreateHandler(commandBus command.Bus) gin.HandlerFunc {
func CreateHandler(bus bus.Bus) gin.HandlerFunc {
return func(ctx *gin.Context) {
var req createRequest
if err := ctx.BindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, err.Error())
return
}

err := commandBus.Dispatch(ctx, creating.NewCourseCommand(
err := bus.DispatchCommand(ctx, creating.NewCourseCommand(
req.ID,
req.Name,
req.Duration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ import (
"net/http/httptest"
"testing"

"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/command/commandmocks"
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/bus/busmocks"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

func TestHandler_Create(t *testing.T) {
commandBus := new(commandmocks.Bus)
commandBus.On(
"Dispatch",
bus := new(busmocks.Bus)
bus.On(
"DispatchCommand",
mock.Anything,
mock.AnythingOfType("creating.CourseCommand"),
).Return(nil)

gin.SetMode(gin.TestMode)
r := gin.New()
r.POST("/courses", CreateHandler(commandBus))
r.POST("/courses", CreateHandler(bus))

t.Run("given an invalid request it returns 400", func(t *testing.T) {
createCourseReq := createRequest{
Expand Down
Loading