Download as pdf or txt
Download as pdf or txt
You are on page 1of 36

How-to fix tightly coupled

Go
with Corey Scott

corey.scott@ovo.id
@CoreySScott
https://coreyscott.dev
About me
Why does it even
matter?
What is coupling?
Example - PersonLoader
Example - PersonLoader
The Dependency
Inversion
Principle
High level modules should not depend on low level
modules.

Both should depend on abstractions.

Abstractions should not depend on details.


Details should depend on Abstractions.
- Robert C. Martin
High-Level packages should not
depend on Low-Level Packages
High-Level vs Low Level Packages
High-Level vs Low Level Packages
Abstractions should not depend on
details.
Details should depend on
abstractions.
Structs should not depend on Structs
type PizzaMaker struct{}

func (p *PizzaMaker) MakePizza(oven *SuperPizzaOven5000) {


pizza := p.buildPizza()
oven.Bake(pizza)
}
Structs should not depend on Structs
type PizzaMaker struct{}

func (p *PizzaMaker) MakePizza(oven Oven) {


pizza := p.buildPizza()
oven.Bake(pizza)
}

type Oven interface {


Bake(pizza Pizza)
}
Interfaces should not depend on Structs
type Config struct {
DSN string
MaxConnections int
Timeout time.Duration
}

type PersonLoader interface {


Load(cfg *Config, ID int) *Person
}
Interfaces should not depend on Structs
type PersonLoaderConfig interface {
DSN() string
MaxConnections() int
Timeout() time.Duration
}

type PersonLoader interface {


Load(cfg PersonLoaderConfig, ID int) *Person
}
Fixing Tightly
Coupled Code
Example - Typical tightly coupled code
The Example - Introducing an Interface
The Interface Segregation Principle:

Clients should not be forced to depend on methods that


they do not use.
- Robert C. Martin
The Example - The Decoupling
An inverse
Example
Example - Building a “Get user” API
Example - Starting at the bottom
type UserStore struct {}

func (u *UserStore) LoadByID(ID int) (*User, error) {


// code removed

// return the populated user


return &User{}, nil
}

type User struct {


ID int
Name string
Email string
}
Example - The Business Layer
type UserLoader struct {
store *storage.Store
}

func (u *UserLoader) LoadUser(userID int) (*storage.User, error) {


// code removed

// return user from storage layer


return u.store.LoadByID(userID)
}
Example - HTTP Handler
type LoadUserHandler struct {
logic *business.UserLoader
}

func (l *LoadUserHandler) ServeHTTP(resp http.ResponseWriter, req


*http.Request) {

_ = req.ParseForm()
userID, _ := strconv.Atoi(req.Form.Get("userID"))

user, _ := l.logic.LoadUser(userID)

payload, _ := json.Marshal(user)
resp.Write(payload)
}
Example - Extension == WHOOPS
Original: After the Login feature was added.

type User struct { type User struct {


ID int ID int
Name string Name string
Email string Email string
} Password string
}
func (l *LoadUserHandler) ServeHTTP() {
// code removed

user, _ := l.logic.LoadUser(userID)
payload, _ := json.Marshal(&loadUserResponse{
ID: user.ID,
Name: user.Name,
Email: user.Email,
})
resp.Write(payload)
}

type loadUserResponse struct {


ID int
Name string
Email string
}
Thank YOu!
corey.scott@ovo.id
@CoreySScott
https://coreyscott.dev
Bonus Content:
Dependency
Injection and
Testing
Reducing the cost of Dependency Injection
func NewHandler(logger Logger, statsD StatsD, auth Authenticator, loader
UserLoader) *LoadUserHandler {

return &LoadUserHandler{
logger: logger,
stats: stats,
auth: auth,
loader: loader,
store: store,
}
}
Strategy 1 - Config Injection
func NewHandler(logger Logger, stats StatsD, auth Authenticator, loader
UserLoader) *LoadUserHandler {

return &LoadUserHandler{
logger: logger,
stats: stats,
auth: auth,
loader: loader,
}
}
Strategy 1 - Config Injection
func NewHandler(cfg Config, auth Authenticator, loader UserLoader)
*LoadUserHandler {
return &LoadUserHandler{
logger: cfg.Logger(),
stats: cfg.Stats(),
auth: auth,
loader: loader,
}
}

type Config interface {


Logger() Logger
StatsD() StatsD
}
Strategy 2 - Double Constructors
func NewHandler(cfg Config, auth Authenticator, loader UserLoader)
*LoadUserHandler {

return &LoadUserHandler{
logger: cfg.Logger(),
stats: cfg.Stats(),
auth: auth,
loader: loader,
}
}
Strategy 2 - Double Constructors
func NewHandler(cfg Config, loader UserLoader) *LoadUserHandler {
return newHandler(cfg, &MyAuthenticator{}, loader)
}

func newHandler(cfg Config, auth Authenticator, loader UserLoader)


*LoadUserHandler {

return &LoadUserHandler{
logger: cfg.Logger(),
stats: cfg.Stats(),
auth: auth,
loader: loader,
}
}
Thank YOu!
corey.scott@ovo.id
@CoreySScott
https://coreyscott.dev

You might also like