Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
roblaszczak
TACTICAL DDD
PATTERNS IN GOLANG
roblaszczak
Hello!
roblaszczak
Hello!
● Co-founder @Tree Dots Labs
roblaszczak
Hello!
● Co-founder @Tree Dots Labs
● Golang, Python, PHP
roblaszczak
TACTICAL DDD
PATTERNS IN GOLANG
roblaszczak
6
+ DDD = ?
roblaszczak
7
roblaszczak
8
roblaszczak
9
roblaszczak
10
Dlaczego to nie działa tak jak
ustaliliśmy?
roblaszczak
11
Myśleliśmy że wiecie że to tak
będzie działać
roblaszczak
12
Czy ktoś w ogóle wie jak to
działa?
roblaszczak
13
Poczekaj, sprawdzę w tym
handlerze na 1k linii i w
triggerach DB
roblaszczak
Nie do końca to mieliśmy na
myśli mówiąc “klient kupił
produkt”...
14
roblaszczak
15
roblaszczak
16
roblaszczak
17
roblaszczak
18
+ DDD =
roblaszczak
19
DOKŁADNE
ODZWIERCIEDLENIE
LOGIKI BIZNESOWEJ
roblaszczak
20
KOD KTÓRY MOŻNA
POKAZAĆ EKSPERTOWI
DOMENOWEMU
roblaszczak
21
BEZPIECZNE
WPROWADZANIE
ZNACZĄCYCH ZMIAN W
LOGICE BIZNESOWEJ
roblaszczak
22
LEGENDARNY
SAMODOKUMENTUJĄCY
SIĘ KOD
roblaszczak
23
UPORZĄDKOWANIE
NAZEWNICTWA
roblaszczak
24
WERYFIKACJA LOGIKI
DOMENOWEJ PRZED
ROZPOCZĘCIEM
IMPLEMENTACJI
roblaszczak
25
LITE DDD =
roblaszczak
26
EVENT STORMING
roblaszczak
27
HOT SPOTS
roblaszczak
28
ENTITY
roblaszczak
29
Nurse administer standard flu
vaccine dose to adult patient.
roblaszczak
30
patient := patient.NewPatient()
patient.SetShotType(vaccine.Flu)
patient.SetDose(10)
patient.SetNurse(nurse)
roblaszczak
31
patient := patient.NewPatient()
patient.SetShotType(vaccine.Flu)
patient.SetDose(10)
patient.SetNurse(nurse)
roblaszczak
32
patient.GiveFluShot()
roblaszczak
33
patient.GiveFluShot()
roblaszczak
34
vaccine :=
vaccine.StandardAdultFluDose()
nurse.AdministerFluVaccine(
patient,
vaccine,
)
roblaszczak
35
Nurse administer standard flu
vaccine dose to adult patient.
roblaszczak
36
vaccine :=
vaccine.StandardAdultFluDose()
nurse.AdministerFluVaccine(
patient,
vaccine,
)
roblaszczak
37
roblaszczak
38
roblaszczak
39
package backlog
type ItemID struct{ id.ID }
type Item struct {
id ItemID
name string
// ...
}
roblaszczak
40
package backlog
type ItemID struct{ id.ID }
type Item struct {
id ItemID
name string
// ...
}
roblaszczak
41
type ItemID struct{ id.ID }
roblaszczak
42
package id
type ID struct {
u ulid.ULID
valid bool
}
func New() ID {
return ID{ulid.MustNew(ulid.Timestamp(...
roblaszczak
43
type ItemID id.ID
roblaszczak
44
type ItemID id.ID
roblaszczak
45
type ItemID struct{ id.ID }
roblaszczak
46
func NewItem(id ItemID, name string) (*Item, error) {
if id.Empty() {
return nil, ErrItemEmptyID
}
i := &It...
roblaszczak
47
func (i *Item) ChangeName(name string) error
{
if name == "" {
return ErrItemEmptyName
}
i.name = name
retu...
roblaszczak
48
var (
ErrItemEmptyName =
domain.NewIllegalStateError("name cannot be empty")
)
roblaszczak
49
func (i *Item) changeName(name string)
error {
if name == "" {
return ErrItemEmptyName
}
i.name = name
retu...
roblaszczak
50
func NewItem(id ItemID, name string) (*Item, error) {
if id.Empty() {
return nil, ErrItemEmptyID
}
i := &It...
roblaszczak
51
{
"valid_item",
args{backlog.ItemID{id.New()}, "foo"},
nil,
}, {
"missing_id",
args{backlog.ItemID{}, "foo"...
roblaszczak
52
{
"valid_item",
args{backlog.ItemID{id.New()}, "foo"},
nil,
}, {
"missing_id",
args{backlog.ItemID{}, "foo"...
roblaszczak
53
func TestNewItem(t *testing.T) {
type args struct {
id backlog.ItemID
name string
}
tests := []struct {
nam...
roblaszczak
54
t.Run(tt.name, func(t *testing.T) {
_, err := backlog.NewItem(
tt.args.id, tt.args.name
)
assert.Equal(t, t...
roblaszczak
55
Lifetip: CTRL + Shift + T
roblaszczak
56
package backlog_test
roblaszczak
57
tests := []struct {
name string
args args
wantErr error
}{
roblaszczak
58
func TestItem_ChangeName(t *testing.T) {
roblaszczak
59
roblaszczak
60
{
name: "scheduled_for_release",
item: createTestScheduledForReleaseItem(t),
wantErr: nil,
},
roblaszczak
61
roblaszczak
62
{
name: "scheduled_for_release",
item: createTestScheduledForReleaseItem(t),
wantErr: nil,
},
roblaszczak
63
{
name: "not_scheduled_for_release",
item: createTestItem(t),
wantErr: backlog.ErrMustBeScheduledToCommit,
...
roblaszczak
64
{
name: "already_commited_to_sprint",
item: createTestCommitedToSprintItem(t),
wantErr: nil,
},
roblaszczak
65
func createTestItem(t *testing.T) *backlog.Item {
i, err := backlog.NewItem(
backlog.ItemID{id.New()},
"Foo...
roblaszczak
66
func createTestItem(t *testing.T) *backlog.Item {
i, err := backlog.NewItem(
backlog.ItemID{id.New()},
"Foo...
roblaszczak
67
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
roblaszczak
68
func createTestScheduledForReleaseItem(t *testing.T)
*backlog.Item {
i := createTestItem(t)
err := i.Schedu...
roblaszczak
69
func createTestCommitedToSprintItem(t *testing.T)
*backlog.Item {
i := createTestScheduledForReleaseItem(t)...
roblaszczak
70
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := createTestSprint(t)
initialSprintID := ...
roblaszczak
71
if tt.wantErr == nil {
assert.Equal(t, s.ID(), tt.i.CommitedSprintID())
assert.NotEqual(t, s.ID(), initialS...
roblaszczak
72
if tt.wantErr == nil {
assert.Equal(t, s.ID(), tt.i.CommitedSprintID())
assert.NotEqual(t, s.ID(), initialS...
roblaszczak
73
if tt.wantErr == nil {
assert.Equal(t, s.ID(), tt.i.CommitedSprintID())
assert.NotEqual(t, s.ID(), initialS...
roblaszczak
74
if tt.wantErr == nil {
assert.Equal(t, s.ID(), tt.i.CommitedSprintID())
assert.NotEqual(t, s.ID(), initialS...
roblaszczak
75
func (i *Item) CommitToSprint(s *sprint.Sprint) error {
if !i.IsScheduledForRelease() {
return ErrMustBeSch...
roblaszczak
76
func (i *Item) CommitToSprint(s *sprint.Sprint) error {
if !i.IsScheduledForRelease() {
return ErrMustBeSch...
roblaszczak
77
func (i *Item) CommitToSprint(s *sprint.Sprint) error {
if !i.IsScheduledForRelease() {
return ErrMustBeSch...
roblaszczak
78
func (i *Item) CommitToSprint(s *sprint.Sprint) error {
if !i.IsScheduledForRelease() {
return ErrMustBeSch...
roblaszczak
79
roblaszczak
80
func (i *Item) CommitToSprint(s *sprint.Sprint) error {
if !i.IsScheduledForRelease() {
return ErrMustBeSch...
roblaszczak
81
VALUE
OBJECT
roblaszczak
82
(RACZEJ)
NIE POSIADA ID
roblaszczak
83
JEST IMMUTABLE
roblaszczak
84
NIE POSIADA ZACHOWAŃ
roblaszczak
85
CZĘSTO ŁĄCZY 2
WARTOŚCI KTÓRE
ODDZIELNIE NIE MAJĄ
SENSU
roblaszczak
86
type Price struct {
cents int
currency string
}
roblaszczak
87
func NewPrice(cents int, currency string) (Price, error)
{
if cents <= 0 {
return Price{}, ErrCentsTooLow
}...
roblaszczak
88
func NewPrice(cents int, currency string) (Price, error)
{
if cents <= 0 {
return Price{}, ErrCentsTooLow
}...
roblaszczak
89
func NewPrice(cents int, currency string) (Price, error)
{
if cents <= 0 {
return Price{}, ErrCentsTooLow
}...
roblaszczak
90
Price{}
roblaszczak
91
func (p Price) Empty() bool {
return p.cents == 0 || p.currency == ""
}
roblaszczak
92
func (p Price) Add(toAdd Price) (Price, error) {
if p.currency != toAdd.currency {
return Price{}, ErrCurre...
roblaszczak
93
REPOSITORY
roblaszczak
94
roblaszczak
95
roblaszczak
96
ODKŁADAMY DECYZJĘ
O BAZIE DANYCH
NA PÓŹNIEJ
roblaszczak
97
package backlog
type ItemRepository interface {
Add(*Item) error
ByID(ItemID) (*Item, error)
Update(*Item) ...
roblaszczak
98
package backlog
// ….
var (
ErrItemNotFound = errors.New("item not found")
ErrItemAlreadyExists = errors.Ne...
roblaszczak
99
package backlog
type ItemRepository interface {
Add(*Item) error
ByID(ItemID) (*Item, error)
Update(*Item) ...
roblaszczak
100
type BacklogItemRepository struct {
items map[string]backlog.Item
}
func (r *BacklogItemRepository) Add(i ...
roblaszczak
101
type BacklogItemRepository struct {
items map[string]backlog.Item
}
func (r *BacklogItemRepository) Add(i ...
roblaszczak
102
type BacklogItemRepository struct {
items map[string]backlog.Item
}
func (r *BacklogItemRepository) Add(i ...
roblaszczak
103
Jak tego teraz użyć?
roblaszczak
104
CQRS
roblaszczak
105
MAŁE SERWISY
APLIKACYJNE
PER USE CASE
roblaszczak
106
REUŻYWALNE
roblaszczak
107
roblaszczak
108
type CommitBacklogItemToSprint struct {
BacklogItemID backlog.ItemID
SprintID sprint.ID
}
roblaszczak
109
type CommitBacklogItemToSprint struct {
BacklogItemID backlog.ItemID
SprintID sprint.ID
}
roblaszczak
110
type CommitBacklogItemToSprintHandler struct {
sprintRepository sprint.Repository
backlogItemRepository ba...
roblaszczak
111
func NewCommitToSprintHandler(
sprintRepository sprint.Repository,
backlogItemRepository backlog.ItemRepos...
roblaszczak
112
func NewCommitToSprintHandler(
sprintRepository sprint.Repository,
backlogItemRepository backlog.ItemRepos...
roblaszczak
113
type CommitBacklogItemToSprintHandler struct {
sprintRepository sprint.Repository
backlogItemRepository ba...
roblaszczak
114
type CommitBacklogItemToSprintHandler struct {
sprintRepository sprint.Repository
backlogItemRepository ba...
roblaszczak
115
type AddBacklogItem struct {
ID backlog.ItemID
Name string
}
func (a AddBacklogItemHandler) Handle(cmd Add...
roblaszczak
116
type AddBacklogItem struct {
ID backlog.ItemID
Name string
}
func (a AddBacklogItemHandler) Handle(cmd Add...
roblaszczak
117
type AddBacklogItem struct {
ID backlog.ItemID
Name string
}
func (a AddBacklogItemHandler) Handle(cmd Add...
roblaszczak
118
type AddBacklogItem struct {
ID backlog.ItemID
Name string
}
func (a AddBacklogItemHandler) Handle(cmd Add...
roblaszczak
119
type AddSprint struct {
// ...
roblaszczak
120
type CommitBacklogItemToSprint struct {
BacklogItemID backlog.ItemID
SprintID sprint.ID
}
roblaszczak
121
func (c CommitBacklogItemToSprintHandler) Handle(cmd
CommitBacklogItemToSprint) error {
sprint, err := c.s...
roblaszczak
122
func (c CommitBacklogItemToSprintHandler) Handle(cmd
CommitBacklogItemToSprint) error {
sprint, err := c.s...
roblaszczak
123
func (c CommitBacklogItemToSprintHandler) Handle(cmd
CommitBacklogItemToSprint) error {
// ...
if err := i...
roblaszczak
124
func (c CommitBacklogItemToSprintHandler) Handle(cmd
CommitBacklogItemToSprint) error {
// …
if item.IsCom...
roblaszczak
125
func (c CommitBacklogItemToSprintHandler) Handle(cmd
CommitBacklogItemToSprint) error {
// …
if item.IsCom...
roblaszczak
126
DOBRE MIEJSCE NA
CROSS-CUTTING
CONCERNS
roblaszczak
127
REST
roblaszczak
128
r.Get("/sprint/{sprintID}/backlog/item/{itemID}", func(w
http.ResponseWriter, r *http.Request) {
backlogIt...
roblaszczak
129
r.Get("/sprint/{sprintID}/backlog/item/{itemID}", func(w
http.ResponseWriter, r *http.Request) {
backlogIt...
roblaszczak
130
r.Get("/sprint/{sprintID}/backlog/item/{itemID}", func(w
http.ResponseWriter, r *http.Request) {
backlogIt...
roblaszczak
131
Gdzie to
teraz
upchnąć?
roblaszczak
132
Jeden package
==
tracimy
enkapsulację
roblaszczak
133
.
├── app
│ └── command
├── domain
│ ├── backlog
│ └── sprint
├── infrastructure
│ └── memory
└── interfac...
roblaszczak
134
.
├── app
│ └── command
├── domain
│ ├── backlog
│ └── sprint
├── infrastructure
│ └── memory
└── interfac...
roblaszczak
135
.
├── app
│ └── command
├── domain
│ ├── backlog
│ └── sprint
├── infrastructure
│ └── memory
└── interfac...
roblaszczak
136
.
├── app
│ └── command
├── domain
│ ├── backlog
│ └── sprint
├── infrastructure
│ └── memory
└── interfac...
roblaszczak
137
.
├── app
│ └── command
├── domain
│ ├── backlog
│ └── sprint
├── infrastructure
│ └── memory
└── interfac...
roblaszczak
138
domain
├── backlog
│ ├── item.go
│ ├── item_status.go
│ ├── repository.go
│ └── unmarshal.go
├── error.go
...
roblaszczak
139
roblaszczak
140
github.com/roblaszczak/go-cleanarch
roblaszczak
141
github.com/roblaszczak/go-cleanarch
(wszystkie linki podam pod koniec prezentacji)
roblaszczak
142
roblaszczak
143
roblaszczak
144
type Item struct {
id ItemID `json:"id"`
name string `json:"name"`
status ItemStatus `json:"status"`
relea...
roblaszczak
145
Zmiana widoku
wymaga zmiany w
domenie
roblaszczak
146
Atrybuty są
prywatne więc się
nie zmarshalują :)
roblaszczak
147
type BacklogItemView struct {
ID string `json:"id"`
Name string `json:"name"`
}
func NewBacklogItemView(it...
roblaszczak
148
SOLID Motivational Posters by Derick Bailey, used under CC BY-SA 3.0 US
roblaszczak
149
PERSYSTENTNE
REPOZYTORIUM
roblaszczak
150
roblaszczak
151
roblaszczak
152
Jak unmarshalować
obiekt domenowy bez
zbytniej ingerencji?
roblaszczak
153type ItemUnmarshalData struct {
ID ItemID
Name string
Status ItemStatus
ReleaseSchedule time.Time
CommitedS...
roblaszczak
154type ItemUnmarshalData struct {
ID ItemID
Name string
Status ItemStatus
ReleaseSchedule time.Time
CommitedS...
roblaszczak
155
ANEMICZNY
MODEL
roblaszczak
156
type User struct {
id ID
email string
name string
address string
}
func (u *User) Id() ID { return u.id }
...
roblaszczak
157
SILVER BULLET
NIE ISTNIEJE
DDD TEGO NIE ZMIENI
roblaszczak
158
CRUD IS OK
roblaszczak
159
LITE DDD =
roblaszczak
160
What next?
roblaszczak
161
Wrzucę linki na Twitterze
roblaszczak
roblaszczak
162
roblaszczak
163
roblaszczak
164
roblaszczak
165
https://threedots.tech/
roblaszczak
166
roblaszczak
Three Dots Labs
hire us!
roblaszczak
github.com/ThreeDotsLabs/watermill/
roblaszczak
DZIĘKI!
roblaszczak
robert@threedotslabs.com
Upcoming SlideShare
Loading in …5
×

Tactical DDD patterns in Go

DDD udowodniło już, że jego zastosowanie w przy implementowaniu złożonej logiki biznesowej pozwala nam tworzyć niezawodny, testowalny i rozwijany kod. Wiele zostało już powiedziane i napisane na temat zastosowania DDD w Javie, C# czy w PHP. Mimo to ciężko jeszcze znaleźć dobre i poprawnie zaimplementowane przykłady w Go, które do stosowania DDD nadają się naprawdę dobrze.
Podczas prezentacji pokażę zbiór patternów, które wypracowałem wraz z teamem przez prawie rok produkcyjnego stosowania DDD. Opowiem też z czym wiąże się ich stosowanie.
Jeśli nie miałeś/miałaś jeszcze do czynienia z DDD tym bardziej zapraszam - wspomnę o źródłach, z których można czerpać wiedzę żeby zgłębić temat i to, dlaczego warto zainteresować się tą techniką.

  • Be the first to comment

Tactical DDD patterns in Go

  1. 1. roblaszczak TACTICAL DDD PATTERNS IN GOLANG
  2. 2. roblaszczak Hello!
  3. 3. roblaszczak Hello! ● Co-founder @Tree Dots Labs
  4. 4. roblaszczak Hello! ● Co-founder @Tree Dots Labs ● Golang, Python, PHP
  5. 5. roblaszczak TACTICAL DDD PATTERNS IN GOLANG
  6. 6. roblaszczak 6 + DDD = ?
  7. 7. roblaszczak 7
  8. 8. roblaszczak 8
  9. 9. roblaszczak 9
  10. 10. roblaszczak 10 Dlaczego to nie działa tak jak ustaliliśmy?
  11. 11. roblaszczak 11 Myśleliśmy że wiecie że to tak będzie działać
  12. 12. roblaszczak 12 Czy ktoś w ogóle wie jak to działa?
  13. 13. roblaszczak 13 Poczekaj, sprawdzę w tym handlerze na 1k linii i w triggerach DB
  14. 14. roblaszczak Nie do końca to mieliśmy na myśli mówiąc “klient kupił produkt”... 14
  15. 15. roblaszczak 15
  16. 16. roblaszczak 16
  17. 17. roblaszczak 17
  18. 18. roblaszczak 18 + DDD =
  19. 19. roblaszczak 19 DOKŁADNE ODZWIERCIEDLENIE LOGIKI BIZNESOWEJ
  20. 20. roblaszczak 20 KOD KTÓRY MOŻNA POKAZAĆ EKSPERTOWI DOMENOWEMU
  21. 21. roblaszczak 21 BEZPIECZNE WPROWADZANIE ZNACZĄCYCH ZMIAN W LOGICE BIZNESOWEJ
  22. 22. roblaszczak 22 LEGENDARNY SAMODOKUMENTUJĄCY SIĘ KOD
  23. 23. roblaszczak 23 UPORZĄDKOWANIE NAZEWNICTWA
  24. 24. roblaszczak 24 WERYFIKACJA LOGIKI DOMENOWEJ PRZED ROZPOCZĘCIEM IMPLEMENTACJI
  25. 25. roblaszczak 25 LITE DDD =
  26. 26. roblaszczak 26 EVENT STORMING
  27. 27. roblaszczak 27 HOT SPOTS
  28. 28. roblaszczak 28 ENTITY
  29. 29. roblaszczak 29 Nurse administer standard flu vaccine dose to adult patient.
  30. 30. roblaszczak 30 patient := patient.NewPatient() patient.SetShotType(vaccine.Flu) patient.SetDose(10) patient.SetNurse(nurse)
  31. 31. roblaszczak 31 patient := patient.NewPatient() patient.SetShotType(vaccine.Flu) patient.SetDose(10) patient.SetNurse(nurse)
  32. 32. roblaszczak 32 patient.GiveFluShot()
  33. 33. roblaszczak 33 patient.GiveFluShot()
  34. 34. roblaszczak 34 vaccine := vaccine.StandardAdultFluDose() nurse.AdministerFluVaccine( patient, vaccine, )
  35. 35. roblaszczak 35 Nurse administer standard flu vaccine dose to adult patient.
  36. 36. roblaszczak 36 vaccine := vaccine.StandardAdultFluDose() nurse.AdministerFluVaccine( patient, vaccine, )
  37. 37. roblaszczak 37
  38. 38. roblaszczak 38
  39. 39. roblaszczak 39 package backlog type ItemID struct{ id.ID } type Item struct { id ItemID name string // ... }
  40. 40. roblaszczak 40 package backlog type ItemID struct{ id.ID } type Item struct { id ItemID name string // ... }
  41. 41. roblaszczak 41 type ItemID struct{ id.ID }
  42. 42. roblaszczak 42 package id type ID struct { u ulid.ULID valid bool } func New() ID { return ID{ulid.MustNew(ulid.Timestamp(time.Now()), rand.Reader), true} } func FromString(s string) (ID, error) {} func (i ID) String() string {} func (i ID) Bytes() []byte {} func (i ID) Equals(toCompare ID) bool {} func (i ID) Empty() bool {}
  43. 43. roblaszczak 43 type ItemID id.ID
  44. 44. roblaszczak 44 type ItemID id.ID
  45. 45. roblaszczak 45 type ItemID struct{ id.ID }
  46. 46. roblaszczak 46 func NewItem(id ItemID, name string) (*Item, error) { if id.Empty() { return nil, ErrItemEmptyID } i := &Item{id: id} if err := i.ChangeName(name); err != nil { return nil, err } return i, nil }
  47. 47. roblaszczak 47 func (i *Item) ChangeName(name string) error { if name == "" { return ErrItemEmptyName } i.name = name return nil }
  48. 48. roblaszczak 48 var ( ErrItemEmptyName = domain.NewIllegalStateError("name cannot be empty") )
  49. 49. roblaszczak 49 func (i *Item) changeName(name string) error { if name == "" { return ErrItemEmptyName } i.name = name return nil }
  50. 50. roblaszczak 50 func NewItem(id ItemID, name string) (*Item, error) { if id.Empty() { return nil, ErrItemEmptyID } i := &Item{id: id} if err := i.ChangeName(name); err != nil { return nil, err } return i, nil }
  51. 51. roblaszczak 51 { "valid_item", args{backlog.ItemID{id.New()}, "foo"}, nil, }, { "missing_id", args{backlog.ItemID{}, "foo"}, backlog.ErrItemEmptyID, }, { "missing_name", args{backlog.ItemID{id.New()}, ""}, backlog.ErrItemEmptyName,
  52. 52. roblaszczak 52 { "valid_item", args{backlog.ItemID{id.New()}, "foo"}, nil, }, { "missing_id", args{backlog.ItemID{}, "foo"}, backlog.ErrItemEmptyID, }, { "missing_name", args{backlog.ItemID{id.New()}, ""}, backlog.ErrItemEmptyName,
  53. 53. roblaszczak 53 func TestNewItem(t *testing.T) { type args struct { id backlog.ItemID name string } tests := []struct { name string args args wantErr error }{
  54. 54. roblaszczak 54 t.Run(tt.name, func(t *testing.T) { _, err := backlog.NewItem( tt.args.id, tt.args.name ) assert.Equal(t, tt.wantErr, err) })
  55. 55. roblaszczak 55 Lifetip: CTRL + Shift + T
  56. 56. roblaszczak 56 package backlog_test
  57. 57. roblaszczak 57 tests := []struct { name string args args wantErr error }{
  58. 58. roblaszczak 58 func TestItem_ChangeName(t *testing.T) {
  59. 59. roblaszczak 59
  60. 60. roblaszczak 60 { name: "scheduled_for_release", item: createTestScheduledForReleaseItem(t), wantErr: nil, },
  61. 61. roblaszczak 61
  62. 62. roblaszczak 62 { name: "scheduled_for_release", item: createTestScheduledForReleaseItem(t), wantErr: nil, },
  63. 63. roblaszczak 63 { name: "not_scheduled_for_release", item: createTestItem(t), wantErr: backlog.ErrMustBeScheduledToCommit, },
  64. 64. roblaszczak 64 { name: "already_commited_to_sprint", item: createTestCommitedToSprintItem(t), wantErr: nil, },
  65. 65. roblaszczak 65 func createTestItem(t *testing.T) *backlog.Item { i, err := backlog.NewItem( backlog.ItemID{id.New()}, "Foo", ) require.NoError(t, err) return i }
  66. 66. roblaszczak 66 func createTestItem(t *testing.T) *backlog.Item { i, err := backlog.NewItem( backlog.ItemID{id.New()}, "Foo", ) require.NoError(t, err) return i }
  67. 67. roblaszczak 67 "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
  68. 68. roblaszczak 68 func createTestScheduledForReleaseItem(t *testing.T) *backlog.Item { i := createTestItem(t) err := i.ScheduleRelease(time.Now()) require.NoError(t, err) return i }
  69. 69. roblaszczak 69 func createTestCommitedToSprintItem(t *testing.T) *backlog.Item { i := createTestScheduledForReleaseItem(t) s := createTestSprint(t) err := i.CommitToSprint(s) require.NoError(t, err) return i }
  70. 70. roblaszczak 70 for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := createTestSprint(t) initialSprintID := tt.i.CommitedSprintID() initialStatus := tt.i.Status() err := tt.i.CommitToSprint(s) assert.Equal(t, tt.wantErr, err) // ... }) }
  71. 71. roblaszczak 71 if tt.wantErr == nil { assert.Equal(t, s.ID(), tt.i.CommitedSprintID()) assert.NotEqual(t, s.ID(), initialSprintID) assert.Equal( t, tt.i.Status(), backlog.ItemStatusCommited ) } else { assert.Equal(t,initialSprintID,tt.i.CommitedSprintID()) assert.Equal(t, tt.item.Status(), initialStatus) }
  72. 72. roblaszczak 72 if tt.wantErr == nil { assert.Equal(t, s.ID(), tt.i.CommitedSprintID()) assert.NotEqual(t, s.ID(), initialSprintID) assert.Equal( t, tt.i.Status(), backlog.ItemStatusCommited ) } else { assert.Equal(t,initialSprintID,tt.i.CommitedSprintID()) assert.Equal(t, tt.item.Status(), initialStatus) }
  73. 73. roblaszczak 73 if tt.wantErr == nil { assert.Equal(t, s.ID(), tt.i.CommitedSprintID()) assert.NotEqual(t, s.ID(), initialSprintID) assert.Equal( t, tt.i.Status(), backlog.ItemStatusCommited ) } else { assert.Equal(t,initialSprintID,tt.i.CommitedSprintID()) assert.Equal(t, tt.item.Status(), initialStatus) }
  74. 74. roblaszczak 74 if tt.wantErr == nil { assert.Equal(t, s.ID(), tt.i.CommitedSprintID()) assert.NotEqual(t, s.ID(), initialSprintID) assert.Equal( t, tt.i.Status(), backlog.ItemStatusCommited ) } else { assert.Equal(t,initialSprintID,tt.i.CommitedSprintID()) assert.Equal(t, tt.item.Status(), initialStatus) }
  75. 75. roblaszczak 75 func (i *Item) CommitToSprint(s *sprint.Sprint) error { if !i.IsScheduledForRelease() { return ErrMustBeScheduledToCommit } if i.IsCommittedToSprint() { i.UncommitFromSprint() } i.setStatus(ItemStatusCommited) i.setSprintID(s.ID()) return nil }
  76. 76. roblaszczak 76 func (i *Item) CommitToSprint(s *sprint.Sprint) error { if !i.IsScheduledForRelease() { return ErrMustBeScheduledToCommit } if i.IsCommittedToSprint() { i.UncommitFromSprint() } i.setStatus(ItemStatusCommited) i.setSprintID(s.ID()) return nil }
  77. 77. roblaszczak 77 func (i *Item) CommitToSprint(s *sprint.Sprint) error { if !i.IsScheduledForRelease() { return ErrMustBeScheduledToCommit } if i.IsCommittedToSprint() { i.UncommitFromSprint() } i.setStatus(ItemStatusCommited) i.setSprintID(s.ID()) return nil }
  78. 78. roblaszczak 78 func (i *Item) CommitToSprint(s *sprint.Sprint) error { if !i.IsScheduledForRelease() { return ErrMustBeScheduledToCommit } if i.IsCommittedToSprint() { i.UncommitFromSprint() } i.setStatus(ItemStatusCommited) i.setSprintID(s.ID()) return nil }
  79. 79. roblaszczak 79
  80. 80. roblaszczak 80 func (i *Item) CommitToSprint(s *sprint.Sprint) error { if !i.IsScheduledForRelease() { return ErrMustBeScheduledToCommit } if i.IsCommittedToSprint() { i.UncommitFromSprint() } i.setStatus(ItemStatusCommited) i.setSprintID(s.ID()) return nil }
  81. 81. roblaszczak 81 VALUE OBJECT
  82. 82. roblaszczak 82 (RACZEJ) NIE POSIADA ID
  83. 83. roblaszczak 83 JEST IMMUTABLE
  84. 84. roblaszczak 84 NIE POSIADA ZACHOWAŃ
  85. 85. roblaszczak 85 CZĘSTO ŁĄCZY 2 WARTOŚCI KTÓRE ODDZIELNIE NIE MAJĄ SENSU
  86. 86. roblaszczak 86 type Price struct { cents int currency string }
  87. 87. roblaszczak 87 func NewPrice(cents int, currency string) (Price, error) { if cents <= 0 { return Price{}, ErrCentsTooLow } if len(currency) != 3 { return Price{}, ErrInvalidCurrency } return Price{cents, currency}, nil }
  88. 88. roblaszczak 88 func NewPrice(cents int, currency string) (Price, error) { if cents <= 0 { return Price{}, ErrCentsTooLow } if len(currency) != 3 { return Price{}, ErrInvalidCurrency } return Price{cents, currency}, nil }
  89. 89. roblaszczak 89 func NewPrice(cents int, currency string) (Price, error) { if cents <= 0 { return Price{}, ErrCentsTooLow } if len(currency) != 3 { return Price{}, ErrInvalidCurrency } return Price{cents, currency}, nil }
  90. 90. roblaszczak 90 Price{}
  91. 91. roblaszczak 91 func (p Price) Empty() bool { return p.cents == 0 || p.currency == "" }
  92. 92. roblaszczak 92 func (p Price) Add(toAdd Price) (Price, error) { if p.currency != toAdd.currency { return Price{}, ErrCurrencyDoesntMatch } return NewPrice(p.cents+toAdd.cents, p.currency) }
  93. 93. roblaszczak 93 REPOSITORY
  94. 94. roblaszczak 94
  95. 95. roblaszczak 95
  96. 96. roblaszczak 96 ODKŁADAMY DECYZJĘ O BAZIE DANYCH NA PÓŹNIEJ
  97. 97. roblaszczak 97 package backlog type ItemRepository interface { Add(*Item) error ByID(ItemID) (*Item, error) Update(*Item) error }
  98. 98. roblaszczak 98 package backlog // …. var ( ErrItemNotFound = errors.New("item not found") ErrItemAlreadyExists = errors.New("item already exists") )
  99. 99. roblaszczak 99 package backlog type ItemRepository interface { Add(*Item) error ByID(ItemID) (*Item, error) Update(*Item) error }
  100. 100. roblaszczak 100 type BacklogItemRepository struct { items map[string]backlog.Item } func (r *BacklogItemRepository) Add(i *backlog.Item) error { if _, ok := r.items[i.ID().String()]; ok { return backlog.ErrItemAlreadyExists } r.items[i.ID().String()] = *i return nil }
  101. 101. roblaszczak 101 type BacklogItemRepository struct { items map[string]backlog.Item } func (r *BacklogItemRepository) Add(i *backlog.Item) error { if _, ok := r.items[i.ID().String()]; ok { return backlog.ErrItemAlreadyExists } r.items[i.ID().String()] = *i return nil }
  102. 102. roblaszczak 102 type BacklogItemRepository struct { items map[string]backlog.Item } func (r *BacklogItemRepository) Add(i *backlog.Item) error { if _, ok := r.items[i.ID().String()]; ok { return backlog.ErrItemAlreadyExists } r.items[i.ID().String()] = *i return nil }
  103. 103. roblaszczak 103 Jak tego teraz użyć?
  104. 104. roblaszczak 104 CQRS
  105. 105. roblaszczak 105 MAŁE SERWISY APLIKACYJNE PER USE CASE
  106. 106. roblaszczak 106 REUŻYWALNE
  107. 107. roblaszczak 107
  108. 108. roblaszczak 108 type CommitBacklogItemToSprint struct { BacklogItemID backlog.ItemID SprintID sprint.ID }
  109. 109. roblaszczak 109 type CommitBacklogItemToSprint struct { BacklogItemID backlog.ItemID SprintID sprint.ID }
  110. 110. roblaszczak 110 type CommitBacklogItemToSprintHandler struct { sprintRepository sprint.Repository backlogItemRepository backlog.ItemRepository }
  111. 111. roblaszczak 111 func NewCommitToSprintHandler( sprintRepository sprint.Repository, backlogItemRepository backlog.ItemRepository, ) CommitBacklogItemToSprintHandler { return CommitBacklogItemToSprintHandler{ sprintRepository, backlogItemRepository, } }
  112. 112. roblaszczak 112 func NewCommitToSprintHandler( sprintRepository sprint.Repository, backlogItemRepository backlog.ItemRepository, ) CommitBacklogItemToSprintHandler { return CommitBacklogItemToSprintHandler{ sprintRepository, backlogItemRepository, } }
  113. 113. roblaszczak 113 type CommitBacklogItemToSprintHandler struct { sprintRepository sprint.Repository backlogItemRepository backlog.ItemRepository }
  114. 114. roblaszczak 114 type CommitBacklogItemToSprintHandler struct { sprintRepository sprint.Repository backlogItemRepository backlog.ItemRepository }
  115. 115. roblaszczak 115 type AddBacklogItem struct { ID backlog.ItemID Name string } func (a AddBacklogItemHandler) Handle(cmd AddBacklogItem) error { i, err := backlog.NewItem(cmd.ID, cmd.Name) if err != nil { return err } return a.backlogItemRepository.Add(i) }
  116. 116. roblaszczak 116 type AddBacklogItem struct { ID backlog.ItemID Name string } func (a AddBacklogItemHandler) Handle(cmd AddBacklogItem) error { i, err := backlog.NewItem(cmd.ID, cmd.Name) if err != nil { return err } return a.backlogItemRepository.Add(i) }
  117. 117. roblaszczak 117 type AddBacklogItem struct { ID backlog.ItemID Name string } func (a AddBacklogItemHandler) Handle(cmd AddBacklogItem) error { i, err := backlog.NewItem(cmd.ID, cmd.Name) if err != nil { return err } return a.backlogItemRepository.Add(i) }
  118. 118. roblaszczak 118 type AddBacklogItem struct { ID backlog.ItemID Name string } func (a AddBacklogItemHandler) Handle(cmd AddBacklogItem) error { i, err := backlog.NewItem(cmd.ID, cmd.Name) if err != nil { return err } return a.backlogItemRepository.Add(i) }
  119. 119. roblaszczak 119 type AddSprint struct { // ...
  120. 120. roblaszczak 120 type CommitBacklogItemToSprint struct { BacklogItemID backlog.ItemID SprintID sprint.ID }
  121. 121. roblaszczak 121 func (c CommitBacklogItemToSprintHandler) Handle(cmd CommitBacklogItemToSprint) error { sprint, err := c.sprintRepository.ByID(cmd.SprintID) if err != nil { return err } item, err := c.backlogItemRepository.ByID(cmd.BacklogItemID) if err != nil { return err } // ...
  122. 122. roblaszczak 122 func (c CommitBacklogItemToSprintHandler) Handle(cmd CommitBacklogItemToSprint) error { sprint, err := c.sprintRepository.ByID(cmd.SprintID) if err != nil { return err } item, err := c.backlogItemRepository.ByID(cmd.BacklogItemID) if err != nil { return err } // ...
  123. 123. roblaszczak 123 func (c CommitBacklogItemToSprintHandler) Handle(cmd CommitBacklogItemToSprint) error { // ... if err := item.CommitToSprint(sprint); err != nil { return err } return c.backlogItemRepository.Update(item) }
  124. 124. roblaszczak 124 func (c CommitBacklogItemToSprintHandler) Handle(cmd CommitBacklogItemToSprint) error { // … if item.IsCommitedToBacklog() { return err } // … return c.backlogItemRepository.Update(item) }
  125. 125. roblaszczak 125 func (c CommitBacklogItemToSprintHandler) Handle(cmd CommitBacklogItemToSprint) error { // … if item.IsCommitedToBacklog() { return err } // … return c.backlogItemRepository.Update(item) }
  126. 126. roblaszczak 126 DOBRE MIEJSCE NA CROSS-CUTTING CONCERNS
  127. 127. roblaszczak 127 REST
  128. 128. roblaszczak 128 r.Get("/sprint/{sprintID}/backlog/item/{itemID}", func(w http.ResponseWriter, r *http.Request) { backlogItemID := id.FromStringMust(chi.URLParam(r, "itemID")) sprintID := id.FromStringMust(chi.URLParam(r, "sprintID")) err := h.Handle(command.CommitBacklogItemToSprint{ BacklogItemID: backlog.ItemID{backlogItemID}, SprintID: sprint.ID{sprintID}, }) if err != nil { log.Println("error:", err.Error()) w.WriteHeader(http.StatusInternalServerError) return }
  129. 129. roblaszczak 129 r.Get("/sprint/{sprintID}/backlog/item/{itemID}", func(w http.ResponseWriter, r *http.Request) { backlogItemID := id.FromStringMust(chi.URLParam(r, "itemID")) sprintID := id.FromStringMust(chi.URLParam(r, "sprintID")) err := h.Handle(command.CommitBacklogItemToSprint{ BacklogItemID: backlog.ItemID{backlogItemID}, SprintID: sprint.ID{sprintID}, }) if err != nil { log.Println("error:", err.Error()) w.WriteHeader(http.StatusInternalServerError) return }
  130. 130. roblaszczak 130 r.Get("/sprint/{sprintID}/backlog/item/{itemID}", func(w http.ResponseWriter, r *http.Request) { backlogItemID := id.FromStringMust(chi.URLParam(r, "itemID")) sprintID := id.FromStringMust(chi.URLParam(r, "sprintID")) err := h.Handle(command.CommitBacklogItemToSprint{ BacklogItemID: backlog.ItemID{backlogItemID}, SprintID: sprint.ID{sprintID}, }) if err != nil { log.Println("error:", err.Error()) w.WriteHeader(http.StatusInternalServerError) return }
  131. 131. roblaszczak 131 Gdzie to teraz upchnąć?
  132. 132. roblaszczak 132 Jeden package == tracimy enkapsulację
  133. 133. roblaszczak 133 . ├── app │ └── command ├── domain │ ├── backlog │ └── sprint ├── infrastructure │ └── memory └── interfaces └── rest
  134. 134. roblaszczak 134 . ├── app │ └── command ├── domain │ ├── backlog │ └── sprint ├── infrastructure │ └── memory └── interfaces └── rest
  135. 135. roblaszczak 135 . ├── app │ └── command ├── domain │ ├── backlog │ └── sprint ├── infrastructure │ └── memory └── interfaces └── rest
  136. 136. roblaszczak 136 . ├── app │ └── command ├── domain │ ├── backlog │ └── sprint ├── infrastructure │ └── memory └── interfaces └── rest
  137. 137. roblaszczak 137 . ├── app │ └── command ├── domain │ ├── backlog │ └── sprint ├── infrastructure │ └── memory └── interfaces └── rest
  138. 138. roblaszczak 138 domain ├── backlog │ ├── item.go │ ├── item_status.go │ ├── repository.go │ └── unmarshal.go ├── error.go └── sprint ├── repository.go └── sprint.go
  139. 139. roblaszczak 139
  140. 140. roblaszczak 140 github.com/roblaszczak/go-cleanarch
  141. 141. roblaszczak 141 github.com/roblaszczak/go-cleanarch (wszystkie linki podam pod koniec prezentacji)
  142. 142. roblaszczak 142
  143. 143. roblaszczak 143
  144. 144. roblaszczak 144 type Item struct { id ItemID `json:"id"` name string `json:"name"` status ItemStatus `json:"status"` releaseSchedule time.Time `json:"release_schedule"` commitedSprintID sprint.ID `json:"commited_sprint_id"` }
  145. 145. roblaszczak 145 Zmiana widoku wymaga zmiany w domenie
  146. 146. roblaszczak 146 Atrybuty są prywatne więc się nie zmarshalują :)
  147. 147. roblaszczak 147 type BacklogItemView struct { ID string `json:"id"` Name string `json:"name"` } func NewBacklogItemView(item *backlog.Item) BacklogItemView { return BacklogItemView{item.ID().String(), item.Name()} }
  148. 148. roblaszczak 148 SOLID Motivational Posters by Derick Bailey, used under CC BY-SA 3.0 US
  149. 149. roblaszczak 149 PERSYSTENTNE REPOZYTORIUM
  150. 150. roblaszczak 150
  151. 151. roblaszczak 151
  152. 152. roblaszczak 152 Jak unmarshalować obiekt domenowy bez zbytniej ingerencji?
  153. 153. roblaszczak 153type ItemUnmarshalData struct { ID ItemID Name string Status ItemStatus ReleaseSchedule time.Time CommitedSprintID sprint.ID } func UnmarshalItem(ud ItemUnmarshalData) *Item { return &Item{ ud.ID, ud.Name, ud.Status, ud.ReleaseSchedule, ud.CommitedSprintID, } }
  154. 154. roblaszczak 154type ItemUnmarshalData struct { ID ItemID Name string Status ItemStatus ReleaseSchedule time.Time CommitedSprintID sprint.ID } func UnmarshalItem(ud ItemUnmarshalData) *Item { return &Item{ ud.ID, ud.Name, ud.Status, ud.ReleaseSchedule, ud.CommitedSprintID, } }
  155. 155. roblaszczak 155 ANEMICZNY MODEL
  156. 156. roblaszczak 156 type User struct { id ID email string name string address string } func (u *User) Id() ID { return u.id } func (u *User) SetId(id ID) { u.id = id } func (u *User) Email() string { return u.email } func (u *User) SetEmail(email string) { u.email = email } func (u *User) Name() string { return u.name } func (u *User) SetName(name string) { u.name = name } func (u *User) Address() string { return u.address } func (u *User) SetAddress(address string) { u.address = address }
  157. 157. roblaszczak 157 SILVER BULLET NIE ISTNIEJE DDD TEGO NIE ZMIENI
  158. 158. roblaszczak 158 CRUD IS OK
  159. 159. roblaszczak 159 LITE DDD =
  160. 160. roblaszczak 160 What next?
  161. 161. roblaszczak 161 Wrzucę linki na Twitterze roblaszczak
  162. 162. roblaszczak 162
  163. 163. roblaszczak 163
  164. 164. roblaszczak 164
  165. 165. roblaszczak 165 https://threedots.tech/
  166. 166. roblaszczak 166
  167. 167. roblaszczak Three Dots Labs hire us!
  168. 168. roblaszczak github.com/ThreeDotsLabs/watermill/
  169. 169. roblaszczak DZIĘKI! roblaszczak robert@threedotslabs.com

×