Skip to content

Commit d06f998

Browse files
committed
Add lesson07 / video02
1 parent 89fc513 commit d06f998

File tree

28 files changed

+1247
-0
lines changed

28 files changed

+1247
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package bootstrap
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"time"
8+
9+
mooc "github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/internal"
10+
"github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/internal/creating"
11+
"github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/internal/increasing"
12+
"github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/internal/platform/bus/inmemory"
13+
"github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/internal/platform/server"
14+
"github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/internal/platform/storage/mysql"
15+
_ "github.com/go-sql-driver/mysql"
16+
)
17+
18+
const (
19+
host = "localhost"
20+
port = 8080
21+
shutdownTimeout = 10 * time.Second
22+
23+
dbUser = "codely"
24+
dbPass = "codely"
25+
dbHost = "localhost"
26+
dbPort = "3306"
27+
dbName = "codely"
28+
dbTimeout = 5 * time.Second
29+
)
30+
31+
func Run() error {
32+
mysqlURI := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUser, dbPass, dbHost, dbPort, dbName)
33+
db, err := sql.Open("mysql", mysqlURI)
34+
if err != nil {
35+
return err
36+
}
37+
38+
var (
39+
commandBus = inmemory.NewCommandBus()
40+
eventBus = inmemory.NewEventBus()
41+
)
42+
43+
courseRepository := mysql.NewCourseRepository(db, dbTimeout)
44+
45+
creatingCourseService := creating.NewCourseService(courseRepository, eventBus)
46+
increasingCourseCounterService := increasing.NewCourseCounterService()
47+
48+
createCourseCommandHandler := creating.NewCourseCommandHandler(creatingCourseService)
49+
commandBus.Register(creating.CourseCommandType, createCourseCommandHandler)
50+
51+
eventBus.Subscribe(
52+
mooc.CourseCreatedEventType,
53+
creating.NewIncreaseCoursesCounterOnCourseCreated(increasingCourseCounterService),
54+
)
55+
56+
ctx, srv := server.New(context.Background(), host, port, shutdownTimeout, commandBus)
57+
return srv.Run(ctx)
58+
}
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/07-02-domain-events-subscriber/cmd/api/bootstrap"
7+
)
8+
9+
func main() {
10+
if err := bootstrap.Run(); err != nil {
11+
log.Fatal(err)
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package mooc
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
8+
"github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/kit/event"
9+
"github.com/google/uuid"
10+
)
11+
12+
var ErrInvalidCourseID = errors.New("invalid Course ID")
13+
14+
// CourseID represents the course unique identifier.
15+
type CourseID struct {
16+
value string
17+
}
18+
19+
// NewCourseID instantiate the VO for CourseID
20+
func NewCourseID(value string) (CourseID, error) {
21+
v, err := uuid.Parse(value)
22+
if err != nil {
23+
return CourseID{}, fmt.Errorf("%w: %s", ErrInvalidCourseID, value)
24+
}
25+
26+
return CourseID{
27+
value: v.String(),
28+
}, nil
29+
}
30+
31+
// String type converts the CourseID into string.
32+
func (id CourseID) String() string {
33+
return id.value
34+
}
35+
36+
var ErrEmptyCourseName = errors.New("the field Course Name can not be empty")
37+
38+
// CourseName represents the course name.
39+
type CourseName struct {
40+
value string
41+
}
42+
43+
// NewCourseName instantiate VO for CourseName
44+
func NewCourseName(value string) (CourseName, error) {
45+
if value == "" {
46+
return CourseName{}, ErrEmptyCourseName
47+
}
48+
49+
return CourseName{
50+
value: value,
51+
}, nil
52+
}
53+
54+
// String type converts the CourseName into string.
55+
func (name CourseName) String() string {
56+
return name.value
57+
}
58+
59+
var ErrEmptyDuration = errors.New("the field Duration can not be empty")
60+
61+
// CourseDuration represents the course duration.
62+
type CourseDuration struct {
63+
value string
64+
}
65+
66+
func NewCourseDuration(value string) (CourseDuration, error) {
67+
if value == "" {
68+
return CourseDuration{}, ErrEmptyDuration
69+
}
70+
71+
return CourseDuration{
72+
value: value,
73+
}, nil
74+
}
75+
76+
// String type converts the CourseDuration into string.
77+
func (duration CourseDuration) String() string {
78+
return duration.value
79+
}
80+
81+
// Course is the data structure that represents a course.
82+
type Course struct {
83+
id CourseID
84+
name CourseName
85+
duration CourseDuration
86+
87+
events []event.Event
88+
}
89+
90+
// CourseRepository defines the expected behaviour from a course storage.
91+
type CourseRepository interface {
92+
Save(ctx context.Context, course Course) error
93+
}
94+
95+
//go:generate mockery --case=snake --outpkg=storagemocks --output=platform/storage/storagemocks --name=CourseRepository
96+
97+
// NewCourse creates a new course.
98+
func NewCourse(id, name, duration string) (Course, error) {
99+
idVO, err := NewCourseID(id)
100+
if err != nil {
101+
return Course{}, err
102+
}
103+
104+
nameVO, err := NewCourseName(name)
105+
if err != nil {
106+
return Course{}, err
107+
}
108+
109+
durationVO, err := NewCourseDuration(duration)
110+
if err != nil {
111+
return Course{}, err
112+
}
113+
114+
course := Course{
115+
id: idVO,
116+
name: nameVO,
117+
duration: durationVO,
118+
}
119+
course.Record(NewCourseCreatedEvent(idVO.String(), nameVO.String(), durationVO.String()))
120+
return course, nil
121+
}
122+
123+
// ID returns the course unique identifier.
124+
func (c Course) ID() CourseID {
125+
return c.id
126+
}
127+
128+
// Name returns the course name.
129+
func (c Course) Name() CourseName {
130+
return c.name
131+
}
132+
133+
// Duration returns the course duration.
134+
func (c Course) Duration() CourseDuration {
135+
return c.duration
136+
}
137+
138+
// Record records a new domain event.
139+
func (c *Course) Record(evt event.Event) {
140+
c.events = append(c.events, evt)
141+
}
142+
143+
// PullEvents returns all the recorded domain events.
144+
func (c Course) PullEvents() []event.Event {
145+
evt := c.events
146+
c.events = []event.Event{}
147+
148+
return evt
149+
}
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/07-02-domain-events-subscriber/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+
"errors"
6+
7+
mooc "github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/internal"
8+
"github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/internal/increasing"
9+
"github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/kit/event"
10+
)
11+
12+
type IncreaseCoursesCounterOnCourseCreated struct {
13+
increasingService increasing.CourseCounterService
14+
}
15+
16+
func NewIncreaseCoursesCounterOnCourseCreated(increaserService increasing.CourseCounterService) IncreaseCoursesCounterOnCourseCreated {
17+
return IncreaseCoursesCounterOnCourseCreated{
18+
increasingService: increaserService,
19+
}
20+
}
21+
22+
func (e IncreaseCoursesCounterOnCourseCreated) Handle(_ context.Context, evt event.Event) error {
23+
courseCreatedEvt, ok := evt.(mooc.CourseCreatedEvent)
24+
if !ok {
25+
return errors.New("unexpected event")
26+
}
27+
28+
return e.increasingService.Increase(courseCreatedEvt.ID())
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package creating
2+
3+
import (
4+
"context"
5+
6+
mooc "github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/internal"
7+
"github.com/CodelyTV/go-hexagonal_http_api-course/07-02-domain-events-subscriber/kit/event"
8+
)
9+
10+
// CourseService is the default CourseService interface
11+
// implementation returned by creating.NewCourseService.
12+
type CourseService struct {
13+
courseRepository mooc.CourseRepository
14+
eventBus event.Bus
15+
}
16+
17+
// NewCourseService returns the default Service interface implementation.
18+
func NewCourseService(courseRepository mooc.CourseRepository, eventBus event.Bus) CourseService {
19+
return CourseService{
20+
courseRepository: courseRepository,
21+
eventBus: eventBus,
22+
}
23+
}
24+
25+
// CreateCourse implements the creating.CourseService interface.
26+
func (s CourseService) CreateCourse(ctx context.Context, id, name, duration string) error {
27+
course, err := mooc.NewCourse(id, name, duration)
28+
if err != nil {
29+
return err
30+
}
31+
32+
if err := s.courseRepository.Save(ctx, course); err != nil {
33+
return err
34+
}
35+
36+
return s.eventBus.Publish(ctx, course.PullEvents())
37+
}

0 commit comments

Comments
 (0)