Skip to content

Commit 312f552

Browse files
committed
Add lesson04 / video03
1 parent dcc5aa3 commit 312f552

File tree

19 files changed

+717
-20
lines changed

19 files changed

+717
-20
lines changed

04-01-application-service/internal/creating/service.go

-10
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,3 @@ func (s CourseService) CreateCourse(ctx context.Context, id, name, duration stri
2727
}
2828
return s.courseRepository.Save(ctx, course)
2929
}
30-
31-
32-
33-
34-
35-
36-
37-
38-
39-

04-02-application-service-test/internal/creating/service.go

-10
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,3 @@ func (s CourseService) CreateCourse(ctx context.Context, id, name, duration stri
2727
}
2828
return s.courseRepository.Save(ctx, course)
2929
}
30-
31-
32-
33-
34-
35-
36-
37-
38-
39-
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package bootstrap
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
7+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/creating"
8+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/platform/bus/inmemory"
9+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/platform/server"
10+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/platform/storage/mysql"
11+
_ "github.com/go-sql-driver/mysql"
12+
)
13+
14+
const (
15+
host = "localhost"
16+
port = 8080
17+
18+
dbUser = "codely"
19+
dbPass = "codely"
20+
dbHost = "localhost"
21+
dbPort = "3306"
22+
dbName = "codely"
23+
)
24+
25+
func Run() error {
26+
mysqlURI := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUser, dbPass, dbHost, dbPort, dbName)
27+
db, err := sql.Open("mysql", mysqlURI)
28+
if err != nil {
29+
return err
30+
}
31+
32+
var (
33+
commandBus = inmemory.NewCommandBus()
34+
)
35+
36+
courseRepository := mysql.NewCourseRepository(db)
37+
38+
creatingCourseService := creating.NewCourseService(courseRepository)
39+
40+
createCourseCommandHandler := creating.NewCourseCommandHandler(creatingCourseService)
41+
commandBus.Register(creating.CourseCommandType, createCourseCommandHandler)
42+
43+
srv := server.New(host, port, commandBus)
44+
return srv.Run()
45+
}

04-03-command-bus/cmd/api/main.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package main
2+
3+
import (
4+
"log"
5+
6+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/cmd/api/bootstrap"
7+
)
8+
9+
func main() {
10+
if err := bootstrap.Run(); err != nil {
11+
log.Fatal(err)
12+
}
13+
}

