A Microservice Toolkit from gig-delivery
2021. 4. 2
Barney
Why?
- Copy & Paste ➔ import
- Change all repo ➔ Change only one repo
Why DI?
Testability Problem
func query() (email string) {
db, err := sql.Open("postgres", "user=postgres dbname=test ...")
if err != nil {
panic(err)
}
err = db.QueryRow(`SELECT email FROM "user" WHERE id = $1`, 1).Scan(&email)
if err != nil {
panic(err)
}
return email
}
func query(db *sql.DB) (email string) {
err = db.QueryRow(`SELECT email FROM "user" WHERE id = $1`, 1).Scan(&email)
if err != nil {
panic(err)
}
return email
}
func TestQuery(t *testing.T) {
db := mockDB()
defer db.Close()
email := query(db)
assert.Equal(t, email, "email@example.com")
}
Cost of Manual Wiring
Stage 1
Everything in main()
func main() {
// Create logger
logger := log.New(os.Stdout, "[daangn] ", 0)
// Create handlers
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Println("Handler called")
io.WriteString(w, "Hello, world!\n")
})
// Register handlers
http.Handle("/", handler)
// Start server
http.ListenAndServe(":8080", nil)
}
Stage 2
Functions and Manual Wiring
type Logger interface{
Println(v ...interface{})
}
func NewLogger() Logger {
return log.New(os.Stdout, "[daangn] ", 0)
}
func main() {
logger := NewLogger()
handler := NewHandler(logger)
RegisterHandlers(handler)
StartServer()
}
Stage 3
Libraries and Manual Wiring
package logger
type Logger interface{
Println(v ...interface{})
}
func NewLogger() Logger {
return log.New(os.Stdout, "[daangn] ", 0)
}
func main() {
logger := logger.NewLogger()
handler := handlers.NewHandler(logger)
server.RegisterHandlers(handler)
server.StartServer()
}
MySQL, MongoDB, Redis, gRPC, firebase, kafka, jaeger etc...
func main() {
...
if err != nil {
...
}
...
if err != nil {
...
}
...
if err != nil {
...
}
...
}
Fx
Package fx is a framework that makes it easy to build applications out of reusable, composable modules.
No XML
No Annotations
No Magic
Stage 4
Using Fx
package logger
var Module = fx.Options(
fx.Provide(NewLogger),
)
...
func main() {
app := fx.New(
server.Module,
logger.Module,
fx.Provide(
handlers.NewHandler,
),
)
app.Run()
}
App
-
A modular application built around dependency injection
- When created, the application immediately executes all the functions passed via Invoke options
- To supply these functions with the parameters they need, the application looks for constructors that return the appropriate types
- Any required types are missing or any invocations return an error, the application will fail to start
- OnStart hooks are executed
Provider
- It's just a regular Go function
- Fx calls constructors lazily
- Once instantiated, it is cached and reused
(it's effectively singleton)
Invocation
-
We need some invocations to kick-start our application
-
Unlike constructors, invocations are always executed
- They're always run in order
Hook
-
A Hook is a pair of start and stop callbacks
-
Fx imposes a time limit on OnStart and OnStop hooks
deck
By JongHoon Kim
deck
- 69