Writing idiomatic Go using
Domain Driven Design
Who am I?
![](https://s3.amazonaws.com/media-p.slid.es/uploads/760725/images/5426980/19029742_10212118839960433_3809399219961601328_n.jpg)
Damiano Petrungaro
Italy
![](https://s3.amazonaws.com/media-p.slid.es/uploads/760725/images/6150916/italy.jpg)
source: www.vidiani.com
Tivoli
![](https://s3.amazonaws.com/media-p.slid.es/uploads/760725/images/6150921/tivoli.jpg)
source: www.livitaly.com
![](https://s3.amazonaws.com/media-p.slid.es/uploads/760725/images/6154243/arco.jpg)
source: www.tivolitouring.com
![](https://s3.amazonaws.com/media-p.slid.es/uploads/760725/images/6154240/buche-villanova-3.jpg)
source: www.romaest.org
Tivoli (RM)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/760725/images/6154245/traffico.jpg)
source: www.confinelive.it
![](https://s3.amazonaws.com/media-p.slid.es/uploads/760725/images/6154257/greenpark.jpg)
source: www.greenparkmadama.it
Me everyday:
![](https://s3.amazonaws.com/media-p.slid.es/uploads/760725/images/6222124/me-chicken.jpeg)
Once upon a time...
![](https://media.giphy.com/media/JZn9hkPaRhcmQ/giphy.gif)
I mean, it's PHP...
BUT
its community has plenty of
Domain Driven Design
advocates
Time passed by...
![](https://media3.giphy.com/media/hM87DMnls5oZy/giphy.gif)
PHP is OOP
Golang is not OOP
DDD is.... both and none!
DDD does not resonate in Golang
End goal:
Writing idiomatic Go code using DDD patterns
without ending up with an OOP application written in Go
DDD
![](https://media0.giphy.com/media/21PV0Su6USswD76iLv/giphy.gif)
Two sides for one love
DDD
Strategic Design
Tactical Design
It is the one in which you and the domain experts analyze a domain, define its bounded contexts, and look for the best way to let them communicate.
As you can easily assume, the strategic design is programming language agnostic.
Describes a group of patterns to use to shape as code the invariants and models defined in a domain analysis, often driven by the strategic design.
The end goal of applying those patterns is to model the code in a simple but expressive and safe way.
Strategic Design
Packaging and bounded contexts
![](https://media0.giphy.com/media/M1vu1FJnW6gms/giphy.gif)
Strategic Design
Packaging and bounded contexts
package main
import "github.com/company/email"
func main() {
aEmail, err := email.NewAdminEmail("info@whatever.com")
//...
cEmail, err := email.NewCustomerEmail("name.surname@gmail.com")
//...
}
Grouping by kind
Strategic Design
Packaging via bounded contexts
package main
import (
"github.com/company/service/admin"
"github.com/company/service/customer"
)
func main() {
aEmail, err := admin.NewEmail("info@whatever.com")
//...
cEmail, err := customer.NewEmail("name.surname@gmail.com")
//...
}
Grouping by context
Strategic Design
Packaging and bounded contexts
📂 app
┣ 📦customer
┃ ┗ 📜customer.go
┣ 📦events
┃ ┣ 📜customer_events.go
┃ ┣ 📜product_events.go
┃ ┗ 📜user_events.go
┣ 📦product
┃ ┗ 📜product.go
┗ 📦user
┗ 📜user.go
Strategic Design
Packaging via bounded contexts
📂 app
┣ 📦customer
┃ ┣ 📜 events.go
┃ ┗ 📜customer.go
┣ 📦product
┃ ┣ 📜 events.go
┃ ┗ 📜product.go
┗ 📦user
┣ 📜 events.go
┗ 📜user.go
Strategic Design
Patterns and communication by design
Strategic Design
Strategic patterns and communication by design
📂 app
┣ 📦delivery
┃ ┗ 📜 delivery.go // may import product
┗ 📦 product
┗ 📜 product.go // or may import delivery
Strategic Design
Anti-Corruption Layer
📂 app
┣ 📦delivery
┃ ┗ 📜delivery.go // imports pubsub
┣ 📦product
┃ ┗ 📜product.go // imports pubsub
┗ 📦pubsub
┗ 📜pubsub.go
Strategic Design
Strategic patterns and communication by design
📂 app
┣ 📦customer
┃ ┣ 📜 events.go
┃ ┗ 📜customer.go
┣ 📦product
┃ ┣ 📜 events.go
┃ ┗ 📜product.go
┗ 📦user
┣ 📜 events.go
┗ 📜user.go
Strategic Design
📂 app
┣ 📦customer
┃ ┣ 📦event
┃ ┃ ┗ 📜registered.go
┃ ┃ ┗ 📜activated.go
┃ ┃ ┗ 📜banned.go
┃ ┗ 📜customer.go
┣ 📦product
┃ ┣ 📦event
┃ ┃ ┗ 📜added.go
┃ ┃ ┗ 📜removed.go
┃ ┃ ┗ 📜published.go
┃ ┗ 📜product.go
┗ 📦user
┣ 📦event
┃ ┗ 📜logged.go
┃ ┗ 📜signed.go
┗ 📜user.go
Customer/Supplier
Strategic Design
Strategic patterns and communication by design
![](https://media1.giphy.com/media/zPOErRpLtHWbm/giphy.gif)
THE LEGACY CODE
Strategic Design
Big ball of mud
![](https://thumbs.gfycat.com/MagnificentIckyArgentinehornedfrog-size_restricted.gif)
A little copying is better than a little dependency
![](https://media0.giphy.com/media/3o7bui8qZJeSQuXgMo/giphy.gif)
Strategic Design
![](https://media1.giphy.com/media/yJFeycRK2DB4c/giphy.gif)
Tactical Design
It's all about code... almost.
![](https://media3.giphy.com/media/LmNwrBhejkK9EFP504/giphy.gif)
Tactical Design
It's all about code... almost.
- Value Type (AKA Value Object )
 - Repository
Tactical Design
It's all about code... almost.
![](https://media3.giphy.com/media/26AHICv4otlZ0ruGk/giphy.gif)
Tactical Design
THE ALWAYS VALID STATE
![](https://media1.giphy.com/media/WuGSL4LFUMQU/giphy.gif)
![](https://media3.giphy.com/media/5VKbvrjxpVJCM/giphy.gif)
![](https://media0.giphy.com/media/l3q2K5jinAlChoCLS/giphy.gif)
![](https://media1.giphy.com/media/3oEduF2azrdC2O0zvi/giphy.gif)
Tactical Design
// tab/tab.go
package tab
type Tab struct {
Title string
}
// cmd/app/main.go
package main
import "tab"
func main() {
t := Tab{Title: ""}
// ...
}
The always valid state, the unachievable always valid state
Tactical Design
// tab/tab.go
package tab
import (
"errors"
)
type Tab struct {
Title string
}
func New(t string) (*Tab, error) {
switch l := len(t); {
case l < 1:
return nil, errors.New("tab: could not use title less than 1 char")
case l > 50:
return nil, errors.New("tab: could not use title more than 50 char")
default:
return &Tab{Title: t}, nil
}
}
// cmd/app/main.go
package main
import "tab"
func main() {
t, err := Tab.New("a valid title")
if err != nil {
panic(err)
}
t.Title = ""
// ...
}
The always valid state
Tactical Design
// tab/tab.go
package tab
import (
"errors"
)
type Tab struct {
title string
}
func New(t string) (*Tab, error) {
switch l := len(t); {
case l < 1:
return nil, errors.New("tab: could not use title less than 1 char")
case l > 50:
return nil, errors.New("tab: could not use title more than 50 char")
default:
return &Tab{title: t}, nil
}
}
// cmd/app/main.go
package main
import "tab"
func main() {
t, err := tab.New("a valid title")
if err != nil {
panic(err)
}
t2 := &tab.Tab{}
// ...
}
The always valid state
Tactical Design
![](https://media3.giphy.com/media/ZZGmc9j8QpHlKYyMnZ/giphy.gif)
The always valid state: finding a balance
Tactical Design
Value Type
Tactical Design
Value Type: design
package tab
import (
"errors"
"fmt"
"strings"
)
const (
minTitleLength = 1
maxTitleLength = 50
)
var (
// Errors used when an invalid title is given
ErrInvalidTitle = errors.New("tab: could not use invalid title")
ErrTitleTooShort = fmt.Errorf("%w: min length allowed is %d", ErrInvalidTitle, minTitleLength)
ErrTitleTooLong = fmt.Errorf("%w: max length allowed is %d", ErrInvalidTitle, maxTitleLength)
)
// Title represents a tab title
type Title string
// NewTitle returns a title and an error back
func NewTitle(d string) (Title, error) {
switch l := len(strings.TrimSpace(d)); {
case l < minTitleLength:
return "", ErrTitleTooShort
case l > maxTitleLength:
return "", ErrTitleTooLong
default:
return Title(d), nil
}
}
// String returns a string representation of the title
func (t Title) String() string {
return string(t)
}
// Equals returns true if the titles are equal
func (t Title) Equals(t2 Title) bool {
return t.String() == t2.String()
}
Tactical Design
Value Type: advantages
type addTabReq struct {
Title tab.Title `json:"tab_title"`
}
func (r *addTabReq) UnmarshalJSON(data []byte) error {
type clone addTabReq
var req clone
if err := json.Unmarshal(data, &req); err != nil {
return err
}
var err error
if r.Title, err = tab.NewTitle(req.Title.String()); err != nil {
return err
}
return nil
}
Tactical Design
Value Type: design
package tab
import (
"errors"
"fmt"
"strings"
)
const (
minTitleLength = 1
maxTitleLength = 50
)
var (
// Errors used when an invalid title is given
ErrInvalidTitle = errors.New("tab: could not use invalid title")
ErrTitleTooShort = fmt.Errorf("%w: min length allowed is %d", ErrInvalidTitle, minTitleLength)
ErrTitleTooLong = fmt.Errorf("%w: max length allowed is %d", ErrInvalidTitle, maxTitleLength)
)
// Title represents a tab title
type Title string
// NewTitle returns a title and an error back
func NewTitle(d string) (Title, error) {
switch l := len(strings.TrimSpace(d)); {
case l < minTitleLength:
return "", ErrTitleTooShort
case l > maxTitleLength:
return "", ErrTitleTooLong
default:
return Title(d), nil
}
}
// String returns a string representation of the title
func (t Title) String() string {
return string(t)
}
// Equals returns true if the titles are equal
func (t Title) Equals(t2 Title) bool {
return t.String() == t2.String()
}
Tactical Design
Value Type: where to place it
📂 app
┗ 📦tab
┣ 📜 tab.go
┗ 📜 title.go
Tactical Design
Repository
Tactical Design
Repository: design
package tab
import "errors"
var (
//Errors returned by the repository
ErrRepoNextID = errors.New("tab: could not return next id")
ErrRepoList = errors.New("tab: could not list")
ErrNotFound = errors.New("tab: could not find")
ErrRepoGet = errors.New("tab: could not get")
ErrRepoAdd = errors.New("tab: could not add")
ErrRepoRemove = errors.New("tab: could not remove")
)
type Repo interface {
// NextID returns the next free ID and an error in case of failure
NextID() (ID, error)
// List returns a tab slice and an error in case of failure
List() ([]*Tab, error)
// Find returns a tab or nil if it is not found and an error in case of failure
Find(ID) (*Tab, error)
// Get returns a tab and error in case is not found or failure
Get(ID) (*Tab, error)
// Add persists a tab (already existing or not) and returns an error in case of failure
Add(*Tab) error
// Remove removes a tab and returns and error in case is not found or failure
Remove(ID) error
}
Tactical Design
Repository: design
package tab
// ...
type ReadRepo interface {
// List returns a tab slice and an error in case of failure
List() ([]*Tab, error)
// Find returns a tab or nil if it is not found and an error in case of failure
Find(ID) (*Tab, error)
// Get returns a tab and error in case is not found or failure
Get(ID) (*Tab, error)
}
type WriteRepo interface {
// NextID returns the next free ID and an error in case of failure
NextID() (ID, error)
// Add persists a tab (already existing or not) and returns an error in case of failure
Add(*Tab) error
// Remove removes a tab and returns and error in case is not found or failure
Remove(ID) error
}
Tactical Design
Repository: design
package tab
import "errors"
var (
//Errors returned by the repository
ErrRepoNextID = errors.New("tab: could not return next id")
ErrRepoList = errors.New("tab: could not list")
ErrNotFound = errors.New("tab: could not find")
ErrRepoGet = errors.New("tab: could not get")
ErrRepoAdd = errors.New("tab: could not add")
ErrRepoRemove = errors.New("tab: could not remove")
)
type Repo interface {
// NextID returns the next free ID and an error in case of failure
NextID() (ID, error)
// List returns a tab slice and an error in case of failure
List() ([]*Tab, error)
// Find returns a tab or nil if it is not found and an error in case of failure
Find(ID) (*Tab, error)
// Get returns a tab and error in case is not found or failure
Get(ID) (*Tab, error)
// Add persists a tab (already existing or not) and returns an error in case of failure
Add(*Tab) error
// Remove removes a tab and returns and error in case is not found or failure
Remove(ID) error
}
Tactical Design
Repository: design
func (r *MysqlRepo) Add(t *Tab) error {
// ...
return fmt.Errorf("%w: %s", tab.ErrRepoAdd, "a more detailed reason here")
}
Tactical Design
Repository: design
package tab
import "errors"
var (
//Errors returned by the repository
ErrRepoNextID = errors.New("tab: could not return next id")
ErrRepoList = errors.New("tab: could not list")
ErrNotFound = errors.New("tab: could not find")
ErrRepoGet = errors.New("tab: could not get")
ErrRepoAdd = errors.New("tab: could not add")
ErrRepoRemove = errors.New("tab: could not remove")
)
type Repo interface {
// NextID returns the next free ID and an error in case of failure
NextID() (ID, error)
// List returns a tab slice and an error in case of failure
List() ([]*Tab, error)
// Find returns a tab or nil if it is not found and an error in case of failure
Find(ID) (*Tab, error)
// Get returns a tab and error in case is not found or failure
Get(ID) (*Tab, error)
// Add persists a tab (already existing or not) and returns an error in case of failure
Add(*Tab) error
// Remove removes a tab and returns and error in case is not found or failure
Remove(ID) error
}
Tactical Design
Repository: hint
ts, errr := repo.ListActive()
ts, errr := repo.List(repo.Filter{"active": true})
Don't
Do
Tactical Design
Repository: where to place it
📂 app
┣ 📦internal
┃ ┗ 📦tab
┃ ┗ 📜repo.go // here a MySQL implementation
┗ 📦tab
┗ 📜repo.go // here a the interface and the errors
Tactical Design
Repository: where to place it
📂 app
┣ 📦internal
┃ ┗ 📦tab
┃ ┗ 📜repo.go // here a MySQL implementation
┗ 📦tab
┗ 📜repo.go // here a the interface and the errors
- Entity
 - Aggregate
 - Aggregate Root
 - Domain Service
Tactical Design
More patterns
Conclusion
- DDD helps us structuring and modeling all the time
 - Idiomatic Go code using DDD is achievable
 - Applying DDD enhance the languange mechanisms
![](https://media3.giphy.com/media/xULW8v7LtZrgcaGvC0/giphy.gif)
twitter: @damiano_dev
email: damianopetrungaro@gmail.com
Writing idiomatic Go using Domain Driven Design
By Damiano Petrungaro
Writing idiomatic Go using Domain Driven Design
- 1,593