04-03-command-bus/internal/course.go

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package mooc
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
8+
"github.com/google/uuid"
9+
)
10+
11+
var ErrInvalidCourseID = errors.New("invalid Course ID")
12+
13+
// CourseID represents the course unique identifier.
14+
type CourseID struct {
15+
value string
16+
}
17+
18+
// NewCourseID instantiate the VO for CourseID
19+
func NewCourseID(value string) (CourseID, error) {
20+
v, err := uuid.Parse(value)
21+
if err != nil {
22+
return CourseID{}, fmt.Errorf("%w: %s", ErrInvalidCourseID, value)
23+
}
24+
25+
return CourseID{
26+
value: v.String(),
27+
}, nil
28+
}
29+
30+
// String type converts the CourseID into string.
31+
func (id CourseID) String() string {
32+
return id.value
33+
}
34+
35+
var ErrEmptyCourseName = errors.New("the field Course Name can not be empty")
36+
37+
// CourseName represents the course name.
38+
type CourseName struct {
39+
value string
40+
}
41+
42+
// NewCourseName instantiate VO for CourseName
43+
func NewCourseName(value string) (CourseName, error) {
44+
if value == "" {
45+
return CourseName{}, ErrEmptyCourseName
46+
}
47+
48+
return CourseName{
49+
value: value,
50+
}, nil
51+
}
52+
53+
// String type converts the CourseName into string.
54+
func (name CourseName) String() string {
55+
return name.value
56+
}
57+
58+
var ErrEmptyDuration = errors.New("the field Duration can not be empty")
59+
60+
// CourseDuration represents the course duration.
61+
type CourseDuration struct {
62+
value string
63+
}
64+
65+
func NewCourseDuration(value string) (CourseDuration, error) {
66+
if value == "" {
67+
return CourseDuration{}, ErrEmptyDuration
68+
}
69+
70+
return CourseDuration{
71+
value: value,
72+
}, nil
73+
}
74+
75+
// String type converts the CourseDuration into string.
76+
func (duration CourseDuration) String() string {
77+
return duration.value
78+
}
79+
80+
// Course is the data structure that represents a course.
81+
type Course struct {
82+
id CourseID
83+
name CourseName
84+
duration CourseDuration
85+
}
86+
87+
// CourseRepository defines the expected behaviour from a course storage.
88+
type CourseRepository interface {
89+
Save(ctx context.Context, course Course) error
90+
}
91+
92+
//go:generate mockery --case=snake --outpkg=storagemocks --output=platform/storage/storagemocks --name=CourseRepository
93+
94+
// NewCourse creates a new course.
95+
func NewCourse(id, name, duration string) (Course, error) {
96+
idVO, err := NewCourseID(id)
97+
if err != nil {
98+
return Course{}, err
99+
}
100+
101+
nameVO, err := NewCourseName(name)
102+
if err != nil {
103+
return Course{}, err
104+
}
105+
106+
durationVO, err := NewCourseDuration(duration)
107+
if err != nil {
108+
return Course{}, err
109+
}
110+
111+
return Course{
112+
id: idVO,
113+
name: nameVO,
114+
duration: durationVO,
115+
}, nil
116+
}
117+
118+
// ID returns the course unique identifier.
119+
func (c Course) ID() CourseID {
120+
return c.id
121+
}
122+
123+
// Name returns the course name.
124+
func (c Course) Name() CourseName {
125+
return c.name
126+
}
127+
128+
// Duration returns the course duration.
129+
func (c Course) Duration() CourseDuration {
130+
return c.duration
131+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package creating
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/command"
8+
)
9+
10+
const CourseCommandType command.Type = "command.creating.course"
11+
12+
// CourseCommand is the command dispatched to create a new course.
13+
type CourseCommand struct {
14+
id string
15+
name string
16+
duration string
17+
}
18+
19+
// NewCourseCommand creates a new CourseCommand.
20+
func NewCourseCommand(id, name, duration string) CourseCommand {
21+
return CourseCommand{
22+
id: id,
23+
name: name,
24+
duration: duration,
25+
}
26+
}
27+
28+
func (c CourseCommand) Type() command.Type {
29+
return CourseCommandType
30+
}
31+
32+
// CourseCommandHandler is the command handler
33+
// responsible for creating courses.
34+
type CourseCommandHandler struct {
35+
service CourseService
36+
}
37+
38+
// NewCourseCommandHandler initializes a new CourseCommandHandler.
39+
func NewCourseCommandHandler(service CourseService) CourseCommandHandler {
40+
return CourseCommandHandler{
41+
service: service,
42+
}
43+
}
44+
45+
// Handle implements the command.Handler interface.
46+
func (h CourseCommandHandler) Handle(ctx context.Context, cmd command.Command) error {
47+
createCourseCmd, ok := cmd.(CourseCommand)
48+
if !ok {
49+
return errors.New("unexpected command")
50+
}
51+
52+
return h.service.CreateCourse(
53+
ctx,
54+
createCourseCmd.id,
55+
createCourseCmd.name,
56+
createCourseCmd.duration,
57+
)
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package creating
2+
3+
import (
4+
"context"
5+
6+
mooc "github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal"
7+
)
8+
9+
// CourseService is the default CourseService interface
10+
// implementation returned by creating.NewCourseService.
11+
type CourseService struct {
12+
courseRepository mooc.CourseRepository
13+
}
14+
15+
// NewCourseService returns the default Service interface implementation.
16+
func NewCourseService(courseRepository mooc.CourseRepository) CourseService {
17+
return CourseService{
18+
courseRepository: courseRepository,
19+
}
20+
}
21+
22+
// CreateCourse implements the creating.CourseService interface.
23+
func (s CourseService) CreateCourse(ctx context.Context, id, name, duration string) error {
24+
course, err := mooc.NewCourse(id, name, duration)
25+
if err != nil {
26+
return err
27+
}
28+
return s.courseRepository.Save(ctx, course)
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package inmemory
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/command"
8+
)
9+
10+
// CommandBus is an in-memory implementation of the command.Bus.
11+
type CommandBus struct {
12+
handlers map[command.Type]command.Handler
13+
}
14+
15+
// NewCommandBus initializes a new instance of CommandBus.
16+
func NewCommandBus() *CommandBus {
17+
return &CommandBus{
18+
handlers: make(map[command.Type]command.Handler),
19+
}
20+
}
21+
22+
// Dispatch implements the command.Bus interface.
23+
func (b *CommandBus) Dispatch(ctx context.Context, cmd command.Command) error {
24+
handler, ok := b.handlers[cmd.Type()]
25+
if !ok {
26+
return nil
27+
}
28+
29+
go func() {
30+
err := handler.Handle(ctx, cmd)
31+
if err != nil {
32+
log.Printf("Error while handling %s - %s\n", cmd.Type(), err)
33+
}
34+
35+
}()
36+
37+
return nil
38+
}
39+
40+
// Register implements the command.Bus interface.
41+
func (b *CommandBus) Register(cmdType command.Type, handler command.Handler) {
42+
b.handlers[cmdType] = handler
43+
}

0 commit comments

Comments
 (0)