Skip to content

Commit cef2f59

Browse files
committed
Added Command/Query decorators
1 parent 7aae9c3 commit cef2f59

26 files changed

Lines changed: 483 additions & 98 deletions
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package decorator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/sirupsen/logrus"
9+
)
10+
11+
func ApplyCommandDecorators[H any](handler CommandHandler[H], logger *logrus.Entry, metricsClient MetricsClient) CommandHandler[H] {
12+
return commandLoggingDecorator[H]{
13+
base: commandMetricsDecorator[H]{
14+
base: handler,
15+
client: metricsClient,
16+
},
17+
logger: logger,
18+
}
19+
}
20+
21+
type CommandHandler[C any] interface {
22+
Handle(ctx context.Context, cmd C) error
23+
}
24+
25+
func generateActionName(handler any) string {
26+
return strings.Split(fmt.Sprintf("%T", handler), ".")[1]
27+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package decorator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/sirupsen/logrus"
8+
)
9+
10+
type commandLoggingDecorator[C any] struct {
11+
base CommandHandler[C]
12+
logger *logrus.Entry
13+
}
14+
15+
func (d commandLoggingDecorator[C]) Handle(ctx context.Context, cmd C) (err error) {
16+
handlerType := generateActionName(cmd)
17+
18+
logger := d.logger.WithFields(logrus.Fields{
19+
"command": handlerType,
20+
"command_body": fmt.Sprintf("%#v", cmd),
21+
})
22+
23+
logger.Debug("Executing command")
24+
defer func() {
25+
if err == nil {
26+
logger.Info("Command executed successfully")
27+
} else {
28+
logger.WithError(err).Error("Failed to execute command")
29+
}
30+
}()
31+
32+
return d.base.Handle(ctx, cmd)
33+
}
34+
35+
type queryLoggingDecorator[C any, R any] struct {
36+
base QueryHandler[C, R]
37+
logger *logrus.Entry
38+
}
39+
40+
func (d queryLoggingDecorator[C, R]) Handle(ctx context.Context, cmd C) (result R, err error) {
41+
logger := d.logger.WithFields(logrus.Fields{
42+
"query": generateActionName(cmd),
43+
"query_body": fmt.Sprintf("%#v", cmd),
44+
})
45+
46+
logger.Debug("Executing query")
47+
defer func() {
48+
if err == nil {
49+
logger.Info("Query executed successfully")
50+
} else {
51+
logger.WithError(err).Error("Failed to execute query")
52+
}
53+
}()
54+
55+
return d.base.Handle(ctx, cmd)
56+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package decorator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"time"
8+
)
9+
10+
type MetricsClient interface {
11+
Inc(key string, value int)
12+
}
13+
14+
type commandMetricsDecorator[C any] struct {
15+
base CommandHandler[C]
16+
client MetricsClient
17+
}
18+
19+
func (d commandMetricsDecorator[C]) Handle(ctx context.Context, cmd C) (err error) {
20+
start := time.Now()
21+
22+
actionName := strings.ToLower(generateActionName(cmd))
23+
24+
defer func() {
25+
end := time.Since(start)
26+
27+
d.client.Inc(fmt.Sprintf("commands.%s.duration", actionName), int(end.Seconds()))
28+
29+
if err == nil {
30+
d.client.Inc(fmt.Sprintf("commands.%s.success", actionName), 1)
31+
} else {
32+
d.client.Inc(fmt.Sprintf("commands.%s.failure", actionName), 1)
33+
}
34+
}()
35+
36+
return d.base.Handle(ctx, cmd)
37+
}
38+
39+
type queryMetricsDecorator[C any, R any] struct {
40+
base QueryHandler[C, R]
41+
client MetricsClient
42+
}
43+
44+
func (d queryMetricsDecorator[C, R]) Handle(ctx context.Context, query C) (result R, err error) {
45+
start := time.Now()
46+
47+
actionName := strings.ToLower(generateActionName(query))
48+
49+
defer func() {
50+
end := time.Since(start)
51+
52+
d.client.Inc(fmt.Sprintf("querys.%s.duration", actionName), int(end.Seconds()))
53+
54+
if err == nil {
55+
d.client.Inc(fmt.Sprintf("querys.%s.success", actionName), 1)
56+
} else {
57+
d.client.Inc(fmt.Sprintf("querys.%s.failure", actionName), 1)
58+
}
59+
}()
60+
61+
return d.base.Handle(ctx, query)
62+
}

