-
Notifications
You must be signed in to change notification settings - Fork 8
/
svc.go
137 lines (114 loc) · 2.74 KB
/
svc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package allsrv
import (
"context"
"time"
"github.com/gofrs/uuid"
"github.com/jsteenb2/errors"
)
// Foo represents the foo domain entity.
type Foo struct {
ID string
Name string
Note string
CreatedAt time.Time
UpdatedAt time.Time
}
// OK validates the fields are provided.
func (f Foo) OK() error {
if f.Name == "" {
return InvalidErr("name is required")
}
return nil
}
// FooUpd is a record for updating an existing foo.
type FooUpd struct {
ID string
Name *string
Note *string
}
// SVC defines the service behavior.
type SVC interface {
CreateFoo(ctx context.Context, f Foo) (Foo, error)
ReadFoo(ctx context.Context, id string) (Foo, error)
UpdateFoo(ctx context.Context, f FooUpd) (Foo, error)
DelFoo(ctx context.Context, id string) error
}
// Service dependencies
type (
// DB represents the foo persistence layer.
DB interface {
CreateFoo(ctx context.Context, f Foo) error
ReadFoo(ctx context.Context, id string) (Foo, error)
UpdateFoo(ctx context.Context, f Foo) error
DelFoo(ctx context.Context, id string) error
}
)
// Service is the home for business logic of the foo domain.
type Service struct {
db DB
idFn func() string
nowFn func() time.Time
}
func WithSVCIDFn(fn func() string) func(*Service) {
return func(s *Service) {
s.idFn = fn
}
}
func WithSVCNowFn(fn func() time.Time) func(*Service) {
return func(s *Service) {
s.nowFn = fn
}
}
func NewService(db DB, opts ...func(*Service)) *Service {
s := Service{
db: db,
idFn: func() string { return uuid.Must(uuid.NewV4()).String() },
nowFn: func() time.Time { return time.Now().UTC() },
}
for _, o := range opts {
o(&s)
}
return &s
}
func (s *Service) CreateFoo(ctx context.Context, f Foo) (Foo, error) {
if err := f.OK(); err != nil {
return Foo{}, errors.Wrap(err)
}
now := s.nowFn()
f.ID, f.CreatedAt, f.UpdatedAt = s.idFn(), now, now
if err := s.db.CreateFoo(ctx, f); err != nil {
return Foo{}, errors.Wrap(err)
}
return f, nil
}
func (s *Service) ReadFoo(ctx context.Context, id string) (Foo, error) {
if id == "" {
return Foo{}, errIDRequired
}
f, err := s.db.ReadFoo(ctx, id)
return f, errors.Wrap(err)
}
func (s *Service) UpdateFoo(ctx context.Context, f FooUpd) (Foo, error) {
existing, err := s.db.ReadFoo(ctx, f.ID)
if err != nil {
return Foo{}, errors.Wrap(err)
}
if newName := f.Name; newName != nil {
existing.Name = *newName
}
if newNote := f.Note; newNote != nil {
existing.Note = *newNote
}
existing.UpdatedAt = s.nowFn()
err = s.db.UpdateFoo(ctx, existing)
if err != nil {
return Foo{}, errors.Wrap(err)
}
return existing, nil
}
func (s *Service) DelFoo(ctx context.Context, id string) error {
if id == "" {
return errors.Wrap(errIDRequired)
}
return errors.Wrap(s.db.DelFoo(ctx, id))
}