본문 바로가기
Java

스프링 프레임워크와 제어 역전, 의존성 주입 🍃-02

by 돈민찌 2021. 11. 9.
반응형

스프링 프레임워크의 핵심은 무엇일까? 누군가는 스프링 프레임워크의 핵심을 한 단어로 표현해 보라 한다면 주저 없이 의존성 주입(DI, Dependency Injection)을 말할 것이다. 의존성 주입은 IoC(Inversion of Control)와 함께 많이 언급되는데 IoC는 제어를 역전하는 것을 보편적으로 설명하는 단어이고, 의존성은 이러한 IoC를 디자인 패턴으로 구현하는 방법 중에 하나다.

의존성 주입

의존성 주입을 설명하기에 앞서서 의존성이 무엇이고, 왜 필요한지에 대해 이해해야 한다. 할 일을 기록하는 Todo 애플리케이션을 만든다고 하자. 자바를 주 언어로 사용하는 사람이라면 다음과 같은 코드에 익숙할 것이다.

public class TodoService {
	private final FileTodoPersistence persistence;
    
    public TodoService() {
    	this.persistence = new FileTodoPersistence();
    }
    
    public void create(...) {
    	...
        persistence.create(...);
    }
}

Todo 애플리케이션은 TodoService 클래스를 가지고 있고, 이 클래스가 Todo 목록을 관리하는 기능을 제공한다고 가정하자. 이런 기능을 제공하려고 앞의 TodoService는 FileTodoPersistence를 사용한다. FileTodoPersistence가 파일에 Todo 목록을 저장할 수 있도록 도와주는 클래스일 경우 TodoService는 FileTodoPersistence 없이는 제 기능을 하지를 못한다. 따라서 TodoService는 FileTodoPersistence에 의존(dependent)한다. FileTodoPersistence에 의존하는 TodoService가 FileTodoPersistence 오브젝트를 생성하고 관리하는 것이다.

이렇게 만든 TodoServie는 main에서 이렇게 사용할 수 있다. 당장은 문제가 없어 보인다.

public static void main(String[] args) {
	TodoService service = new TodoService();
}

그런데 어느날 비즈니스 모델이 바뀌어 할 일 목록을 파일로 저장하는 것 대신에 데이터베이스에 저장하기로 한다. 그래서 다시 코드를 작성한다. DatabaseTodoPersistence 를 구현하기 시작해, FileTodoPersistence→DatabaseTodoPersistence로 변경한다. 그리고 비즈니스의 규모가 커지면서 데이터베이스 요금이 부담이 되기 시작했다. 이번에는 오브젝트를 AWS S3에 저장하는 로직으로 변경한다. 그런데 사업이 커지면서 이미 DatabasePersistence를 사용하는 Service가 수십개 생긴 상태다. 이제 각 클래스마다 코드를 변경해야 하는 불상사가 발생했다. 수십개면 수십개, 백개면 백개를 직접 코드를 변경해야 할 것이다.

또 다른 문제도 있을 수 있다. 처음의 코드 구현 방식을 계속 고쳐나가는 방식으로 운영해온 프로그램은 유닛 테스트를 작성하기 어려운 문제가 있다. 대충 껍데기만 갖고 있는 가짜 클래스(Mock Class)를 구현해 써야 하는데, 이미 FileTodoPersistence/DatabaseTodoPersistence를 내부적으로 생성하는 TodoService를 그대로 사용하기는 힘들어진다.

위의 상황처럼, 하나의 클래스가 다른 클래스를 직접적으로 의존하는 것은 코드 운영에 문제를 일으킬 가능성이 크다. 이런 문제점을 해결하는 방법 중 하나가 의존성 주입이다. 한 클래스가 의존하는 다른 클래스들을 내부에서 생성하고 가지고 있는 것이 아니라 외부에서 주입하는 것이다. 이를 구현하는 방법에는 생성자(Constructor)를 이용하는 방법과 세터(Setter)를 이용하는 방법이 있다.

서비스코드

// 생성자를 이용한 DI, 서비스 코드
public class TodoService {
	private final ITodoPersistence persistence; // interface라는 뜻으로 I
    
    public TodoService(ITodoPersistence persistence) {
    	this.persistence = persistence;  // 새로 생성하는 것이 아닌 인수로 주입받는다.
    }
    
    public void create(...) {
    	...
        persistence.create(...);
    }
}
// 세터를 이용한 DI, 서비스 코드
public class TodoService {
	private final ITodoPersistence persistence; // interface라는 뜻으로 I
    
    public void setITodoPersistence(ITodoPersistence persistence) {
    	this.persistence = persistence;  // 생성자 대신 setter를 만든다(return void)
    }
}

메인코드

// 생성자를 이용한 DI 메인함수

public static void main(String[] args) {
	ITodoPersistence persistence = new FileTodoPersistence();
	TodoService service = new TodoService(persistence);
}
// Setter를 이용한 메인함수

public static void main(String[] args) {
	ITodoPersistence persistence = new FileTodoPersistence();
	TodoService service = new TodoService();
    service.setITodoPersistence(persistence);
}

뭐야 개념은 되게 어려운데 별거 아닌 것 같은데? -라고 생각이 든다. 그게 의존성 주입 컨테이너가 전문인 일이고, 그 의존성 주입 컨테이너 중 하나가 스프링 프레임워크다. 프로젝트의 규모가 커질 수록, 또 관리해야 하는 오브젝트(빈)가 많아질 수록 오브젝트를 생성하는 데에만 할애하는 시간이 상당히 늘어난다. 이런 경우 주입 컨테이너의 도움을 받아 오브젝트 생성과 관리를 효율적으로 할 수 있다.

스프링 프레임워크는 어노테이션, XML, 또는 자바 코드(위와 같이) 자체를 이용해 오브젝트(빈) 간의 의존성을 명시할 수 있다. 그러면 스프링 프레임워크가 어플리케이션 시작 시 IoC 컨테이너라는 자바 오브젝트를 이용해 오브젝트 생성 및 관리를 알아서 해준다. 어노테이션과 XML, 자바 코드 중 각 상황에 맞게 의존성 주입을 구현하면 되지만, 지금은 배우는 과정인데다 아직 어노테이션조차 어색하고 서툴기 때문에 어노테이션에 집중한다.

다음에는 스프링 프레임워크 어노테이션들을 분류해보고 익혀놔야겠다.

예시코드 출처: ≪React.js, 스프링 부트, AWS로 배우는 웹 개발 101≫, 김다정 지음. 에이콘 출판.
반응형

댓글