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