[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# google/wire — Compile-Time Code Generation
|
||||
|
||||
Wire uses code generation to resolve the dependency graph at compile time. Type-safe, but requires a build step.
|
||||
|
||||
- Docs: [github.com/google/wire](https://github.com/google/wire) | [User Guide](https://github.com/google/wire/blob/main/docs/guide.md)
|
||||
|
||||
Before writing Wire code, refer to the library's official documentation for up-to-date API signatures and examples.
|
||||
|
||||
## Provider Definitions
|
||||
|
||||
```go
|
||||
// providers.go
|
||||
package wire
|
||||
|
||||
import "github.com/google/wire"
|
||||
|
||||
// ProviderSet groups related providers
|
||||
var InfraSet = wire.NewSet(
|
||||
NewConfig,
|
||||
NewDatabase,
|
||||
NewCache,
|
||||
)
|
||||
|
||||
var ServiceSet = wire.NewSet(
|
||||
NewUserService,
|
||||
wire.Bind(new(UserStore), new(*PostgresUserStore)), // bind interface to impl
|
||||
)
|
||||
```
|
||||
|
||||
## Injector Definition
|
||||
|
||||
```go
|
||||
// wire.go — build constraint ensures this is only used by the wire tool
|
||||
//go:build wireinject
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/google/wire"
|
||||
|
||||
func InitializeApp() (*App, error) {
|
||||
wire.Build(
|
||||
InfraSet,
|
||||
ServiceSet,
|
||||
NewApp,
|
||||
)
|
||||
return nil, nil // wire replaces this body
|
||||
}
|
||||
```
|
||||
|
||||
## Generated Code
|
||||
|
||||
Run `wire ./...` to produce `wire_gen.go`:
|
||||
|
||||
```go
|
||||
// wire_gen.go — DO NOT EDIT (auto-generated by wire)
|
||||
func InitializeApp() (*App, error) {
|
||||
config := NewConfig()
|
||||
database, err := NewDatabase(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cache := NewCache(config)
|
||||
store := NewPostgresUserStore(database)
|
||||
userService := NewUserService(store, cache)
|
||||
app := NewApp(userService)
|
||||
return app, nil
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Wire generates plain constructors, so testing uses manual injection — no container to clone:
|
||||
|
||||
```go
|
||||
func TestUserService(t *testing.T) {
|
||||
mock := &MockUserStore{...}
|
||||
svc := NewUserService(mock, NewTestCache())
|
||||
// ... test
|
||||
}
|
||||
```
|
||||
|
||||
## Tradeoffs
|
||||
|
||||
- Errors caught at compile time (codegen fails if graph is incomplete)
|
||||
- Requires running `wire ./...` after every dependency change
|
||||
- No lazy loading — all dependencies created eagerly
|
||||
- No built-in lifecycle management (health checks, shutdown)
|
||||
- No runtime container — wire generates plain Go constructor calls
|
||||
- Interface bindings require explicit `wire.Bind` declarations
|
||||
- Generated files (`wire_gen.go`) must be committed and kept in sync
|
||||
|
||||
Wire injectors MUST use `//go:build wireinject` build constraint. Generated `wire_gen.go` MUST NOT be edited manually — always regenerate with `wire ./...`.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Manual Constructor Injection
|
||||
|
||||
Manual DI is the simplest approach — pass dependencies through constructors. No library, no magic.
|
||||
|
||||
## Complete Application Example
|
||||
|
||||
```go
|
||||
func main() {
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer stop()
|
||||
|
||||
// Layer 1: Configuration
|
||||
cfg := LoadConfig()
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
|
||||
// Layer 2: Infrastructure
|
||||
db, err := postgres.Connect(cfg.DatabaseURL)
|
||||
if err != nil {
|
||||
logger.Error("database connection failed", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
cache := redis.NewClient(cfg.RedisURL)
|
||||
defer cache.Close()
|
||||
|
||||
mailer := smtp.NewMailer(cfg.SMTPAddr)
|
||||
|
||||
// Layer 3: Repositories
|
||||
userRepo := postgres.NewUserRepository(db)
|
||||
orderRepo := postgres.NewOrderRepository(db)
|
||||
|
||||
// Layer 4: Services
|
||||
userSvc := service.NewUserService(userRepo, cache, mailer, logger)
|
||||
orderSvc := service.NewOrderService(orderRepo, userSvc, logger)
|
||||
paymentSvc := service.NewPaymentService(orderRepo, cfg.StripeKey, logger)
|
||||
|
||||
// Layer 5: Transport
|
||||
handler := http.NewHandler(userSvc, orderSvc, paymentSvc, logger)
|
||||
server := http.NewServer(cfg.Port, handler)
|
||||
|
||||
// Run
|
||||
go server.ListenAndServe()
|
||||
<-ctx.Done()
|
||||
server.Shutdown(context.Background())
|
||||
}
|
||||
```
|
||||
|
||||
## When Manual DI Works Well
|
||||
|
||||
- Small to medium projects (< 15 services)
|
||||
- Simple dependency graph with clear layering
|
||||
- No need for lazy loading or lifecycle management
|
||||
- Team prefers explicit, visible wiring
|
||||
|
||||
## When Manual DI Breaks Down
|
||||
|
||||
- Adding a new service means editing `main()` and getting the wiring order right
|
||||
- Lifecycle management (health checks, graceful shutdown) must be hand-coded with `defer`
|
||||
- No lazy initialization — all services are created at startup, even if unused
|
||||
- Cross-cutting concerns (logging, tracing) must be threaded through every constructor
|
||||
- With 30+ services, the wiring code becomes fragile and hard to maintain
|
||||
|
||||
Manual DI SHOULD be the default for small projects (< 15 services). Dependencies MUST be initialized in order — infrastructure first, then repositories, then services, then transport.
|
||||
@@ -0,0 +1,36 @@
|
||||
# samber/do — Generics-Based DI
|
||||
|
||||
> **For the full samber/do API, patterns, and advanced features, see the `samber/cc-skills-golang@golang-samber-do` skill.**
|
||||
|
||||
Type-safe dependency injection using Go generics. No reflection, no code generation, simple API.
|
||||
|
||||
- Docs: [do.samber.dev](https://do.samber.dev) | [github.com/samber/do/v2](https://github.com/samber/do)
|
||||
|
||||
## Core Pattern
|
||||
|
||||
```go
|
||||
// Register services with providers
|
||||
injector := do.New()
|
||||
do.Provide(injector, func(i do.Injector) (*UserService, error) {
|
||||
db := do.MustInvoke[*Database](i)
|
||||
return NewUserService(db), nil
|
||||
})
|
||||
|
||||
// Invoke services (lazy — created on demand)
|
||||
svc := do.MustInvoke[*UserService](injector)
|
||||
|
||||
// Graceful shutdown — all services implementing Shutdowner are closed
|
||||
injector.ShutdownOnSignalsWithContext(ctx, os.Interrupt)
|
||||
```
|
||||
|
||||
## Why samber/do
|
||||
|
||||
- **No code generation** — no build step, no generated files to maintain
|
||||
- **No reflection** — errors are caught at compile time via generics, not at runtime
|
||||
- **Strongly typed** — Go generics provide full type safety without `interface{}` casts
|
||||
- **Built-in lifecycle** — health checks and graceful shutdown detected automatically
|
||||
- **Container cloning** — create isolated test containers from production configuration
|
||||
- **Simple API** — `Provide`, `Invoke`, `Shutdown` — that's most of what you need
|
||||
- **Package system** — organize services by domain without manual wiring order
|
||||
|
||||
→ See `samber/cc-skills-golang@golang-samber-do` for full application setup, package organization, lifecycle management, debugging, testing with clone + override, and complete API reference.
|
||||
@@ -0,0 +1,142 @@
|
||||
# uber-go/dig + uber-go/fx — Reflection-Based DI
|
||||
|
||||
`dig` is the low-level DI container; `fx` is the full application framework built on top. Powerful but uses reflection — errors appear at startup, not compile time.
|
||||
|
||||
- Docs: [github.com/uber-go/dig](https://github.com/uber-go/dig) | [uber-go.github.io/fx](https://uber-go.github.io/fx/)
|
||||
|
||||
Before writing dig/fx code, refer to the library's official documentation for up-to-date API signatures and examples.
|
||||
|
||||
## dig — Basic Container
|
||||
|
||||
```go
|
||||
func main() {
|
||||
container := dig.New()
|
||||
|
||||
container.Provide(NewConfig)
|
||||
container.Provide(NewDatabase)
|
||||
container.Provide(NewUserStore)
|
||||
container.Provide(NewUserService)
|
||||
|
||||
// Invoke — dig resolves the full dependency chain
|
||||
err := container.Invoke(func(svc *UserService) {
|
||||
svc.Run()
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Named Dependencies
|
||||
|
||||
```go
|
||||
type DatabaseParams struct {
|
||||
dig.In
|
||||
|
||||
Primary *sql.DB `name:"primary"`
|
||||
Replica *sql.DB `name:"replica"`
|
||||
}
|
||||
|
||||
container.Provide(NewPrimaryDB, dig.Name("primary"))
|
||||
container.Provide(NewReplicaDB, dig.Name("replica"))
|
||||
|
||||
container.Provide(func(p DatabaseParams) *UserService {
|
||||
return &UserService{
|
||||
writer: p.Primary,
|
||||
reader: p.Replica,
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### dig Tradeoffs
|
||||
|
||||
- Uses reflection — type mismatches are runtime errors, not compile errors
|
||||
- `dig.In` and `dig.Out` structs add boilerplate for complex graphs
|
||||
- No built-in lifecycle management
|
||||
- Powerful grouping with `dig.Group` for collecting multiple implementations
|
||||
|
||||
## fx — Full Application Framework
|
||||
|
||||
### Basic Application
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := fx.New(
|
||||
fx.Provide(
|
||||
NewConfig,
|
||||
NewDatabase,
|
||||
NewUserStore,
|
||||
NewUserService,
|
||||
),
|
||||
fx.Invoke(RegisterRoutes),
|
||||
fx.Invoke(StartServer),
|
||||
)
|
||||
|
||||
app.Run() // blocks until signal, then calls shutdown hooks
|
||||
}
|
||||
```
|
||||
|
||||
### Lifecycle Hooks
|
||||
|
||||
```go
|
||||
func NewDatabase(lc fx.Lifecycle, cfg *Config) (*Database, error) {
|
||||
db := &Database{}
|
||||
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
return db.Connect(cfg.URL)
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return db.Close()
|
||||
},
|
||||
})
|
||||
|
||||
return db, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Modules
|
||||
|
||||
```go
|
||||
var InfraModule = fx.Module("infra",
|
||||
fx.Provide(NewConfig),
|
||||
fx.Provide(NewDatabase),
|
||||
fx.Provide(NewCache),
|
||||
)
|
||||
|
||||
var ServiceModule = fx.Module("service",
|
||||
fx.Provide(NewUserService),
|
||||
fx.Provide(NewOrderService),
|
||||
)
|
||||
|
||||
app := fx.New(InfraModule, ServiceModule, fx.Invoke(StartServer))
|
||||
```
|
||||
|
||||
### Testing with fx
|
||||
|
||||
```go
|
||||
func TestUserService(t *testing.T) {
|
||||
var svc *UserService
|
||||
|
||||
app := fxtest.New(t,
|
||||
fx.Provide(NewMockUserStore),
|
||||
fx.Provide(NewUserService),
|
||||
fx.Populate(&svc),
|
||||
)
|
||||
app.RequireStart()
|
||||
defer app.RequireStop()
|
||||
|
||||
// ... test svc
|
||||
}
|
||||
```
|
||||
|
||||
### fx Tradeoffs
|
||||
|
||||
- Full application framework — manages startup, shutdown, and signal handling
|
||||
- Reflection-based — errors at startup, not compile time
|
||||
- Steep learning curve — `fx.In`, `fx.Out`, `fx.Annotate`, `fx.Decorate`
|
||||
- Built-in lifecycle (OnStart/OnStop hooks)
|
||||
- Heavyweight — pulls in the full fx framework
|
||||
- `fxtest` package for testing, but requires starting/stopping the app
|
||||
|
||||
fx lifecycle hooks MUST be used for start/stop — register `OnStart`/`OnStop` via `fx.Lifecycle`. fx modules SHOULD group related providers — use `fx.Module` to organize by domain.
|
||||
Reference in New Issue
Block a user