Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Tactical DDD patterns in Go
Search
Robert Laszczak
September 19, 2018
0
84
Tactical DDD patterns in Go
Robert Laszczak
September 19, 2018
Tweet
Share
More Decks by Robert Laszczak
See All by Robert Laszczak
Let's build event-driven application in 15 minutes - introduction to Watermill
roblaszczak
0
630
Wprowadzenie do Watermill - Gophers Silesia
roblaszczak
1
57
Wprowadzenie do Watermill GoCracow
roblaszczak
0
28
Event-driven application made easy with Watermill
roblaszczak
1
190
Featured
See All Featured
Building a Modern Day E-commerce SEO Strategy
aleyda
35
6.8k
Principles of Awesome APIs and How to Build Them.
keavy
125
16k
VelocityConf: Rendering Performance Case Studies
addyosmani
321
23k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
42
2k
GitHub's CSS Performance
jonrohan
1029
450k
Large-scale JavaScript Application Architecture
addyosmani
508
110k
Visualization
eitanlees
142
15k
The Brand Is Dead. Long Live the Brand.
mthomps
53
37k
YesSQL, Process and Tooling at Scale
rocio
167
14k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
165
48k
The Cost Of JavaScript in 2023
addyosmani
39
5.2k
The Language of Interfaces
destraynor
153
23k
Transcript
roblaszczak TACTICAL DDD PATTERNS IN GOLANG
roblaszczak 2
roblaszczak 3
roblaszczak 4
roblaszczak 5 Dlaczego to nie działa tak jak ustaliliśmy?
roblaszczak 6 Myśleliśmy, że wiecie że to tak będzie działać
roblaszczak 7 Czy ktoś w ogóle wie jak to działa?
roblaszczak 8 Poczekaj, sprawdzę w tym handlerze na 1k linii
i w triggerach DB
roblaszczak 9
roblaszczak 10
roblaszczak 11 DOKŁADNE ODZWIERCIEDLENIE LOGIKI BIZNESOWEJ
roblaszczak 12 KOD KTÓRY MOŻNA POKAZAĆ EKSPERTOWI DOMENOWEMU
roblaszczak 13 BEZPIECZNE WPROWADZANIE ZNACZĄCYCH ZMIAN W LOGICE BIZNESOWEJ
roblaszczak 14 LEGENDARNY SAMODOKUMENTUJĄCY SIĘ KOD
roblaszczak 15 UPORZĄDKOWANIE NAZEWNICTWA
roblaszczak 16 WERYFIKACJA LOGIKI DOMENOWEJ PRZED ROZPOCZĘCIEM IMPLEMENTACJI
roblaszczak 17 EVENT STORMING
roblaszczak 18 HOT SPOTS
roblaszczak 19
roblaszczak 20 LITE DDD =
roblaszczak 21 ENTITY
roblaszczak 22 Nurse administers standard flu vaccine dose to adult
patient.
roblaszczak 23 patient := patient.NewPatient() patient.SetShotType(vaccine.Flu) patient.SetDose(10) patient.SetNurse(nurse)
roblaszczak 24 patient := patient.NewPatient() patient.SetShotType(vaccine.Flu) patient.SetDose(10) patient.SetNurse(nurse)
roblaszczak 25 patient.GiveFluShot()
roblaszczak 26 patient.GiveFluShot()
roblaszczak 27 vaccine := vaccine.StandardAdultFluDose() nurse.AdministerFluVaccine( patient, vaccine, )
roblaszczak 28 Nurse administers standard flu vaccine dose to adult
patient.
roblaszczak 29 vaccine := vaccine.StandardAdultFluDose() nurse.AdministerFluVaccine( patient, vaccine, )
roblaszczak 30
roblaszczak 31
roblaszczak 32 package backlog type ItemID struct{ id.ID } type
Item struct { id ItemID name string // ... }
roblaszczak 33 package backlog type ItemID struct{ id.ID } type
Item struct { id ItemID name string // ... }
roblaszczak 34 func NewItem(id ItemID, name string) (*Item, error) {
if id.Empty() { return nil, ErrItemEmptyID } i := &Item{id: id} if err := i.SetName(name); err != nil { return nil, err } return i, nil }
roblaszczak 35 func NewItem(id ItemID, name string) (*Item, error) {
if id.Empty() { return nil, ErrItemEmptyID } i := &Item{id: id} if err := i.SetName(name); err != nil { return nil, err } return i, nil }
roblaszczak 36 func (i *Item) SetName(name string) error { if
name == "" { return ErrItemEmptyName } i.name = name return nil }
roblaszczak 37 func (i *Item) SetName(name string) error { if
name == "" { return ErrItemEmptyName } i.name = name return nil }
roblaszczak 38 var ( ErrItemEmptyName = domain.NewIllegalStateError("name cannot be empty")
)
roblaszczak 39 func (i *Item) setName(name string) error { if
name == "" { return ErrItemEmptyName } i.name = name return nil }
roblaszczak 40
roblaszczak 41 { name: "scheduled_for_release", item: createTestScheduledForReleaseItem(t), wantErr: nil, },
roblaszczak 42
roblaszczak 43 { name: "scheduled_for_release", item: createTestScheduledForReleaseItem(t), wantErr: nil, },
roblaszczak 44 { name: "not_scheduled_for_release", item: createTestItem(t), wantErr: backlog.ErrMustBeScheduledToCommit, },
roblaszczak 45 { name: "already_commited_to_sprint", item: createTestCommitedToSprintItem(t), wantErr: nil, },
roblaszczak 46 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) // ... }) }
roblaszczak 47 if tt.wantErr == nil { assert.Equal(t, s.ID(), tt.i.CommitedSprintID())
assert.Equal( t, tt.i.Status(), backlog.ItemStatusCommited ) } else { assert.Equal(t,initialSprintID,tt.i.CommitedSprintID()) assert.Equal(t, tt.item.Status(), initialStatus) }
roblaszczak 48 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) }
roblaszczak 49 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) }
roblaszczak 50 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) }
roblaszczak 51 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 }
roblaszczak 52 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 }
roblaszczak 53 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 }
roblaszczak 54 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 }
roblaszczak 55
roblaszczak 56 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 }
roblaszczak 57 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 }
roblaszczak 58 VALUE OBJECT
roblaszczak 59 (RACZEJ) NIE POSIADA ID
roblaszczak 60 JEST IMMUTABLE
roblaszczak 61 CZĘSTO ŁĄCZY 2 WARTOŚCI KTÓRE ODDZIELNIE NIE MAJĄ
SENSU
roblaszczak 62 type Price struct { cents int currency string
}
roblaszczak 63 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 }
roblaszczak 64 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 }
roblaszczak 65 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 }
roblaszczak 66 Price{}
roblaszczak 67 func (p Price) Empty() bool { return p.cents
== 0 || p.currency == "" }
roblaszczak 68 func (p Price) Add(toAdd Price) (Price, error) {
if p.currency != toAdd.currency { return Price{}, ErrCurrencyDoesntMatch } return NewPrice(p.cents+toAdd.cents, p.currency) }
roblaszczak 69 REPOSITORY
roblaszczak 70
roblaszczak 71
roblaszczak 72 ODKŁADAMY DECYZJĘ O BAZIE DANYCH NA PÓŹNIEJ
roblaszczak 73 ODKŁADAMY DECYZJĘ O BAZIE DANYCH NA PÓŹNIEJ
roblaszczak 74 package backlog type ItemRepository interface { Add(*Item) error
ByID(ItemID) (*Item, error) Update(*Item) error }
roblaszczak 75 package backlog // …. var ( ErrItemNotFound =
errors.New("item not found") ErrItemAlreadyExists = errors.New("item already exists") )
roblaszczak 76 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 }
roblaszczak 77 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 }
roblaszczak 78 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 }
roblaszczak 79 Jak tego teraz użyć?
roblaszczak 80 CQRS
roblaszczak 81
roblaszczak 82 type CommitBacklogItemToSprint struct { BacklogItemID backlog.ItemID SprintID sprint.ID
}
roblaszczak 83 type CommitBacklogItemToSprintHandler struct { sprintRepository sprint.Repository backlogItemRepository backlog.ItemRepository
}
roblaszczak 84 type CommitBacklogItemToSprintHandler struct { sprintRepository sprint.Repository backlogItemRepository backlog.ItemRepository
}
roblaszczak 85 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 } // ...
roblaszczak 86 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 } // ...
roblaszczak 87 func (c CommitBacklogItemToSprintHandler) Handle(cmd CommitBacklogItemToSprint) error { //
... if err := item.CommitToSprint(sprint); err != nil { return err } return c.backlogItemRepository.Update(item) }
roblaszczak 88 func (c CommitBacklogItemToSprintHandler) Handle(cmd CommitBacklogItemToSprint) error { //
… if item.IsCommitedToBacklog() { return err } // … return c.backlogItemRepository.Update(item) }
roblaszczak 89 func (c CommitBacklogItemToSprintHandler) Handle(cmd CommitBacklogItemToSprint) error { //
… if item.IsCommitedToBacklog() { return err } // … return c.backlogItemRepository.Update(item) }
roblaszczak 90 DOBRE MIEJSCE NA CROSS-CUTTING CONCERNS
roblaszczak 91 Gdzie to teraz upchnąć?
roblaszczak 92 Jeden package == tracimy enkapsulację
roblaszczak 93 . ├── app │ └── command ├── domain
│ ├── backlog │ └── sprint ├── infrastructure │ └── memory └── interfaces └── rest
roblaszczak 94
roblaszczak 95 github.com/roblaszczak/go-cleanarch
roblaszczak 96 github.com/roblaszczak/go-cleanarch (wszystkie linki podam pod koniec prezentacji)
roblaszczak 97
roblaszczak 98 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"` }
roblaszczak 99 Zmiana widoku wymaga zmiany w domenie
roblaszczak 100 Atrybuty są prywatne więc się nie zmarshalują :)
roblaszczak 101 type BacklogItemView struct { ID string `json:"id"` Name
string `json:"name"` } func NewBacklogItemView(item *backlog.Item) BacklogItemView { return BacklogItemView{item.ID().String(), item.Name()} }
roblaszczak 102 PERSYSTENTNE REPOZYTORIUM
roblaszczak 103
roblaszczak 104 Jak unmarshalować obiekt domenowy bez zbytniej ingerencji?
roblaszczak 105 type 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, } }
roblaszczak 106 type 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, } }
roblaszczak 107 SILVER BULLET NIE ISTNIEJE DDD TEGO NIE ZMIENI
roblaszczak 108 CRUD
roblaszczak Pragmatic Hype-driven development Kraków 109
roblaszczak Pragmatic Hype-driven development Kraków 2018/11/26-27 110 Będę miał do
rozdania 2 wejściówki
roblaszczak 111 LITE DDD =
roblaszczak 112 What next?
roblaszczak 113 Wrzucę linki na Twitterze roblaszczak
roblaszczak 114
roblaszczak 115
roblaszczak 116
roblaszczak 117 https://threedots.tech/
roblaszczak 118
roblaszczak 119 SpeakerRate: http://spkr8.com/t/76351 Do wygrania 2 wejściówki na CoreDump!
roblaszczak DZIĘKI! 120 http://spkr8.com/t/76351 roblaszczak