Skip to content

Commit 896d0bb

Browse files
committed
Add lesson08 / video01
1 parent 5515123 commit 896d0bb

File tree

30 files changed

+1249
-0
lines changed

30 files changed

+1249
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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/08-01-reading-env-variables/internal"
10+
"github.com/CodelyTV/go-hexagonal_http_api-course/08-01-reading-env-variables/internal/creating"
11+
"github.com/CodelyTV/go-hexagonal_http_api-course/08-01-reading-env-variables/internal/increasing"
12+
"github.com/CodelyTV/go-hexagonal_http_api-course/08-01-reading-env-variables/internal/platform/bus/inmemory"
13+
"github.com/CodelyTV/go-hexagonal_http_api-course/08-01-reading-env-variables/internal/platform/server"
14+
"github.com/CodelyTV/go-hexagonal_http_api-course/08-01-reading-env-variables/internal/platform/storage/mysql"
15+
_ "github.com/go-sql-driver/mysql"
16+
"github.com/kelseyhightower/envconfig"
17+
)
18+
19+
func Run() error {
20+
var cfg config
21+
err := envconfig.Process("MOOC", &cfg)
22+
if err != nil {
23+
return err
24+
}
25+
26+
mysqlURI := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", cfg.DbUser, cfg.DbPass, cfg.DbHost, cfg.DbPort, cfg.DbName)
27+
db, err := sql.Open("mysql", mysqlURI)
28+
if err != nil {
29+
return err
30+
}
31+
32+
var (
33+
commandBus = inmemory.NewCommandBus()
34+
eventBus = inmemory.NewEventBus()
35+
)
36+
37+
courseRepository := mysql.NewCourseRepository(db, cfg.DbTimeout)
38+
39+
creatingCourseService := creating.NewCourseService(courseRepository, eventBus)
40+
increasingCourseService := increasing.NewCourseCounterService()
41+
42+
createCourseCommandHandler := creating.NewCourseCommandHandler(creatingCourseService)
43+
commandBus.Register(creating.CourseCommandType, createCourseCommandHandler)
44+
45+
eventBus.Subscribe(
46+
mooc.CourseCreatedEventType,
47+
creating.NewIncreaseCoursesCounterOnCourseCreated(increasingCourseService),
48+
)
49+
50+
ctx, srv := server.New(context.Background(), cfg.Host, cfg.Port, cfg.ShutdownTimeout, commandBus)
51+
return srv.Run(ctx)
52+
}
53+
54+
type config struct {
55+
// Server configuration
56+
Host string `default:"localhost"`
57+
Port uint `default:"8080"`
58+
ShutdownTimeout time.Duration `default:"10s"`
59+
// Database configuration
60+
DbUser string `default:"codely"`
61+
DbPass string `default:"codely"`
62+
DbHost string `default:"localhost"`
63+
DbPort uint `default:"3306"`
64+
DbName string `default:"codely"`
65+
DbTimeout time.Duration `default:"5s"`
66+
}
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/08-01-reading-env-variables/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/08-01-reading-env-variables/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/08-01-reading-env-variables/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/08-01-reading-env-variables/internal"
8+
"github.com/CodelyTV/go-hexagonal_http_api-course/08-01-reading-env-variables/internal/increasing"
9+
"github.com/CodelyTV/go-hexagonal_http_api-course/08-01-reading-env-variables/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/08-01-reading-env-variables/internal"
7+
"github.com/CodelyTV/go-hexagonal_http_api-course/08-01-reading-env-variables/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)