본문 바로가기

카테고리 없음

[GO] Golang DI Framework - 1

팀 내에서 한 분이 현재 우리 서비스 코드를 앞으로의 유지보수와 테스트가 용이한 환경을 만들기 위해 DI를 이용한 구조로 변경하자고 의견을 내었다.

 

현재는 Uber Fx Framework를 통해 DI를 이용하여 변경 해놓은 상태이지만 적용하는 과정에서의 공부한 점을 적어보자.

 

 

//TODO : 왜 DI를 이용하면 Test가 용이한 환경이 되는가?

 

사용하려는 DI Framework의 후보군은 아래 2가지였다.

1. google에서 만든 Wire Framework ( https://github.com/google/wire )

2. Uber에서 만든 fx Framework (https://github.com/uber-go/fx)

 

  • 차이점
    • Fx는 Lazy Loading을 기본으로 하여, 런타임 시점에 DI를 진행한다.
    • Wire는 컴파일 시점에 DI를 진행한다.
    • Wire는 런타임보다 컴파일타임에 이루어지기 때문에 좀 더 안정적이며, 에러 발생 시 더 쉽게 알 수 있다는 점이 있다.
    • 다만, 의존성간의 그래프를 분석하기 위한 코드 go generate가 필수적이라 편리성은 떨어질 수 있다

여러가지 이유가 있었지만 우리는 유지보수가 좀 더 잘 되고 있다는 측면에서 uber-fx를 사용하기로 결정했다.

 

 

Fx 공식 문서

https://uber-go.github.io/fx/index.html

 

Fx

Fx Fx is a dependency injection system for Go. Eliminate globals By using Fx-managed singletons, you can eliminate global state from your application. With Fx, you don't have to rely on init() functions for setup, instead relying on Fx to manage the lifecy

uber-go.github.io

 

 

DI란 무엇인가?

Dependency Injection의 줄임말로 의존성 주입이라고 한다.

1. 생성자 주입 (Constructor Injection)

2. 필드 주입 (Field Injection)

3. 수정자 주입 (Setter Injection) 등의 방법이 존재한다.

다만 , 생성자 주입을 권장한다.

 

package remittance

import (
    "fmt"
)

type Remittance struct {
    Currency string
    Amount   float64
}

func (r *Remittance) Print() {
    fmt.Printf("Currency: %s, Amount: %f", r.Currency, r.Amount)
}

// NewRemittance Remittance constructor
func NewRemittance(currency string, amount float64) *Remittance {
    return &Remittance{Currency: currency, Amount: amount}
}

type Send struct {
    remittance Remittance
}

func (s *Send) Send() {
    s.remittance.Print()
}

// NewSend Send constructor
func NewSend() *Send {
    remittance := NewRemittance("USD", 100.0)
    return &Send{remittance: *remittance}
}

 

위 코드에서는 main에서 Send 객체를 생성하는 순간 NewRemittance까지 호출하여 Remittance 객체 까지 생성된다.

굉장히 강하게 결합되어있다고 볼 수 있다.

 

만약 DI를 적용하는 형태로 개선하면 어떻게 될까

 

package main

import (
    "fmt"
)

type Remittance interface {
    PrintRemittance()
}

// Standard Remittance

type StandardRemittance struct {
    Currency string
    Amount   float64
}

func (s *StandardRemittance) PrintRemittance() {
    fmt.Printf("Standard Currency: %s, Amount: %f\n", s.Currency, s.Amount)
}

func NewStandardRemittance(currency string, amount float64) *StandardRemittance {
    return &StandardRemittance{Currency: currency, Amount: amount}
}

// Express Remittance

type ExpressRemittance struct {
    Currency string
    Amount   float64
}

func (e *ExpressRemittance) PrintRemittance() {
    fmt.Printf("ExpressRemittance Currency: %s, Amount: %f\n", e.Currency, e.Amount)
}

func NewExpressRemittance(currency string, amount float64) *ExpressRemittance {
    return &ExpressRemittance{Currency: currency, Amount: amount}
}

type Send struct {
    remittance Remittance
}

func (send *Send) PrintSend() {
    send.remittance.PrintRemittance()
}

func NewSend(remittance Remittance) *Send {
    return &Send{remittance: remittance}
}

func main() {

    var standardRemittance, expressRemittance Remittance

    standardRemittance = NewStandardRemittance("USD", 100.0)
    expressRemittance = NewExpressRemittance("IDR", 100.0)

    // Send Object Create
    standardSend := NewSend(standardRemittance)
    standardSend.PrintSend()

    expressSend := NewSend(expressRemittance)
    expressSend.PrintSend()

}

 

  • standardRemittance 객체를 넣어 standardSend 객체 생성 / expressRemittance 객체를 넣어 expressSend 객체를 생성
  • 위 코드에서 standard와 Express의 서로 다른 객체를 이전과 다르게 수정 없이 NewSend에 주입이 가능한 것을 볼 수 있다.