Existem vĂĄrias coisas que vocĂȘ pode fazer para sempre: olhar para o incĂȘndio, consertar bugs no cĂłdigo legado e, claro, falar sobre DI - e ainda nĂŁo, nĂŁo, e vocĂȘ encontrarĂĄ dependĂȘncias estranhas no prĂłximo aplicativo.
No contexto da linguagem GO, no entanto, a situação Ă© um pouco mais complicada, jĂĄ que nĂŁo hĂĄ um padrĂŁo explĂcito e universalmente suportado para trabalhar com dependĂȘncias, e cada um pedala sua prĂłpria scooter - o que significa que hĂĄ algo para discutir e comparar.
Neste artigo, discutirei as ferramentas e abordagens mais populares para organizar uma hierarquia de dependĂȘncias em go, com suas vantagens e desvantagens. Se vocĂȘ conhece a teoria e a abreviatura DI nĂŁo levanta nenhuma questĂŁo para vocĂȘ (incluindo a necessidade de aplicar esta abordagem), entĂŁo vocĂȘ pode começar a ler o artigo do meio, na primeira metade explicarei o que Ă© DI, porque Ă© necessĂĄrio em geral e em particular em th.
Por que precisamos de tudo isso
Para começar, o principal inimigo de todos os programadores e a principal razĂŁo para o surgimento de quase todas as ferramentas de design Ă© a complexidade. O caso trivial Ă© sempre claro, facilmente cai na cabeça, Ă© Ăłbvia e graciosamente resolvido com uma linha de cĂłdigo e nunca hĂĄ problemas com ele. Ă uma questĂŁo diferente quando o sistema tem dezenas e centenas de milhares (e Ă s vezes mais) linhas de cĂłdigo e muitas partes "mĂłveis" que se entrelaçam, interagem e simplesmente existem em um pequeno mundo onde parece impossĂvel virar sem tocar em alguĂ©m. entĂŁo cotovelos.
Para resolver o problema da complexidade, a humanidade ainda nĂŁo encontrou maneira melhor do que dividir as coisas complexas em simples, isolando-as e considerando-as separadamente.
O principal aqui Ă© o isolamento, desde que um componente nĂŁo afete os vizinhos, vocĂȘ nĂŁo pode ter medo de efeitos inesperados e da influĂȘncia implĂcita de um no resultado do segundo. Para garantir esse isolamento, decidimos controlar as conexĂ”es de cada componente, descrevendo explicitamente o que e como depende.
Nesse ponto, chegamos Ă injeção de dependĂȘncia (ou injeção), que na verdade Ă© apenas uma maneira de organizar o cĂłdigo de forma que cada componente (classe, estrutura, mĂłdulo, etc.) tenha acesso apenas Ă s partes da aplicação de que precisa, ocultando tudo o que for desnecessĂĄrio. para seu trabalho ou, para citar a Wikipedia: "DI Ă© o processo de fornecer uma dependĂȘncia externa a um componente de software."
Esta abordagem resolve vĂĄrios problemas de uma vez:
- Esconde o desnecessĂĄrio, reduzindo a carga cognitiva do desenvolvedor;
- ( , );
- , , ;
DI
. :
- â : , , (, ), ;
- â ;
- â , , , .
â â DI , .
, (, DI) â , , , .
, DI ( , ), (DI-, , ), , , , - .
:
, , JSONâ , .
, :
- , , ;
- , ;
- ( ) ;
, ?
, , , internal server error? ( , , , , ?)
, / , ( - )?
: , , .
SIGINT, , , . "" , , Graceful shutdown.
, , , , , .
, , , DI:
- , , , , , , ;
- : , , ;
DI Java
, , - . , , .
, , - : , . : -, (, , - ), -, ( , ), " ", , ( ) .
, , , , , . , , .
.
, , . , , , .
https://github.com/vivid-money/article-golang-di.
, , Logger â , , DBConn , HTTPServer, , , () . , Logger->DBConn->HTTPServer, .
, DBConn ( DBConn.Connect()
), httpServer.Serve
, , .
Reflection based container
, https://github.com/uber-go/dig https://github.com/uber-go/fx.
, , . , :
// , - , .
logger := log.New(os.Stderr, "", 0)
logger.Print("Started")
container := dig.New() //
// .
// Dig , , .
_ = container.Provide(func() components.Logger {
logger.Print("Provided logger")
return logger // .
})
_ = container.Provide(components.NewDBConn)
_ = container.Provide(components.NewHTTPServer)
_ = container.Invoke(func(_ *components.HTTPServer) {
// HTTPServer, "" , .
logger.Print("Can work with HTTPServer")
// , .
})
/*
Output:
---
Started
Provided logger
New DBConn
New HTTPServer
Can work with HTTPServer
*/
fx :
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// , - ,
// .
logger := log.New(os.Stderr, "", 0)
logger.Print("Started")
// fx, "".
app := fx.New(
fx.Provide(func() components.Logger {
return logger // .
}),
fx.Provide(
func(logger components.Logger, lc fx.Lifecycle) *components.DBConn { // lc - .
conn := components.NewDBConn(logger)
// .
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
if err := conn.Connect(ctx); err != nil {
return fmt.Errorf("can't connect to db: %w", err)
}
return nil
},
OnStop: func(ctx context.Context) error {
return conn.Stop(ctx)
},
})
return conn
},
func(logger components.Logger, dbConn *components.DBConn, lc fx.Lifecycle) *components.HTTPServer {
s := components.NewHTTPServer(logger, dbConn)
lc.Append(fx.Hook{
OnStart: func(_ context.Context) error {
go func() {
defer cancel()
// , .. Serve - .
if err := s.Serve(context.Background()); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Print("Error: ", err)
}
}()
return nil
},
OnStop: func(ctx context.Context) error {
return s.Stop(ctx)
},
})
return s
},
),
fx.Invoke(
// - "", , .
func(*components.HTTPServer) {
go func() {
components.AwaitSignal(ctx) // , .
cancel()
}()
},
),
fx.NopLogger,
)
_ = app.Start(ctx)
<-ctx.Done() //
_ = app.Stop(context.Background())
/*
Output:
---
Started
New DBConn
New HTTPServer
Connecting DBConn
Connected DBConn
Serving HTTPServer
^CStop HTTPServer
Stopped HTTPServer
Stop DBConn
Stopped DBConn
*/
, Serve ( ListenAndServe) ? : (go blockingFunc()
), . , , , , .
fx, (fx.In
, fx.Out
) (optional
, name
), , , - .
, , , fx.Supply
, - , .
"" :
- , , , " ". , ;
- , - , ;
- , ;
- ;
- xml yaml;
:
- , ;
- , , compile-time â (, - ) , . , .
- fx:
- ( Serve ), , , ;
, go https://github.com/google/wire .
, , , . , , , , compile-time .
, , . , , , , â , . :
, .
- ( "" , ):
// +build wireinject
package main
import (
"context"
"github.com/google/wire"
"github.com/vivid-money/article-golang-di/pkg/components"
)
func initializeHTTPServer(
_ context.Context,
_ components.Logger,
closer func(), // ,
) (
res *components.HTTPServer,
cleanup func(), // ,
err error,
) {
wire.Build(
NewDBConn,
NewHTTPServer,
)
return &components.HTTPServer{}, nil, nil
}
, wire
( go generate
), wire , wire , :
func initializeHTTPServer(contextContext context.Context, logger components.Logger, closer func()) (*components.HTTPServer, func(), error) {
dbConn, cleanup, err := NewDBConn(contextContext, logger)
if err != nil {
return nil, nil, err
}
httpServer, cleanup2 := NewHTTPServer(contextContext, logger, dbConn, closer)
return httpServer, func() {
cleanup2()
cleanup()
}, nil
}
initializeHTTPServer
, "" :
package main
//go:generate wire
import (
"context"
"fmt"
"log"
"os"
"errors"
"net/http"
"github.com/vivid-money/article-golang-di/pkg/components"
)
// wire lifecycle (, Cleanup-),
// , ,
// cleanup- .
func NewDBConn(ctx context.Context, logger components.Logger) (*components.DBConn, func(), error) {
conn := components.NewDBConn(logger)
if err := conn.Connect(ctx); err != nil {
return nil, nil, fmt.Errorf("can't connect to db: %w", err)
}
return conn, func() {
if err := conn.Stop(context.Background()); err != nil {
logger.Print("Error trying to stop dbconn", err)
}
}, nil
}
func NewHTTPServer(
ctx context.Context,
logger components.Logger,
conn *components.DBConn,
closer func(),
) (*components.HTTPServer, func()) {
srv := components.NewHTTPServer(logger, conn)
go func() {
if err := srv.Serve(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Print("Error serving http: ", err)
}
closer()
}()
return srv, func() {
if err := srv.Stop(context.Background()); err != nil {
logger.Print("Error trying to stop http server", err)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// , - , .
logger := log.New(os.Stderr, "", 0)
logger.Print("Started")
// . "" ,
// Server' , cleanup-.
// .
lifecycleCtx, cancelLifecycle := context.WithCancel(context.Background())
defer cancelLifecycle()
// , Serve .
_, cleanup, _ := initializeHTTPServer(ctx, logger, func() {
cancelLifecycle()
})
defer cleanup()
go func() {
components.AwaitSignal(ctx) //
cancelLifecycle()
}()
<-lifecycleCtx.Done()
/*
Output:
---
New DBConn
Connecting DBConn
Connected DBConn
New HTTPServer
Serving HTTPServer
^CStop HTTPServer
Stopped HTTPServer
Stop DBConn
Stopped DBConn
*/
}
:
- ;
- ;
- ;
- ,
wire.Build
; - xml;
- Wire cleanup-, .
:
- , - ;
- , - ; , , , "" ;
- wire ( , ):
- , , ;
- , , / , , ;
- "" ;
- Cleanup' , , .
, , ( , ) . , , , wire dig/fx, , , ( ).
( - -- -), â .
, , :
logger := log.New(os.Stderr, "", 0)
dbConn := components.NewDBConn(logger)
httpServer := components.NewHTTPServer(logger, dbConn)
doSomething(httpServer)
errgroup.
:
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.New(os.Stderr, "", 0)
logger.Print("Started")
g, gCtx := errgroup.WithContext(ctx)
dbConn := components.NewDBConn(logger)
g.Go(func() error {
// dbConn .
if err := dbConn.Connect(gCtx); err != nil {
return fmt.Errorf("can't connect to db: %w", err)
}
return nil
})
httpServer := components.NewHTTPServer(logger, dbConn)
g.Go(func() error {
go func() {
// , httpServer ( http.ListenAndServe, )
// , .
<-gCtx.Done()
if err := httpServer.Stop(context.Background()); err != nil {
logger.Print("Stopped http server with error:", err)
}
}()
if err := httpServer.Serve(gCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("can't serve http: %w", err)
}
return nil
})
go func() {
components.AwaitSignal(gCtx)
cancel()
}()
_ = g.Wait()
/*
Output:
---
Started
New DBConn
New HTTPServer
Connecting DBConn
Connected DBConn
Serving HTTPServer
^CStop HTTPServer
Stop DBConn
Stopped DBConn
Stopped HTTPServer
Finished serving HTTPServer
*/
}
?
, , g, :
- ( );
- (
ctx.cancel
->gCtx.cancel
); - , â , gCtx .
, : errgroup . , gCtx .Done()
, cancel
, - (, ) .
:
- errgroup , ;
- errgroup , - . - , , , . , - , - , ?
â lifecycle.
, , : errgroup , , .
- :
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.New(os.Stderr, "", 0)
logger.Print("Started")
lc := lifecycle.NewLifecycle()
dbConn := components.NewDBConn(logger)
lc.AddServer(func(ctx context.Context) error { //
return dbConn.Connect(ctx)
}).AddShutdowner(func(ctx context.Context) error {
return dbConn.Stop(ctx)
})
httpSrv := components.NewHTTPServer(logger, dbConn)
lc.Add(httpSrv) // httpSrv Server Shutdowner
go func() {
components.AwaitSignal(ctx)
lc.Stop(context.Background())
}()
_ = lc.Serve(ctx)
, , , , .
( lifecycle
, )
Java - , , , "" , .
, .
, , , - , , , , , .
, , "" , , , , ( ). , â main-.
, defer, , , .
, -, defer' return' , - (, ), -, . , , , :
a, err := NewA()
if err != nil {
panic("cant create a: " + err.Error())
}
go a.Serve()
defer a.Stop()
b, err := NewB(a)
if err != nil {
panic("cant create b: " + err.Error())
}
go b.Serve()
defer b.Stop()
/*
: A, B
: B, A
*/
, , ( , ). :
- ErrSet â / ;
- Serve â -server, server , WithCancel, -server' ( , server' );
- Shutdown â ErrSet, , - ;
, :
package main
import (
"context"
"fmt"
"log"
"os"
"errors"
"net/http"
"github.com/vivid-money/article-golang-di/pkg/components"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.New(os.Stderr, "", 0)
logger.Print("Started")
go func() {
components.AwaitSignal(ctx)
cancel()
}()
errset := &ErrSet{}
errset.Add(runApp(ctx, logger, errset))
_ = errset.Error() //
/*
Output:
---
Started
New DBConn
Connecting DBConn
Connected DBConn
New HTTPServer
Serving HTTPServer
^CStop HTTPServer
Stop DBConn
Stopped DBConn
Stopped HTTPServer
Finished serving HTTPServer
*/
}
func runApp(ctx context.Context, logger components.Logger, errSet *ErrSet) error {
var err error
dbConn := components.NewDBConn(logger)
if err := dbConn.Connect(ctx); err != nil {
return fmt.Errorf("cant connect dbConn: %w", err)
}
defer Shutdown("dbConn", errSet, dbConn.Stop)
httpServer := components.NewHTTPServer(logger, dbConn)
if ctx, err = Serve(ctx, "httpServer", errSet, httpServer.Serve); err != nil && !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("cant serve httpServer: %w", err)
}
defer Shutdown("httpServer", errSet, httpServer.Stop)
components.AwaitSignal(ctx)
return ctx.Err()
}
, , , .
?
- , New-Serve-defer-Shutdown ( , , , );
- , , , ;
- ;
- ( ) ;
- , , ;
- 100% , , ;
- , , ;
- , ;
.
, , golang.
fx ( go), , â .
Wire , .
( , ) , go
, context
, defer
.
, , , . , wire (, , ).