internal/common/decorator/query.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package decorator
2+
3+
import (
4+
"context"
5+
6+
"github.com/sirupsen/logrus"
7+
)
8+
9+
func ApplyQueryDecorators[H any, R any](handler QueryHandler[H, R], logger *logrus.Entry, metricsClient MetricsClient) QueryHandler[H, R] {
10+
return queryLoggingDecorator[H, R]{
11+
base: queryMetricsDecorator[H, R]{
12+
base: handler,
13+
client: metricsClient,
14+
},
15+
logger: logger,
16+
}
17+
}
18+
19+
type QueryHandler[Q any, R any] interface {
20+
Handle(ctx context.Context, q Q) (R, error)
21+
}

internal/common/metrics/dummy.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package metrics
2+
3+
type NoOp struct{}
4+
5+
func (d NoOp) Inc(_ string, _ int) {
6+
// todo - add some implementation!
7+
}

internal/trainer/app/command/cancel_training.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,40 @@ import (
44
"context"
55
"time"
66

7+
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/decorator"
78
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/errors"
89
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/trainer/domain/hour"
10+
"github.com/sirupsen/logrus"
911
)
1012

11-
type CancelTrainingHandler struct {
13+
type CancelTraining struct {
14+
Hour time.Time
15+
}
16+
17+
type CancelTrainingHandler decorator.CommandHandler[CancelTraining]
18+
19+
type cancelTrainingHandler struct {
1220
hourRepo hour.Repository
1321
}
1422

15-
func NewCancelTrainingHandler(hourRepo hour.Repository) CancelTrainingHandler {
23+
func NewCancelTrainingHandler(
24+
hourRepo hour.Repository,
25+
logger *logrus.Entry,
26+
metricsClient decorator.MetricsClient,
27+
) CancelTrainingHandler {
1628
if hourRepo == nil {
1729
panic("nil hourRepo")
1830
}
1931

20-
return CancelTrainingHandler{hourRepo: hourRepo}
32+
return decorator.ApplyCommandDecorators[CancelTraining](
33+
cancelTrainingHandler{hourRepo: hourRepo},
34+
logger,
35+
metricsClient,
36+
)
2137
}
2238

23-
func (h CancelTrainingHandler) Handle(ctx context.Context, hourToCancel time.Time) error {
24-
if err := h.hourRepo.UpdateHour(ctx, hourToCancel, func(h *hour.Hour) (*hour.Hour, error) {
39+
func (h cancelTrainingHandler) Handle(ctx context.Context, cmd CancelTraining) error {
40+
if err := h.hourRepo.UpdateHour(ctx, cmd.Hour, func(h *hour.Hour) (*hour.Hour, error) {
2541
if err := h.CancelTraining(); err != nil {
2642
return nil, err
2743
}

internal/trainer/app/command/make_hours_available.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,40 @@ import (
44
"context"
55
"time"
66

7+
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/decorator"
78
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/errors"
89
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/trainer/domain/hour"
10+
"github.com/sirupsen/logrus"
911
)
1012

11-
type MakeHoursAvailableHandler struct {
13+
type MakeHoursAvailable struct {
14+
Hours []time.Time
15+
}
16+
17+
type MakeHoursAvailableHandler decorator.CommandHandler[MakeHoursAvailable]
18+
19+
type makeHoursAvailableHandler struct {
1220
hourRepo hour.Repository
1321
}
1422

15-
func NewMakeHoursAvailableHandler(hourRepo hour.Repository) MakeHoursAvailableHandler {
23+
func NewMakeHoursAvailableHandler(
24+
hourRepo hour.Repository,
25+
logger *logrus.Entry,
26+
metricsClient decorator.MetricsClient,
27+
) MakeHoursAvailableHandler {
1628
if hourRepo == nil {
1729
panic("hourRepo is nil")
1830
}
1931

20-
return MakeHoursAvailableHandler{hourRepo: hourRepo}
32+
return decorator.ApplyCommandDecorators[MakeHoursAvailable](
33+
makeHoursAvailableHandler{hourRepo: hourRepo},
34+
logger,
35+
metricsClient,
36+
)
2137
}
2238

23-
func (c MakeHoursAvailableHandler) Handle(ctx context.Context, hours []time.Time) error {
24-
for _, hourToUpdate := range hours {
39+
func (c makeHoursAvailableHandler) Handle(ctx context.Context, cmd MakeHoursAvailable) error {
40+
for _, hourToUpdate := range cmd.Hours {
2541
if err := c.hourRepo.UpdateHour(ctx, hourToUpdate, func(h *hour.Hour) (*hour.Hour, error) {
2642
if err := h.MakeAvailable(); err != nil {
2743
return nil, err

internal/trainer/app/command/make_hours_unavailable.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,40 @@ import (
44
"context"
55
"time"
66

7+
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/decorator"
78
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/errors"
89
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/trainer/domain/hour"
10+
"github.com/sirupsen/logrus"
911
)
1012

11-
type MakeHoursUnavailableHandler struct {
13+
type MakeHoursUnavailable struct {
14+
Hours []time.Time
15+
}
16+
17+
type MakeHoursUnavailableHandler decorator.CommandHandler[MakeHoursUnavailable]
18+
19+
type makeHoursUnavailableHandler struct {
1220
hourRepo hour.Repository
1321
}
1422

15-
func NewMakeHoursUnavailableHandler(hourRepo hour.Repository) MakeHoursUnavailableHandler {
23+
func NewMakeHoursUnavailableHandler(
24+
hourRepo hour.Repository,
25+
logger *logrus.Entry,
26+
metricsClient decorator.MetricsClient,
27+
) MakeHoursUnavailableHandler {
1628
if hourRepo == nil {
1729
panic("hourRepo is nil")
1830
}
1931

20-
return MakeHoursUnavailableHandler{hourRepo: hourRepo}
32+
return decorator.ApplyCommandDecorators[MakeHoursUnavailable](
33+
makeHoursUnavailableHandler{hourRepo: hourRepo},
34+
logger,
35+
metricsClient,
36+
)
2137
}
2238

23-
func (c MakeHoursUnavailableHandler) Handle(ctx context.Context, hours []time.Time) error {
24-
for _, hourToUpdate := range hours {
39+
func (c makeHoursUnavailableHandler) Handle(ctx context.Context, cmd MakeHoursUnavailable) error {
40+
for _, hourToUpdate := range cmd.Hours {
2541
if err := c.hourRepo.UpdateHour(ctx, hourToUpdate, func(h *hour.Hour) (*hour.Hour, error) {
2642
if err := h.MakeNotAvailable(); err != nil {
2743
return nil, err

internal/trainer/app/command/schedule_training.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,40 @@ import (
44
"context"
55
"time"
66

7+
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/decorator"
78
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/errors"
89
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/trainer/domain/hour"
10+
"github.com/sirupsen/logrus"
911
)
1012

11-
type ScheduleTrainingHandler struct {
13+
type ScheduleTraining struct {
14+
Hour time.Time
15+
}
16+
17+
type ScheduleTrainingHandler decorator.CommandHandler[ScheduleTraining]
18+
19+
type scheduleTrainingHandler struct {
1220
hourRepo hour.Repository
1321
}
1422

15-
func NewScheduleTrainingHandler(hourRepo hour.Repository) ScheduleTrainingHandler {
23+
func NewScheduleTrainingHandler(
24+
hourRepo hour.Repository,
25+
logger *logrus.Entry,
26+
metricsClient decorator.MetricsClient,
27+
) ScheduleTrainingHandler {
1628
if hourRepo == nil {
1729
panic("nil hourRepo")
1830
}
1931

20-
return ScheduleTrainingHandler{hourRepo: hourRepo}
32+
return decorator.ApplyCommandDecorators[ScheduleTraining](
33+
scheduleTrainingHandler{hourRepo: hourRepo},
34+
logger,
35+
metricsClient,
36+
)
2137
}
2238

23-
func (h ScheduleTrainingHandler) Handle(ctx context.Context, hourToCancel time.Time) error {
24-
if err := h.hourRepo.UpdateHour(ctx, hourToCancel, func(h *hour.Hour) (*hour.Hour, error) {
39+
func (h scheduleTrainingHandler) Handle(ctx context.Context, cmd ScheduleTraining) error {
40+
if err := h.hourRepo.UpdateHour(ctx, cmd.Hour, func(h *hour.Hour) (*hour.Hour, error) {
2541
if err := h.ScheduleTraining(); err != nil {
2642
return nil, err
2743
}

0 commit comments

Comments
 (0)