기타 메모

Google Guice는 언제 쓸까

Jonchann 2023. 9. 15. 20:05

회사에서 내가 주로 맡고 있는 시스템이 Kotlin으로 만든 웹 어플리케이션의 일부인데 Google Guice 프레임워크를 사용하고 있었다. 이제까지 굳이 다 파악할 필요가 없어서 읽지 않았는데 컨테이너화를 하려고 보니 알아야겠어서 이것저것 찾아봤다.

 

먼저, Google이 DI용으로 만든 프레임워크다. 주스라고 읽는다. 2007년인가에 만들어진 꽤 오래된 프레임워크다.

 

DI가 무엇이냐 하면 Dependency Injection이라 해서 의존관계를 줄여주는 소프트웨어 패턴이다.

예를 들어 아래와 같은 구현은 ClientService에게 의존하고 있다. 내부에서 초기화하기 때문에 Service의 변화에 맞춰 Client를 수정해야 할 것이다.

class Service:
    def do(self) -> None:
        pass

class Client:
    def __init__(self) -> None:
        self.service = Service()

client = Client()

외부에서 전달하도록 하면 의존관계는 약해진다. Service를 수정해야해도 Client는 모르는 일이기 때문이다.

class Service:
    def do(self) -> None:
        pass

class Client:
    def __init__(self, service: Service) -> None:
        self.service = service

service = Service()
client = Client(service)

Guice는 Injector라는 것을 만들어 참조해야 하는 외부의 처리와의 의존관계를 약하게 만든다.

class Service {
    public main() {
        ...
    }
}

class Client {
    @Inject
    private service: Service

    public main() {
        injector = Guice.createInjector(object : AcstractModule() {
            override fun configure() {...}
        })
    }
}

createInjector는 아래와 같은 파라미터를 받는다. Module로 넘겨받는 것은 AbstractModule을 계승해서 만든 모듈이다.

public static com.google.inject.Injector createInjector(com.google.inject.Module... modules) { /* compiled code */ }
public static com.google.inject.Injector createInjector(java.lang.Iterable<? extends com.google.inject.Module> modules) { /* compiled code */ }
public static com.google.inject.Injector createInjector(com.google.inject.Stage stage, com.google.inject.Module... modules) { /* compiled code */ }
public static com.google.inject.Injector createInjector(com.google.inject.Stage stage, java.lang.Iterable<? extends com.google.inject.Module> modules) { /* compiled code */ }

다시 Injector를 만드는 코드를 보면, object :라는 표현이 보일텐데 이건 Kotlin의 object를 사용한 것이다. Kotlin에서는 추상 클래스를 직접 초기화 하는 것이 불가능하기 때문에 object :를 사용해 익명의 클래스로 만들어 넘기는 것이다.
(애초에 클래스를 만들 때 object를 붙인다면 그건 싱글톤 클래스로 만들겠단 뜻이다)

    public main() {
        injector = Guice.createInjector(object : AcstractModule() {
            override fun configure() {...}
        })
    }

 

AbstractModuleconfigure메서드를 오버라이트 해야한다. 필요한 DI 설정을 그 안에 기술한다.


예를 들어, 아래와 같이 install을 사용해 또 다른 AbstractModule을 주입할 수 있다.

FYI: @Provides아노테이션을 AbstractModule 내부 메서드에 붙이면 Injector가 invoke한다.

import com.google.inject.AbstractModule
import com.google.inject.Provides

object class SomeOtherModule: AbstractModule() {
    override fun configure() {
        bind(...)
    }

    @Provides
    private fun someMethod() = Unit
}

object class SomeModule: AbstractModule() {
    public main() {
        injector = Guice.createInjector(object : AcstractModule() {
            override fun configure() {
                install(SomeOtherModule)
            }
        })
    }

bindconfigure 내부에서 사용할 수 있는 메서드로 외부의 다른 클래스 등을 주입할 때 쓴다. 바인딩이란 어떠한 인터페이스에 외부의 다른 구현물을 연결짓는 것을 말한다.

 

가장 처음 얘기로 돌아가서 내가 파악해야 하는 것은 웹 어플리케이션의 일부인데 서블릿이 필요했기 때문에 ServletModule을 사용했다.
서블릿이란 웹 어플리케이션에서 클라이언트의 리퀘스트에 응답하기 위한 기술이다. JSP도 있다.

 

이것도 AbstractModule을 계승했다. 위에서 말한대로 주입할 수 있는데, 단, configure가 아니라 configureServlets을 오버라이트 해야한다.

    init {
        injector = Guice.createInjector(object : ServletModule() {
            override fun configureServlets() {
                install(...)
            }
        })