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.

Tactical DDD patterns in Go

19 views

Published on

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ą.

Published in: Software
  • Be the first to comment

  • Be the first to like this

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

×