Idiomatic Design Patterns in Go
@ryan0x44
‘Talk less about frameworks,�more about design’
(Paraphrasing GolangUK 2016 keynote by Dave Cheney)
“
Books
“Idiomatic”
Doing it “the Go way” / best practices a la:
“Software Design Patterns”
“Reusable solution
to a commonly occurring problem
within a given context in software design”
23 Classic Design Patterns
Creational - relates to instantiation
Structural - relates to composition
Behavioral - relates to communication
Creational: Factory pattern
Initialize a value for an interface,�but let the Factory decide which implementation �to instantiate.
// Creational: Factory pattern example
func NewWriter(kind string) (io.Writer, error) {
switch kind {
case "mywriter":
return MyWriter{}, nil
case "stderr":
return os.Stderr, nil
default:
return nil, fmt.Errorf("Kind invalid: %s", kind)
}
}
type MyWriter struct{}
func (w MyWriter) Write(p []byte) (n int, err error) {
fmt.Printf("%s", p)
return len(p), nil
}
// Creational: Factory pattern example (cont.)
package main
func main() {
// Get user preference for writer
kind := "mywriter"
if len(os.Args) > 1 && os.Args[1] == "stderr" {
kind = "stderr"
}
// Create writer and write some output
writer, _ := NewWriter(kind)
writer.Write([]byte("Hello world\n"))
}
Structural: Decorator pattern
Add behavior,
without modifying original function/method,
keeping same interface.
// Structural: Decorator pattern example
func main() {
http.HandleFunc("/", helloEndpoint)
_ = http.ListenAndServe(":8080", nil)
}
func helloEndpoint(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello world")
}
// Structural: Decorator pattern example (cont.)
func main() {
http.HandleFunc("/", helloEndpoint)
http.HandleFunc("/logged", logDecorator(helloEndpoint))
_ = http.ListenAndServe(":8080", nil)
}
func helloEndpoint(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello world")
}
func logDecorator(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL)
fn(w, r)
}
}
Behavioral: Iterator pattern
Accesses elements sequentially,
without exposing underlying representation.
// Behavioral: Iterator pattern example
type Iterator struct {
tasks []string
position int
}
// ErrEOF signals a graceful end of iteration
var ErrEOF = errors.New("EOF")
// Next will return the next task in the slice, or ErrEOF
func (t *Iterator) Next() (int, string, error) {
t.position++
if t.position > len(t.tasks) {
return t.position, "", ErrEOF
}
return t.position, t.tasks[t.position-1], nil
}
// Behavioral: Iterator pattern (cont.)
func main() {
tasks := task.GetExamples()
for {
i, t, err := tasks.Next()
if err == task.ErrEOF {
log.Printf("Done")
break
}
if err != nil {
log.Fatalf("Unknown error: %s", err)
}
log.Printf("Task %d: %s\n", i, t)
}
}
func GetExamples() Iterator {
return Iterator{
tasks: []string{
"say hello",
"say goodbye",
},
}
}
Dependency Inversion Principle
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend on details. Details should depend on abstractions.
DIP: Naive Layering
Client Layer
package client
package service
Service Layer
DIP: Inverted Layers
Client Layer
package client
package service
Service Layer
<< interface >>�Client Service Interface
Dependency Injection pattern
“Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
Dependency Injection pattern
Three “styles” of DI:
Let’s look at the first two.
// Creational: Dependency Injection (Constructor) pattern
func main() {
s := NewMyService(os.Stderr)
s.WriteHello("world")
}
type MyService struct {
writer io.Writer
}
func NewMyService(writer io.Writer) MyService {
return MyService{
writer: writer,
}
}
func (s *MyService) WriteHello(m string) {
fmt.Fprintf(s.writer, "Hello %s\n", m)
}
// Creational: Dependency Injection (Setter) pattern
func main() {
s := NewMyService()
s.SetWriter(os.Stderr)
s.WriteHello("world")
}
type MyService struct {
writer io.Writer
}
func NewMyService() MyService {
return MyService{}
}
func (s *MyService) SetWriter(w io.Writer) {
s.writer = w
}
// (Same WriteHello method as last slide)
DI - Constructor vs. Setter?
Constructor - enforce valid dependencies
Setter - allow creating/changing whenever
Which one? Generally - Constructor.
Can use both if required.
Domain-Driven Design
Premise:
Repository Pattern
“Methods for retrieving domain objects should delegate to a specialized Repository object such that alternative storage implementations may be easily interchanged”
Abstract persistence/retrieval from business logic.
Repository Layering
Package postgres
Client Layer
package main
package (domain)
Postgres Implementation
<< interface >>�(Domain) Repository
// Repository pattern example - repository interface
package project
type Project struct {
ID int64
Name string
}
type Repository interface {
FindAll() ([]Project, error)
Store(Project) (Project, error)
Delete(Project) error
}
// Repository pattern example - in-memory implementation
package inmem
type ProjectRepository struct {
projects map[int64]string
highestID int64
}
// Repository pattern example - in-memory implementation (cont.)
func (r *ProjectRepository) Store(p project.Project) (project.Project, error) {
if r.projects == nil {
r.projects = map[int64]string{}
}
if p.ID <= 0 {
r.highestID++
p.ID = r.highestID
} else {
if _, exists := r.projects[p.ID]; !exists {
return p, fmt.Errorf("Cannot update project: %d", p.ID)
}
}
r.projects[p.ID] = p.Name
return p, nil
}
// Repository pattern example - usage in main function
func main() {
projectsRepo := inmem.ProjectRepository{}
newProject, _ := projectsRepo.Store(
project.Project{
Name: "First project",
},
)
newProject.Name = "First project - updated!"
_, _ = projectsRepo.Store(newProject)
}
Thank you!
I hope this encourages you to read/learn more about design patterns,�+ talk more about the design of your code.�
Questions? @ryan0x44