golang-samber-do은(는) 무엇을 하나요?
Golang에서 samber/do를 사용한 의존성 주입 — 서비스 컨테이너, 생명주기 관리, 범위, 헬스 체크, 정상 종료, 모듈 구성. samber/do를 사용하거나 도입할 때, 코드베이스가 github.com/samber/do 또는 github.com/samber/do/v2를 임포트할 때, 또는 수동 생성자 주입을 DI 컨테이너로 리팩토링할 때 적용하세요.
개요
- 라이브러리:
samber/do(v1.x) - 목적: Go 애플리케이션을 위한 범용 의존성 주입 컨테이너 제공
- 핵심 개념: 서비스 제공자, 범위(Scopes), 생명주기, 헬스 체크, 정상 종료
- 호환성: 표준 Go, 대부분의 웹 프레임워크, Claude Code, Cursor, Codex와 같은 AI 코딩 에이전트
설치
go get github.com/samber/do@v1
기본 사용법
1. 서비스 제공자 등록
package main
import (
"github.com/samber/do"
)
type MyService struct {
Name string
}
func main() {
// 컨테이너 생성
container := do.New()
defer container.Shutdown()
// 서비스 제공자 등록
do.Provide(container, func(c *do.Container) (*MyService, error) {
return &MyService{Name: "hello"}, nil
})
// 컨테이너에서 서비스 가져오기
svc, err := do.Invoke[*MyService](container)
if err != nil {
panic(err)
}
println(svc.Name) // "hello" 출력
}
2. 생명주기 관리
samber/do는 서비스 생명주기 관리 내장 지원을 제공합니다:
- Lazy(기본): 첫 번째 호출 시 초기화
- Eager: 컨테이너 빌드 시 즉시 초기화 (
do.ProvideValue사용 시) - Transient(일시적): 호출할 때마다 새 인스턴스 생성
// 지연 초기화 (기본 동작)
do.Provide(container, func(c *do.Container) (*MyService, error) {
return &MyService{}, nil
})
// 조기 초기화 (값 등록)
do.ProvideValue(container, &MyService{Name: "eager"})
// 일시적 서비스 (호출할 때마다 새 인스턴스)
do.ProvideTransient(container, func(c *do.Container) (*MyService, error) {
return &MyService{}, nil
})
3. 의존성 주입
type Database struct {
DSN string
}
type UserRepository struct {
db *Database
}
func main() {
container := do.New()
defer container.Shutdown()
// 데이터베이스 서비스 등록
do.Provide(container, func(c *do.Container) (*Database, error) {
return &Database{DSN: "postgres://localhost:5432/mydb"}, nil
})
// 데이터베이스에 의존하는 UserRepository 등록
do.Provide(container, func(c *do.Container) (*UserRepository, error) {
db, err := do.Invoke[*Database](c)
if err != nil {
return nil, err
}
return &UserRepository{db: db}, nil
})
// UserRepository 가져오기 (데이터베이스가 자동으로 주입됨)
repo, _ := do.Invoke[*UserRepository](container)
_ = repo
}
고급 사용법
서비스 범위 (Scopes)
do.Scope를 사용하여 요청 범위의 서비스를 생성합니다:
func main() {
root := do.New()
defer root.Shutdown()
// 루트 범위에 데이터베이스 등록
do.Provide(root, func(c *do.Container) (*Database, error) {
return &Database{DSN: "postgres://..."}, nil
})
// 각 요청에 대한 범위 생성
requestScope := root.Scope("request-1")
defer requestScope.Shutdown()
// 요청 범위 서비스는 루트 서비스를 재정의할 수 있습니다
do.Provide(requestScope, func(c *do.Container) (*RequestContext, error) {
return &RequestContext{UserID: "123"}, nil
})
}
헬스 체크
samber/do는 서비스에 건강 상태 확인을 연결할 수 있습니다:
do.Provide(container, func(c *do.Container) (*Database, error) {
return &Database{}, nil
})
// 데이터베이스에 대한 헬스 체크 등록
do.HealthCheck[Database](container, "database.health", func(db *Database) (bool, error) {
err := db.Ping()
if err != nil {
return false, err
}
return true, nil
})
// 모든 헬스 체크 실행
healthy, err := do.HealthCheckAll(container)
if err != nil {
log.Printf("헬스 체크 실패: %v", err)
}
정상 종료 (Graceful Shutdown)
컨테이너가 종료될 때 정리 로직을 정의합니다:
do.Provide(container, func(c *do.Container) (*Database, error) {
db := &Database{}
// 연결 열기
db.Connect()
return db, nil
})
// shutdown 함수 등록
do.AddShutdown(container, func() error {
println("데이터베이스 연결 종료 중...")
return nil
})
// 컨테이너 종료 (등록된 모든 shutdown 함수가 역순으로 실행됨)
container.Shutdown()
모범 사례
모듈별 서비스 구성
관련 서비스를 모듈 함수로 그룹화하세요:
// database/module.go
package database
import "github.com/samber/do"
func Module(container *do.Container) {
do.Provide(container, NewConnection)
do.Provide(container, NewRepository)
do.HealthCheck[Connection](container, "db.ping", func(c *Connection) (bool, error) {
return c.Ping(), nil
})
}
// main.go
package main
import (
"myapp/database"
"github.com/samber/do"
)
func main() {
container := do.New()
defer container.Shutdown()
database.Module(container)
// 모든 애플리케이션 서비스 사용 가능
}
일반적인 함정 피하기
- 순환 의존성: 서비스 제공자에서 직접적인 순환 참조를 피하세요. 대신 인터페이스를 사용하거나 지연 초기화 패턴을 사용하세요.
- GC 누수: 모든 서비스가 더 이상 필요하지 않으면 제거되었는지 확인하세요. 컨테이너를 주기적으로 정리하거나 서비스 수명이 짧은 범위를 사용하세요.
- 명시적 제거:
container.Remove(name)또는container.RemoveAll()을 사용하여 서비스를 명시적으로 제거할 수 있지만, 일반적으로는 범위를 사용하는 것이 더 좋습니다.
문제 해결
ErrMissingDependency: 컨테이너가 아직 제공되지 않은 서비스를 요청할 때 발생합니다. 종속성이 올바른 순서로 등록되었는지 확인하세요.ErrServiceNotFound: 지정된 이름의 서비스가 없습니다. 서비스 이름이 일치하는지 확인하세요.- 성능: 지연 초기화는 시작 시간을 개선하지만 첫 번째 요청을 느리게 만들 수 있습니다. 중요 서비스에는 조기 초기화를 사용하세요.