[teamai] Push 87 resource(s) from XingfenD

This commit is contained in:
2026-09-10 16:10:45 +08:00
parent 425c9c078a
commit 65c04def51
1314 changed files with 211681 additions and 0 deletions
@@ -0,0 +1 @@
XingfenD
@@ -0,0 +1,226 @@
---
name: golang-samber-do
description: "Dependency injection in Golang using samber/do — service containers, lifecycle management, scopes, health checks, graceful shutdown, and module organization. Apply when using or adopting samber/do, when the codebase imports github.com/samber/do or github.com/samber/do/v2, or when refactoring manual constructor injection into a DI container."
user-invocable: true
license: MIT
compatibility: Designed for Claude Code or similar AI coding agents, and for projects using Golang.
metadata:
author: samber
version: "1.2.5"
openclaw:
emoji: "💉"
homepage: https://github.com/samber/cc-skills-golang
requires:
bins:
- go
install: []
skill-library-version: "2.0.0"
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*) Bash(git:*) Agent WebFetch mcp__context7__resolve-library-id mcp__context7__query-docs Bash(godig:*) Bash(gopls:*) LSP mcp__gopls__*
---
**Persona:** You are a Go architect setting up dependency injection. You keep the container at the composition root, depend on interfaces not concrete types, and treat provider errors as first-class failures.
# Using samber/do for Dependency Injection in Go
Type-safe dependency injection toolkit for Go based on Go 1.18+ generics.
**Official Resources:**
- [pkg.go.dev/github.com/samber/do/v2](https://pkg.go.dev/github.com/samber/do/v2)
- [do.samber.dev](https://do.samber.dev)
- [github.com/samber/do/v2](https://github.com/samber/do)
This skill is not exhaustive. Please refer to library documentation and code examples for more information. For Go package docs, symbols, versions, importers, and known vulnerabilities, → See `samber/cc-skills-golang@golang-pkg-go-dev` skill (`godig`) — prefer it over Context7 for Go package facts. To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See `samber/cc-skills-golang@golang-gopls` skill (`gopls`). Context7 remains a fallback for docs not indexed on pkg.go.dev.
DO NOT USE v1 OF THIS LIBRARY. INSTALL v2 INSTEAD:
```bash
go get -u github.com/samber/do/v2
```
## Core Concepts
### The Injector (Container)
```go
import "github.com/samber/do/v2"
injector := do.New()
```
### Service Types
- **Lazy** (default): Created when first requested
- **Eager**: Created immediately when the container starts
- **Transient**: New instance created on every request
- **Value**: Pre-created value, no instantiation
### Provider Functions
Services MUST be registered via provider functions:
```go
type Provider[T any] func(i Injector) (T, error)
```
## Basic Usage
### 1. Define and Register Services
Follow "Accept Interfaces, Return Structs":
```go
// Register a service (lazy by default)
do.Provide(injector, func(i do.Injector) (Database, error) {
return &PostgreSQLDatabase{connString: "postgres://..."}, nil
})
// Register a pre-created value
do.ProvideValue(injector, &Config{Port: 8080})
// Register a transient service (new instance each time)
do.ProvideTransient(injector, func(i do.Injector) (*Logger, error) {
return &Logger{}, nil
})
// Register an eager service (created immediately at startup)
do.ProvideValue(injector, &Config{Port: 8080})
```
### 2. Invoke Services
The container MUST only be accessed at the composition root:
```go
// Invoke with error handling — reserve for call sites outside the DI graph
// (e.g. an HTTP handler that must degrade gracefully instead of crashing)
db, err := do.Invoke[Database](injector)
// MustInvoke panics on error — preferred in providers, recovered by do.Invoke on the parent call
db := do.MustInvoke[Database](injector)
```
Inside a provider function, always use `do.MustInvoke` (or `MustInvokeAs`/`MustInvokeNamed`/`MustInvokeStruct`) rather than the error-returning variant. A provider already returns `(T, error)`, so propagating a dependency failure with `do.Invoke` costs an extra `if err != nil { return nil, err }` on every call. `do.MustInvoke` panics instead, but samber/do correctly catches and recovers that panic at the enclosing `Invoke` call and converts it back into a regular error — this recover happens inside the library itself, not in caller code, so `MustInvoke` is safe to use inside providers. The failure still surfaces as an error at the composition root, just without the manual boilerplate in every provider.
### 3. Service Dependencies
```go
func NewUserService(i do.Injector) (UserService, error) {
db := do.MustInvoke[Database](i)
cache := do.MustInvoke[Cache](i)
return &userService{db: db, cache: cache}, nil
}
do.Provide(injector, NewUserService)
```
### 4. Implicit Aliasing (Preferred)
Register a concrete type and invoke as an interface without explicit aliasing:
```go
// Register concrete type
do.Provide(injector, func(i do.Injector) (*PostgreSQLDatabase, error) {
return &PostgreSQLDatabase{}, nil
})
// Invoke directly as interface (implicit aliasing)
db := do.MustInvokeAs[Database](injector)
```
### 5. Named Services
Register multiple services of the same type:
```go
do.ProvideNamed(injector, "primary-db", func(i do.Injector) (*Database, error) {
return &Database{URL: "postgres://primary..."}, nil
})
mainDB := do.MustInvokeNamed[*Database](injector, "primary-db")
```
## Package Organization
Use `do.Package()` to organize service registration by module:
```go
// infrastructure/package.go
var Package = do.Package(
do.Lazy(func(i do.Injector) (*postgres.DB, error) {
cfg := do.MustInvoke[*Config](i)
return postgres.Connect(cfg.DatabaseURL)
}),
do.Lazy(func(i do.Injector) (*redis.Client, error) {
cfg := do.MustInvoke[*Config](i)
return redis.NewClient(cfg.RedisURL), nil
}),
)
// main.go
injector := do.New(infrastructure.Package, service.Package)
```
## Full Application Setup
```go
func main() {
injector := do.New(
infrastructure.Package,
repository.Package,
service.Package,
transport.Package,
)
server := do.MustInvoke[*http.Server](injector)
go server.ListenAndServe()
_ = injector.ShutdownOnSignalsWithContext(context.Background(), os.Interrupt)
}
```
## Best Practices
1. Depend on interfaces, not concrete types — lets you swap implementations in tests without touching production code
2. Each service should have one job — services with multiple responsibilities are harder to test and harder to replace
3. Keep dependency trees shallow — chains beyond 3-4 levels make initialization order fragile and errors harder to trace
4. Handle errors in provider functions — a silently failing provider creates a broken service that crashes later in unexpected places
5. Use scopes to organize services by lifecycle — request-scoped services prevent leaks, global services prevent redundant initialization
6. Use `do.MustInvoke*` inside provider functions instead of `do.Invoke*` — samber/do correctly catches and recovers the panic at the outer `Invoke` call, turning it back into a returned error, so it's safe to use inside providers and you get the same error propagation without the boilerplate
For scopes, lifecycle management, struct injection, and debugging, see [Advanced Usage](./references/advanced.md).
For testing patterns (cloning, overrides, mocks), see [Testing](./references/testing.md).
## Quick Reference
### Registration
| Function | Purpose |
| ------------------------------- | -------------------------------- |
| `do.Provide[T]()` | Register lazy service (default) |
| `do.ProvideNamed[T]()` | Register named lazy service |
| `do.ProvideValue[T]()` | Register pre-created value |
| `do.ProvideNamedValue[T]()` | Register named value |
| `do.ProvideTransient[T]()` | Register new instance each time |
| `do.ProvideNamedTransient[T]()` | Register named transient service |
| `do.Package()` | Group service registrations |
### Invocation
| Function | Purpose |
| -------------------------- | ----------------------------------------- |
| `do.Invoke[T]()` | Get service (with error) |
| `do.InvokeNamed[T]()` | Get named service |
| `do.InvokeAs[T]()` | Get first service matching interface |
| `do.InvokeStruct[T]()` | Inject into struct fields using tags |
| `do.MustInvoke[T]()` | Get service (panic on error) |
| `do.MustInvokeNamed[T]()` | Get named service (panic on error) |
| `do.MustInvokeAs[T]()` | Get service by interface (panic on error) |
| `do.MustInvokeStruct[T]()` | Inject into struct (panic on error) |
## Cross-References
- → See `samber/cc-skills-golang@golang-dependency-injection` skill for DI concepts, comparison, and when to adopt a DI library
- → See `samber/cc-skills-golang@golang-structs-interfaces` skill for interface design patterns
- → See `samber/cc-skills-golang@golang-testing` skill for general testing patterns
@@ -0,0 +1,154 @@
[
{
"id": 1,
"name": "v2-import-not-v1",
"description": "Tests whether the model uses samber/do/v2, never v1",
"prompt": "I want to set up dependency injection in my Go project using samber/do. Show me how to install it and create a basic container.",
"trap": "Without the skill, the model might import github.com/samber/do (v1) instead of github.com/samber/do/v2",
"assertions": [
{"id": "1.1", "text": "Uses go get github.com/samber/do/v2 (not github.com/samber/do without v2)"},
{"id": "1.2", "text": "Import path is github.com/samber/do/v2 in the code"},
{"id": "1.3", "text": "Uses do.New() to create the container"},
{"id": "1.4", "text": "Does NOT reference v1 API or import paths anywhere"}
]
},
{
"id": 2,
"name": "lazy-vs-eager-vs-transient",
"description": "Tests whether the model correctly chooses between lazy, eager, and transient service types",
"prompt": "I have three services in my Go app: (1) a database connection that must be ready before serving requests, (2) a user repository that's only needed when user endpoints are called, and (3) a request logger that should be a fresh instance per request. Register them with samber/do.",
"trap": "Without the skill, the model registers all three with do.Provide (lazy), missing do.Eager for the database and do.ProvideTransient for the logger",
"assertions": [
{"id": "2.1", "text": "Uses do.Provide with do.Eager wrapper (or equivalent) for the database connection that must be ready immediately"},
{"id": "2.2", "text": "Uses do.Provide (lazy, the default) for the user repository that's only needed on demand"},
{"id": "2.3", "text": "Uses do.ProvideTransient for the request logger that needs a fresh instance each time"},
{"id": "2.4", "text": "Correctly distinguishes between the three service lifecycle types"},
{"id": "2.5", "text": "Does NOT register all three services with the same registration function"}
]
},
{
"id": 3,
"name": "implicit-aliasing-invokeAs",
"description": "Tests whether the model uses InvokeAs for implicit aliasing instead of explicit aliasing",
"prompt": "I have a PostgreSQLDatabase struct that implements a Database interface. I want to register the concrete type but invoke it as the interface in my Go service. How do I set this up with samber/do?",
"trap": "Without the skill, the model uses do.As or do.MustAs for explicit aliasing, which is only needed for legacy code. Implicit aliasing via InvokeAs is preferred.",
"assertions": [
{"id": "3.1", "text": "Registers the concrete type *PostgreSQLDatabase with do.Provide"},
{"id": "3.2", "text": "Uses do.MustInvokeAs[Database] or do.InvokeAs[Database] to invoke as the interface"},
{"id": "3.3", "text": "Prefers implicit aliasing (InvokeAs) over explicit aliasing (As/MustAs)"},
{"id": "3.4", "text": "Does NOT require a separate alias registration step for this basic case"},
{"id": "3.5", "text": "The provider function returns the concrete type, not the interface"}
]
},
{
"id": 4,
"name": "package-organization",
"description": "Tests whether the model organizes service registrations using do.Package",
"prompt": "My Go project has infrastructure services (database, cache), domain services (user service, order service), and transport services (HTTP handlers). How should I organize the DI registrations with samber/do?",
"trap": "Without the skill, the model registers everything in main.go in a long list, missing do.Package for modular organization",
"assertions": [
{"id": "4.1", "text": "Uses do.Package to group related service registrations into separate packages/modules"},
{"id": "4.2", "text": "Creates separate package variables (e.g., infrastructure.Package, service.Package, transport.Package)"},
{"id": "4.3", "text": "Passes all packages to do.New() in main.go: do.New(infrastructure.Package, service.Package, transport.Package)"},
{"id": "4.4", "text": "Each package groups related services (infra, domain, transport) rather than one giant registration list"},
{"id": "4.5", "text": "Uses do.Lazy wrapper inside do.Package for lazy service registration"}
]
},
{
"id": 5,
"name": "scopes-for-lifecycle",
"description": "Tests whether the model uses scopes to organize services by lifecycle and visibility",
"prompt": "In my Go web app using samber/do, I have global services (config, logger) and per-request services (request context, current user). How do I prevent per-request services from being shared across requests?",
"trap": "Without the skill, the model registers everything in the root container, leading to shared per-request state across concurrent requests",
"assertions": [
{"id": "5.1", "text": "Uses do.Scope to create child scopes for per-request services"},
{"id": "5.2", "text": "Registers global/stateless services (config, logger) in the root container"},
{"id": "5.3", "text": "Creates a new scope per request for request-scoped services"},
{"id": "5.4", "text": "Child scope services can access parent (root) services"},
{"id": "5.5", "text": "Does NOT register request-scoped services in the root container"}
]
},
{
"id": 6,
"name": "testing-clone-override",
"description": "Tests whether the model uses container cloning and overrides for testing",
"prompt": "I have a Go service registered in samber/do that depends on a Database interface. I want to test the service with a mock database. How do I set up the test?",
"trap": "Without the skill, the model creates a brand new container from scratch in tests, missing the Clone+Override pattern that reuses the production container configuration",
"assertions": [
{"id": "6.1", "text": "Uses injector.Clone() or do.Clone() to clone the production container"},
{"id": "6.2", "text": "Uses do.Override or do.OverrideValue to replace the Database with a mock"},
{"id": "6.3", "text": "Invokes the service under test from the cloned container"},
{"id": "6.4", "text": "Does NOT build a completely new container from scratch for each test (unless justified)"},
{"id": "6.5", "text": "The test is isolated — changes to the cloned container don't affect the original"}
]
},
{
"id": 7,
"name": "health-check-interface",
"description": "Tests whether the model implements the Healthchecker interface for service health checks",
"prompt": "I have a database service registered in samber/do. I want to add health checking so I can verify the database is reachable. How do I implement this?",
"trap": "Without the skill, the model writes a standalone health check function instead of implementing the Healthchecker interface that integrates with do's lifecycle",
"assertions": [
{"id": "7.1", "text": "Implements a HealthCheck() method on the database service struct"},
{"id": "7.2", "text": "The HealthCheck method signature is either HealthCheck() error or HealthCheck(ctx context.Context) error"},
{"id": "7.3", "text": "Uses do.HealthCheck[Database](injector) to invoke the health check through the container"},
{"id": "7.4", "text": "Does NOT write a standalone function that manually fetches the service and pings it"},
{"id": "7.5", "text": "The health check actually tests connectivity (e.g., conn.Ping())"}
]
},
{
"id": 8,
"name": "graceful-shutdown-interface",
"description": "Tests whether the model implements the Shutdowner interface for graceful shutdown",
"prompt": "My Go application uses samber/do for DI. I need to gracefully shut down all services (close database connections, flush logs) when the application receives SIGINT. Show me how.",
"trap": "Without the skill, the model writes manual shutdown code with signal handling, missing do's ShutdownOnSignals integration and Shutdowner interface",
"assertions": [
{"id": "8.1", "text": "Implements Shutdown() or Shutdown(ctx context.Context) method on services that need cleanup"},
{"id": "8.2", "text": "Uses injector.ShutdownOnSignals or injector.ShutdownOnSignalsWithContext for signal-based shutdown"},
{"id": "8.3", "text": "Passes os.Interrupt or syscall.SIGTERM to the shutdown function"},
{"id": "8.4", "text": "Does NOT manually implement signal handling and iterate over services to shut them down"},
{"id": "8.5", "text": "May use context.WithTimeout for shutdown deadline"}
]
},
{
"id": 9,
"name": "composition-root-only",
"description": "Tests that the container is only accessed at the composition root, not passed around or used as a service locator",
"prompt": "I'm using samber/do for DI in my Go project. I have a UserHandler that needs a UserService. Should I pass the do.Injector to UserHandler so it can resolve its own dependencies?",
"trap": "Without the skill, the model passes the injector into business logic code, turning it into an anti-pattern service locator",
"assertions": [
{"id": "9.1", "text": "Advises against passing do.Injector into business logic or handler code"},
{"id": "9.2", "text": "States that the container should only be accessed at the composition root (main/startup)"},
{"id": "9.3", "text": "Shows resolving dependencies in the provider function using do.MustInvoke from the injector parameter"},
{"id": "9.4", "text": "The UserHandler receives its dependencies as constructor parameters, not the container"},
{"id": "9.5", "text": "Explains that passing the container creates a service locator anti-pattern that hides dependencies"}
]
},
{
"id": 10,
"name": "named-services-same-type",
"description": "Tests whether the model uses named services when registering multiple instances of the same type",
"prompt": "My Go app connects to two PostgreSQL databases — a primary for writes and a replica for reads. Both are *sql.DB instances. How do I register and retrieve them with samber/do?",
"trap": "Without the skill, the model tries to register both with do.Provide which overwrites the first registration, or wraps them in different types unnecessarily",
"assertions": [
{"id": "10.1", "text": "Uses do.ProvideNamed to register each database with a distinct name (e.g., 'primary-db', 'replica-db')"},
{"id": "10.2", "text": "Uses do.MustInvokeNamed or do.InvokeNamed to retrieve each database by name"},
{"id": "10.3", "text": "Both databases are registered as the same type (*sql.DB or a Database interface)"},
{"id": "10.4", "text": "Does NOT create unnecessary wrapper types just to distinguish the two databases"},
{"id": "10.5", "text": "Does NOT overwrite the first registration by using do.Provide twice for the same type"}
]
},
{
"id": 11,
"name": "struct-injection-with-tags",
"description": "Tests knowledge of struct injection using do tags",
"prompt": "I have a Go struct with multiple service dependencies that I want to inject from my samber/do container. Is there a way to avoid calling MustInvoke for each field manually?",
"trap": "Without the skill, the model manually invokes each dependency and assigns to struct fields, missing the do:\"\" tag-based struct injection",
"assertions": [
{"id": "11.1", "text": "Uses struct field tags with do:\"\" or do:\"service-name\" syntax"},
{"id": "11.2", "text": "Uses do.MustInvokeStruct or do.InvokeStruct to populate the struct"},
{"id": "11.3", "text": "Shows that do:\"\" uses the type for resolution and do:\"name\" uses a named service"},
{"id": "11.4", "text": "Does NOT manually call MustInvoke for each field when struct injection is available"}
]
}
]
@@ -0,0 +1,206 @@
# Advanced Usage
## Scopes (Module Tree)
Scopes SHOULD be used to organize services by module:
```go
root := do.New()
// Register shared services in root
do.Provide(root, func(i do.Injector) (Database, error) {
return &Database{}, nil
})
// Create child scope
apiScope := root.Scope("api")
// Services in apiScope can access root services
do.Provide(apiScope, func(i do.Injector) (UserService, error) {
db := do.MustInvoke[Database](i) // from root
return &userService{db: db}, nil
})
// Child scopes are isolated from each other
userScope := root.Scope("user")
```
Organize services by lifecycle and visibility:
```go
root := do.New()
// Global/stateless services in root
do.Provide(root, NewConfig)
do.Provide(root, NewLogger)
// Request-scoped services
requestScope := root.Scope("request")
do.Provide(requestScope, NewRequestContext)
```
## Explicit Service Aliasing
For rare cases when you need to adapt to legacy code:
```go
do.Provide(injector, func(i do.Injector) (*PostgreSQLDatabase, error) {
return &PostgreSQLDatabase{}, nil
})
do.MustAs[*PostgreSQLDatabase, Database](injector)
// Now both work:
db1 := do.MustInvoke[*PostgreSQLDatabase](injector)
db2 := do.MustInvoke[Database](injector)
```
Prefer implicit aliasing with `InvokeAs()` in most cases.
## Struct Injection
Inject services directly into struct fields using tags:
```go
type App struct {
Database *Database `do:""`
Logger *Logger `do:"app-logger"`
Config *Config `do:""`
}
app := do.MustInvokeStruct[App](injector)
```
## Lifecycle Management
### Health Checks
Implement the `Healthchecker` interface:
```go
func (d *Database) HealthCheck() error {
return d.conn.Ping()
}
// With context support:
func (d *Database) HealthCheck(ctx context.Context) error {
return d.conn.PingContext(ctx)
}
// Check health
if err := do.HealthCheck[Database](injector); err != nil {
log.Printf("Database unhealthy: %v", err)
}
```
### Graceful Shutdown
Implement the `Shutdowner` interface (4 variants):
```go
// Simple
func (d *Database) Shutdown() { d.conn.Close() }
// With context
func (d *Database) Shutdown(ctx context.Context) { d.conn.Close() }
// With error
func (d *Database) Shutdown() error { return d.conn.Close() }
// With context + error (most flexible)
func (d *Database) Shutdown(ctx context.Context) error { return d.conn.Close() }
```
Shutdown with timeout:
```go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
report := injector.ShutdownWithContext(ctx)
```
## Debugging
### List Services
```go
services := injector.ListProvidedServices()
for _, svc := range services {
fmt.Printf("%s: %s\n", svc.ScopeName, svc.Service)
}
```
### Explain Injector
```go
explanation := do.ExplainInjector(injector)
fmt.Println(explanation.String())
```
## Migration from Manual DI
Before (manual):
```go
func main() {
config := &Config{Port: 8080}
db := NewDatabase(config)
userRepo := NewUserRepository(db)
userService := NewUserService(userRepo)
api := NewAPI(userService)
}
```
After (with do):
```go
func main() {
injector := do.New()
do.Provide(injector, func(i do.Injector) (*Config, error) {
return &Config{Port: 8080}, nil
})
do.Provide(injector, NewDatabase)
// ... register other services
api := do.MustInvoke[*API](injector)
}
```
## Quick Reference
### Aliasing
| Function | Purpose |
| ------------------------------ | ----------------------- |
| `do.As[Initial, Alias]()` | Create type alias |
| `do.AsNamed[Initial, Alias]()` | Create named type alias |
### Lifecycle & Health
| Function | Purpose |
| -------------------------------- | --------------------------- |
| `do.HealthCheck[T]()` | Check service health |
| `do.HealthCheckNamed()` | Check named service health |
| `do.HealthCheckWithContext[T]()` | Health check with timeout |
| `do.Shutdown[T]()` | Gracefully shutdown service |
| `do.ShutdownNamed()` | Shutdown named service |
| `do.ShutdownWithContext[T]()` | Shutdown with timeout |
| `do.MustShutdown[T]()` | Shutdown (panic on error) |
### Container Management
| Function | Purpose |
| ------------------ | ----------------------------- |
| `do.New()` | Create new root container |
| `do.NewWithOpts()` | Create container with options |
| `injector.Scope()` | Create child scope |
### Debugging
| Function | Purpose |
| --------------------------------- | ------------------------------------ |
| `do.ExplainInjector()` | Visualize scope tree and services |
| `do.ExplainService[T]()` | Get service details and dependencies |
| `do.NameOf[T]()` | Get service name (use sparingly) |
| `injector.ListProvidedServices()` | List all available services |
| `injector.ListInvokedServices()` | List invoked services only |
@@ -0,0 +1,49 @@
# Testing with samber/do
## Container Cloning
Clone containers for isolated tests:
```go
func TestUserService(t *testing.T) {
// Create test container by cloning main container
testInjector := mainInjector.Clone()
// Override with mocks
mockDB := &MockDatabase{}
do.OverrideValue(testInjector, mockDB)
// Test with mocked dependencies
service := do.MustInvoke[UserService](testInjector)
// ... test code
}
```
## Reusable Test Helpers
```go
func SetupTestContainer(t *testing.T) do.Injector {
injector := do.New()
do.Provide(injector, func(i do.Injector) (Database, error) {
return &MockDatabase{}, nil
})
return injector
}
```
## Quick Reference
### Testing & Overrides
| Function | Purpose |
| -------------------------------- | ------------------------------- |
| `injector.Clone()` | Clone container for testing |
| `injector.CloneWithOpts()` | Clone with custom options |
| `do.Override[T]()` | Replace service (use in tests) |
| `do.OverrideNamed[T]()` | Replace named service |
| `do.OverrideValue[T]()` | Replace value service |
| `do.OverrideNamedValue[T]()` | Replace named value |
| `do.OverrideTransient[T]()` | Replace transient factory |
| `do.OverrideNamedTransient[T]()` | Replace named transient factory |