티스토리 뷰

Dagger는 한 번 이해하고 나면 당연하게 느껴지지만 한 번 이해하는 것이 쉽지 않은 라이브러리라고 한다. 처음 접하기에는 생소한 개념이기 때문이다. 그럼에도 불구하고 많이 회자되는 이유는 Dagger를 적용함으로써 얻을 수 있는 장점이 더 크기 때문이라 생각한다. 생소한 개념과 구조를 이해하려면 이 라이브러리의 핵심이 무엇인지 잘 알고 있는 것이 중요하다. Dependency부터 Dependency Injection과 DI 라이브러리로써의 Dagger2의 특징을 정리해 보는 것이 이 글의 목적이다.

Dependency 는 무엇인가?

Dependency (또는 의존성)는 코드에서 두 모듈간의 연결이라고 볼 수 있다. 객체지향언어에서는 두 클래스간의 관계라고도 말한다. 일반적으로 둘 중 하나가 다른 하나를 어떤 용도를 위해 사용한다.

왜 Dependency는 위험할까?

하나의 모듈이 바뀌면 의존한 다른 모듈까지 변경이 이루어지기 때문이다. 그리고 테스트 가능한 애플리케이션을 만들고자 할 때 의존성이 있으면 유닛테스트 작성이 어려운데 유닛테스트의 목적 자체가 다른 모듈로부터 독립적으로 테스트하는 것을 요구하기 때문이다. 그래서 mock으로 의존성을 대체할 필요가 있다.

만약에 한 모듈에서 다른 모듈을 사용한다고 했을 때 테스트를 한다고 해보자. 테스트가 실패했을 때 현재 모듈에서의 실패일까 의존하는 모듈의 실패일까? 알 수 없다. 특히 High-level에서 Low-level로의 의존성은 위험하다. 만약 이 테스트 함수가 어떤 것을 데이터베이스에 저장하거나 원격 API를 요청하는 녀석이라면 더 심각해진다. 모든 'new'는 피하고 싶은 의존성이다.

Dependency를 해결하는 방법은? Dependency Injection!

바로 Dependency Injection 방식을 사용하는 것이다. 만약에 'new'를 사용해 모듈내에서 다른 모듈을 초기화하지 않으려면 어떻게 다른 모듈을 사용할 수 있을까? 객체 생성은 다른 곳에서 하고 생성된 객체를 참조하면 된다.

reference: https://speakerdeck.com/jakewharton/dependency-injection-with-dagger-2-devoxx-2014

기존의 'new'를 사용한 방식이 왼쪽 그림이라면 오른쪽은 추상적으로 DI가 적용된 모습이다. 참조로 DI를 구현하기 위해 일반적으로 객체 생성자에서 필요한 다른 객체를 받아오는 방법을 많이 사용하였는데 이 방법은 몇 가지 문제가 있다. 우선 전달받는 객체를 초기화하는 어떤 곳이 존재할 것이다. 그리고 의존하는 객체가 많을 경우 생성자의 파라미터는 계속해서 늘어나야 할까? 생성자에서 받은 객체들을 사용하기 위해 로컬 필드로 다시 넘겨받는 보일러 플레이트 과정은 또 얼마나 귀찮고 가독성을 떨어트리는가?

Dependency Injection이란 바로 이런 문제점에서 시작해서 모듈간의 의존성을 관리해주고 비즈니스 로직과 관련없지만 꼭 필요한 보일러플레이트 코드를 줄여주는 것을 의미한다.

Dependency Injector란

바로 Dependency Injection을 해주는 모듈로 보면 되는데 필요한 모듈의 인스턴스를 제공하며 의존성을 주입하는 모듈이다. 이 모듈이 모든 의존성을 관리하기 때문에 의존성이 한 점으로 지역화되고 컨트롤하기가 쉬워진다.

Dagger2는?

Dagger2는 최근에 가장 인기있는 DI 라이브러리중 하나이다. 이전 Dagger1의 무겁고, 복잡한 코드에, 의존성 추적이 어려워 디버깅이 힘든 단점을 모두 개선시켜 개발자 경험에 초점을 맞추어 다음과 같은 품질을 추구한다.

  • 추적가능성: find usage 와 open declaration으로 전체 앱 네비게이션, 코드를 통한 추적이 명확하고 구조화되어 있음

  • API의 명확성: 더욱 간단한 @Module 선언

  • 성능: 손으로 쓴 코드만큼 빠른, 프레임워크 주변에 코딩이 없음

Dagger2를 사용함으로써 얻을 수 있는 핵심은 다음과 같다.

  • 공유되는 인스턴스 접근 단순화 : View, EventHandler, Resource에 쉽게 접근할 수 있는 ButterKnife와 같이 공유된 인스턴스를 얻는 방법을 단순화할 수 있다. 예를 들어 MyTwitterApiClient 를 한번 싱글톤으로 선언하면 @Inject 어노테이션만으로 필드를 선언할 수 있다.

  • 복잡한 의존성의 쉬운 환경설정 : 객체가 생성되는 암묵적인 순서를 Dagger2는 의존성 그래프를 통해 들어가고 쉽게 이해하고 따라갈 수 있게 한다. 또한 직접 작성해야 하는 보일러플레이트 코드 작성을 줄이기 때문에 리펙터링에 도움이 되며 생성 순서는 신경쓰지 않고 어떤 모듈을 만들지에만 집중할 수 있도록 한다.

  • 쉬운 유닛/통합 테스트 : 의존성 그래프가 생성되므로 네트워크 응답을 하는 모듈을 우리가 원하는 행동으로 쉽게 바꿀 수 있다.

  • 스코프된 인스턴스 : 전체 앱 라이프사이클동안 지속하는 인스턴스 관리가 편하며 더 짧은 시간 (사용자 세션, 액티비티 라이프사이클 바운더리 등)으로 인스턴스를 정의할 수도 있다.

Reference

Dagger의 개념과 Android에서 Dagger를 활용하는 프로젝트를 따라해볼 수 있는 3편의 시리즈https://antonioleiva.com/dependency-injection-android-dagger-part-1/

Jake Wharton의 Dagger2 소개, Dagger2의 기본 개념 이해와 더불어 다른 DI라이브러리와의 차이를 알 수 있다. https://speakerdeck.com/jakewharton/dependency-injection-with-dagger-2-devoxx-2014

google의 codelab Wiki에서 Dagger2의 자세한 유즈케이스를 다이어그램과 같이 확인할 수 있다.https://github.com/codepath/android_guides/wiki/Dependency-Injection-with-Dagger-2

댓글