Page tree

의존성 주입(DI; Dependency injection)과 제어 역전(IoC; Inversion of Control)은 모듈간의 의존성을 줄이고 소스 수정없이 런타임에 더욱 유연한 소프트웨어를 만들수 있는 개발 패턴입니다.

 

의존성 주입은 객체 지향 언어에서 제공하는 인터페이스(Interface) 를 활용하여 구현됩니다.

 

사용자의 정보를 저장하고 읽어오는 UserRepository 클래스가 있고 이 안에는 저장소에서 사용자의 정보를 처리하는 $repository 라는 변수가 있습니다.

서비스 초기고 사용자가 적어서 사용자의 정보는 콤마로 구분된 CSV 파일에서 읽기로 하였습니다. 물론 향후 확장성을 위해 RepositoryInterface 라는 인터페이스를  만들고 CSVRepository 는  이를 구현한 클래스입니다.

 <?php
 
class UserRepository implements RepositoryInterface {
	private $repository;
 
	public function __construct() {
		$this->repository = new CSVRepository;
	}
}
 

이제 서비스 코드는 다음과 같이 인터페이스를 구현한 클래스의 인스턴스를 생성한 후 사용하도록 작성합니다.

// repository 생성 및 사용
$repos = new UserRepository();
$repos->store();

 

이제 서비스 사용자가  많아져서 사용자 정보를 MySQL DBMS 에서 처리하기로 하고 관련 로직을 구현하였습니다. 그리고 UserRepository 를 다음과 같이 수정합니다.

 <?php
 
class MySQLUserRepository implements RepositoryInterface {
	private $repository;
 
	public function __construct() {
		$this->repository = new MySQLRepository;
	}
}
// repository 생성 및 사용
$repos = new MySQLUserRepository();
$repos->store();

 

서비스는 더욱 사용자가 많아지고 성능 개선을 위해 저장소를 레디스(redis) key/value store 로 변경하기로 했습니다.

인터페이스를 잘 설계하고 이를 구현했다면 위와 같은 상황에서 RedisUserRepository 클래스를 구현하고 기존 서비스의 인스턴스 생성 소스를 수정하여 대처할 수 있습니다.

 <?php
 
class RedisUserRepository implements RepositoryInterface {
	private $repository;
 
	public function __construct() {
		$this->repository = new RedisRepository;
	}
}

 

하지만 외부에서 이 제품을 사용하겠다는 곳이 2개가 나타났는데 각각 DBMS 를 MS-SQL 과 PostgreSQL 을 사용한다고 합니다.

사용자 저장소를 인터페이스로 만들었지만 실제 서비스 코드에서는 인터페이스를 구현한 코드가 들어가야 하며 고객이 사용하는 DB 에 따라 제품을 나눌 경우 버전 관리가 매우 힘들어 지게 되며 새로운 DB가 추가될 경우 인스턴스를 생성하는 코드도 계속 수정되어야 합니다.

 <?php
 
class MSSQLUserRepository implements RepositoryInterface {
	private $repository;
 
	public function __construct() {
		$this->repository = new MSSQLRepository;
	}
}
// repository 생성 및 사용
$repos = new MSSQLUserRepository();
$repos->store();

 

의존성 주입과 제어의 역전 기술을 사용하면 인터페이스를 사용하는 소스의 수정없이 런타임에 의존성을 결정할 수 있습니다.

 <?php
 
class UserRepository implements RepositoryInterface {
	private $repository;
 
	public function __construct(RepositoryInterface $repository) {
		$this->repository = $repository;
	}
}

이제 의존성 제어 처리 로직에서 설정 파일이나 옵션에서 RepositoryInterface 를 구현한 인터페이스를 생성하여 UserRepository 에 주입해 주면 다른 저장소를 사용하는 곳이 나타나도 소스 수정을 하지 않고 설정으로 처리할 수 있습니다. 

$repos = App:make('UserRepository');
// $repos 사용

 

위와 같은 기법을 의존성 주입이라고 하며 외부에서 의존성이 주입되므로 이런 제어를 제어의 역전이라고 부릅니다. 

의존성 주입은 일반적인 서비스나 애플리케이션에서는 고려하지 않아도 되는 경우가 많지만 외부 환경에서 구동해야 하는 제품(솔루션, 프레임워크)은 도입을 고려해 봐야 하는  기능입니다.

 

의존성을 주입하는 방법은 위에서 설명한 생성자를 이용하는 방법과 세터(setter) 를 이용하는 방법이 있는데 라라벨은 전자를 사용하고 있습니다.

 

라라벨은 많은 부분을 런타임에 의존성을 주입하고 있으며 주입할 의존성 인터페이스는 타입 힌트를 통해 결정하고 있습니다.

또 의존성 인터페이스를 구체적으로 구현한 프로바이더는 보통 설정 파일에서 읽어 오므로 설정 파일의 내용만 변경하면 애플리케이션을 수정하지 않고 유연하게 패키지를 사용할 수 있습니다.

예로 config/session.php 내 세션 드라이버 설정을 다음처럼 한 줄만 바꾸면 캐시 데이타 저장소를 파일 시스템에서 DBMS 로 또는 redis 나 memcache 같은 key/value store 로 변경할 수 있습니다.

// 파일 세션 드라이버에서 레디스로 변경
//'driver' => env('SESSION_DRIVER', 'file')
'driver' => env('SESSION_DRIVER', 'redis')

 

Ref