ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Dependency Injection, 그 참을 수 없는 유연함
    Java & Kotlin 2023. 1. 3. 03:27
    반응형

    1) 서론

    혹시 자신의 친구를 누군가에게 소개해준 경험이 있으신가요?

     

    20대 초반, 내 친구 정도면 아주 훌륭하다고 생각하며 또 다른 친구에게 소개팅을 해준 적이 있는데요. 만약 잘 안되더라도 세 명이서 좋은 친구가 되면 좋을 것 같다는 생각이 들었었습니다.

     

    하지만 인생은 실전입니다. 언제나 최악이 기다리고 있었는데요. 두 친구는 잘 사귀던 중 어떠한 문제로 인해 크게 싸우고 헤어졌습니다. 주선자인 저도 괜히 가운데서 어색해졌는데요. 둘 중 누구를 만나기에도 어색한 상황이 되었었던 기억이 있습니다.

     

    당시에 많이 후회했었습니다. 나와 완전히 연관된 친구가 아닌, 친구의 친구를 소개시켜줬다면 어땠을까라는 생각이 들었습니다. 그러면 지금처럼 가운데에서 난감한 상황이 벌어지지 않았을 수도 있을 것 같습니다.

     

    개발도 마찬가지인것 같습니다. 현재의 비즈니스 요구사항을 대응하며, 미래의 변경사항도 함께 고민해야 하는 것 같습니다. 절대 바뀌지 않을 것이라고 생각하며 작성한 코드는 금방 수정이 필요할 것입니다. 만약 이를 고려하지 않았다면 변경하기 매우 어려울 것입니다.

     

    이번에는 미래의 변경사항에 유연하게 대비할 수 있는 DI(Dependency Injection, 의존성 주입)에 대해서 공유드리겠습니다.

     


    2) 현재의 비즈니스 요구사항

    • 햄버거 가게에서 고객은 햄버거를 주문
    • 햄버거 가게는 본사에서 납품받는 재료를 사용. 어디서 어떻게 생산된 것인지 알지 못 함
    • 현재 납품받는 빵은 A_Bread

     

    위의 비즈니스 요구사항을 아래처럼 구현합니다.

     

    즉 햄버거를 만드는데 필요한 재료(component)들이 세 가지입니다. 어디서 어떻게 생산된 재료들인지는 모릅니다. 본사에서 납품받는 세 가지 재료들이 필요할 뿐입니다.


    3) 현실 비즈니스 문제로 인한 요구사항 변경

    하지만 아래와 같은 문제가 발생했습니다.

    • A 브랜드 빵의 열악한 노동환경으로 인해 사회적 불매 발생
    • B 브랜드 빵으로 빠르게 교체 필요

     

     

    이때의 문제점은 두 가지입니다.

    1. 주문 메서드에 햄버거 재료(component) 강결합 (직접 인스턴스화)
    2. 재료(component) 변경에 따른 햄버거 가게(BurgerShop) 클래스 코드 변경 필요

     

    order 메서드에서 B_Bread, Lettuce, Patty 생성까지 책임지고 있습니다. '주문'이라는 행위를 가진 함수가 재료들이 어떻게 만들어지는지 알아야 합니다. 역할과 책임이 너무 과도합니다. 만약 다른 클래스에서 order 메서드를 사용할 때 반드시 B_Bread만을 사용해야 하는 문제가 있습니다.

     

    그리고 사용하는 component 변경으로 인해 핵심 비지니스 로직이 변경되었습니다.

    3. 1) 인터페이스를 통한 결합도 약화

    • Bread 인터페이스 생성
    • 주문 메서드에서 직접 인스턴스화가 아닌 생성자 통한 인스턴스화

     

    더 이상 B_Bread는 order 메서드와 직접 결합되어 있지 않습니다. BurgerShop 클래스가 인터페이스를 통해 B_Bread를 참조하고 있는데요. 인터페이스의 추상화를 통해 결합도가 약해졌습니다. 이제 더 이상 주문 역할 메서드는 빵 종류를 몰라도 됩니다. 

     

    하지만 여전히 조금 아쉬운 점이 존재합니다.

     

    만약 C_Bread로 변경이 발생하면 어떻게 될까요? 여전히 BurgerShop 클래스에서 직접 인스턴스화되는 코드를 수정해야 합니다.

        public BurgerShop() {
            this.bread = new C_Bread();
        }

     

    심지어 양배추, 패티, 참깨 등등 모든 재료들이 변경되면 어떻게 될까요? component가 변경된다면 핵심 비지니스 로직이 변경되어야 합니다. 외부 의존성에 의한 결합도가 아주 높다고 할 수 있습니다.

     

    햄버거 가게(BurgerShop)는 그저 본사에서 주는 빵(Component)을 사용할 뿐입니다. 어디서 생산이 되고, 어떤 빵인지 알아야 할 책임과 역할이 없습니다. 본사에 빵을 주문하면, 어떠한 빵이던지 받기를 원할 뿐입니다.

    3. 2) 의존성 주입 (Dependency Injection)으로 한 단계 더 약결합

    위와 같이 여전히 사용 component 변경에 따른 직접 인스턴스화의 방지가 필요합니다. 이를 위해 의존성 주입으로 변경합니다.

     

    위에서 언급했듯이 햄버거 가게는 어떤 빵인지 알 필요가 없습니다. 그저 본사가 빵을 내려주면, 사용하면 됩니다. 즉 component를 사용하는 클래스에서 직접 인스턴스화하는 것이 아닌 추상화된 인터페이스를 외부에서 넣어줍니다.

     

    의존성 주입은 생성자, setter 방식이 있는데요. 이 글에서는 주로 사용되는 생성자 방식으로 사용해보려고 합니다.

     

    BurgerShop 객체가 인스턴스화될 때 Bread 인터페이스를 주입해 줍니다. 필요한 객체를 외부에서 주입하는 방식입니다.

    BurgerShop burgerShop = new BurgerShop(bread);	// Bread 주입

     

    장점은 이렇습니다.

    • 햄버거 가게(BurgerShop)는 빵(Bread) 정보를 알아야 할 책임이 없어졌습니다
    • 빵(Bread) 종류가 변경되더라도 사용하는 햄버거 가게(BurgerShop)의 비지니스 로직은 변경되지 않아도 됩니다

    4) 결론

    스프링 프레임워크는 IoC 컨테이너를 활용해서 빈을 주입하는 방식을 사용합니다. 특히 lombok으로 생성자 애노테이션까지 사용하게 된다면 아주 쉽게 의존성 주입을 할 수 있는데요.

     

    이렇게 관습적이고 당연하게 코드를 작성하다 보니, 정말로 의존성 주입의 장점에 대해서는 생각해보지 못했습니다. 만약 이러한 장점을 이해할 수 있다면 bean으로 등록되지 않은 코드라고 하더라도 인터페이스를 이용해서 결합도를 낮출 수 있을 것 같습니다. 특히 외부 라이브러리를 사용할 때 좋을것 같습니다. 추상화를 통해 다른 라이브러리로의 변경이 발생하더라도 비지니스 로직까지의 변경을 방지할 수 있습니다.

     

    아직까지 의존성 주입에 대해서 깊은 이해를 했다고 할 수는 없습니다. 하지만 이번 계기로 코드 작성 시 결합도와 유지보수 관점에서의 인터페이스를 고민해볼 수 있을것 같습니다.

     

    제가 놓친 내용이 있다면 댓글로 알려주시면 감사하겠습니다.


    5) 참고 문헌

    https://www.martinfowler.com/articles/injection.html

    반응형

    댓글

Designed by Tistory.