본문 바로가기

Spring

DI란?

스프링의 모든 기술의 근간이 되는, 스프링의 핵심 엔진인 DI에 대해서 알아보겠습니다.


일단 그전에..

전략 패턴과 IOC에 대한 개념부터 알고가시는게 좋습니다.


전략패턴은 디자인 패턴 중 하나입니다. 

간단하게 정의하자면 상황에 따라 전략을 바꿔가며 사용할 수 있다고 해서 전략패턴 입니다.



전략패턴

우리가 자주 사용하는 DB 커넥션을 얻는 과정을 전략패턴화 해볼텐데, 사전에 몇가지 작업을 하고 진행하겠습니다.

먼저 일반적인 DB 커넥션을 얻어오는 코드는 아래와 같습니다.

public List selectData(){
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection(""
            + "jdbc:mysql://localhost:3306/testDB","id","pwd");

     // Connection을 통한 쿼리 작업
}

근데 지금 위의 메서드는 문제점이 있습니다. 지금은 그냥 하나의 메서드라 별 문제가 없어보이지만, 규모가 큰 프로젝트의 경우 위와 같은 메서드가 한두개가 아니죠. selectData 외에도 insertData, updateData, deleteData 등등 아주 많죠..

그러한 상황에서 메서드마다 저런식으로 Connection을 얻는 코드를 입력한다면 상당한 코드의 중복이 일어나게 됩니다.

100개의 메서드가 있다면 위의 Connection 얻는 코드가 100번 들어가게 됩니다.

근데 만약 여기서 DB 접속 정보가 변경이라도 되었다면?

끔찍합니다.. 메서드의 수 만큼 다 변경 작업을 해줘야겠지요.. 

게다가 이렇게 변경되는 양이 많으면 매우 위험해질 수도 있습니다. 사람이 하는 작업이라 실수의 가능성이 충분하거든요.

여러모로 문제점이 많습니다.


위의 문제점을 해결할 수 있는 방안으로 메서드 추출이 있습니다.

중복이 되는 Connection을 얻어오는 과정을, 하나의 메서드로 추출하는 방법입니다.

public class CarDAO{
    public List selectSUVData() throws Exception{
        Connection conn = getConnection();
        
        List list = // SUV 정보 쿼리
        return list;
    }
 
    public List selectSedanData() throws Exception{
        Connection conn = getConnection();
        
        List list = // 세단 정보 쿼리
        return list;
    }
 
    public List selectCoupeData() throws Exception{
        Connection conn = getConnection();
        
        List list = // 쿠페 정보 쿼리
        return list;
    }

    public Connection getConnection(){
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection(""
                + "jdbc:mysql://localhost/testDB","id","pwd");
        return conn;
    }
}

이런식으로 말이죠 ㅎㅎ

이렇게 하면 아무리 쿼리 작업을 하는 메서드가 많더라도, getConnection() 메서드의 코드만 수정해주면 됩니다.


중복을 제거하긴 했지만, 위 방식은 그렇게 이상적인 방식은 아닙니다.

객체지향 프로그래밍 개념 중에 관심사의 분리 라는 것이 있습니다.

관심이 같은 것끼리는 모으고, 관심이 다른 것은 따로 떨어져 있게 하는 것입니다.

위의 상황에서 살펴보면, 자동차 리스트를 얻어오는 행위와 DB 커넥션을 맺는 행위는 엄연히 다른 관심사입니다.

이러한 상황에서는 따로 클래스로 분리해줘서 관심사를 분리시키는 것이 좋습니다.

당장은 클래스를 굳이 하나 더 만들어야 해서 소스가 많아지고 번거로운 것 같지만, 조금만 규모가 커져도 차이점을 확 느끼실 수 있게 됩니다. 

언제나 상황에 따라 적절하게 관심사를 분리해줘야 유지보수가 편하고, 나중에 확장과 변경에도 용이해집니다.


Connection을 맺는 객체는 아래와 같은 형태로 바뀌게 될 것입니다.

public class CarDAO{
    private DBConnection dbConnection = new DBConnection();

    public List selectSUVData(){
        Connection conn = dbConnection.getConnection();
        // 자동차 정보 쿼리
        return list;
    }

    ...

}

public class DBConnection{
    public Conneciton getConnection(){
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection(""
                + "jdbc:mysql://localhost/testDB","id","pwd");
        return conn;
    }
}

확실히 관심사가 분리되었죠?

이제 전략패턴을 적용하기 위한 사전단계까지 완료되었습니다.


조금 더 여러가지 상황에서 생각해볼까요

현재 DBConnection 클래스의 getConnection 메서드에는 MySQL에 대한 Connection을 얻는 코드가 있습니다

근데 만약 DB의 종류나 URL, 계정 정보가 자주 바뀐다면 어떻게 될까요? (DB를 변경해가며 테스트하는 상황 등)

뭐 간단하게 매번 DBConnection 클래스의 getConnection 메서드의 내용을 수정해주면 되긴 합니다만 매번 그러기는 너무 비효율적이죠. 

그래서 그냥 각각 클래스로 만들어봤습니다.

MySqlDBConnection, OracleDBConnection, OracleDBConnection_dev 등의 형태로 말이죠.

근데 막상 만들고 보니 사용하는 것이 문제네요. 각자 클래스들로 구분을 해놓으면 변경될 때마다 해당 클래스의 선언부를 다 변경해줘야 합니다.

DBConnection dbConnection = new DBConnection() 


↓↓변경↓↓


MySqlDBConnection dbConnection = new MySqlDBConnection; // MySql 사용시

OracleDBConnection dbConnection = new OracleDBConnection; // Oracle 사용시

위와 같이 매번 상황에 따라 변경되어야 하는 꼴이 되는거죠.

이는 DB의 정보가 변경될 때마다 DAO의 소스를 다 바꿔줘야 하므로, 매우 비효율적입니다.


하지만 자바에는 이를 타개할 아주 좋은 문법이 있습니다. 바로 인터페이스 라는 것이지요.

DBConnection이라는 인터페이스를 정의하고, 나머지 MySQLDBConnection, OracleDBConnection 등의 클래스를 DBConnection 인터페이스를 상속한 클래스로 만들어버리면 됩니다.

그리고 DBConnection 구현체는 DAO 코드 내에서 직접 생성하지 않고, 생성자나 메서드의 파라미터를 통해 전달받게 해버리면 변경에 매우 유연한 코드가 됩니다.

즉, 아래와 같이 변경되겠네요.

public class CarDAO{
    private DBConnection dbConnection;

    public setDbConnection(DBConnection dbConnection){
        this.dbConnection = dbConnection;
   }
        
    public List selectSUVData(){
        Connection conn = dbConnection.getConnection();
        // 자동차 정보 쿼리
        return list;
    }
}

이렇게 해주면 사용하는 DB의 정보가 변경되더라도 DAO는 항상 변함없이 유지할 수 있게 됩니다.


위의 방식이 전략 패턴 입니다.

DBConnection 인터페이스를 변수로 선언했기 때문에, 저 변수에는 MySQLDBConnection, OracleDBConnection 등 DBConnection 인터페이스를 구현한 클래스라면 어느 클래스가 와도 상관없게 됩니다.

즉, 상황에 따라 구현 클래스를 바꿔 끼워가며(전략을 바꿔가며) 사용할 수 있다는 것입니다.

진짜 아주 좋은 디자인 패턴인 것 같습니다.. 



IOC

그럼 이제 IOC에 대해 설명을 해보겠습니다.

우리가 앞서 전략패턴을 적용하면서, 새로운 대상을 하나 등장시켰습니다.

그리고 DBConnection 구현체는 DAO 코드 내에서 직접 생성하지 않고, 생성자나 메서드의 파라미터를 통해 전달받게 해버리면 변경에 매우 유연한 코드가 됩니다.

라고 제가 말했었죠. 생성자나 메서드의 파라미터를 통해 전달받게 한다고요.

그럼 전달은 누가해줄까요?

여기서 IOC의 개념이 등장합니다.


IOC란 Inversion Of Control의 약자로, 제어의 역전 이라는 뜻을 가지고 있습니다.

일반적인 프로그램은 자신이 사용할 오브젝트를 직접 선택하고, 생성합니다. 변경전의 DAO에서 보셨듯이 DAO는 자신이 사용할 Connection 클래스를 직접 선택하고, 생성했습니다. ex) new MySqlDBConnection();

오브젝트의 대한 제어권을 자신이 가지고 있는, 능동적인 상태인 것이죠.

하지만 마지막에 전략패턴을 적용시키고, 변화에 유연한 코드를 만들면서 우리는 DAO를 수동적인 상태로 변환시켰습니다.

이때까진 필요한 오브젝트를 직접 만들다가, 이젠 남이 만들어준걸 전달받게 되죠.

오브젝트에 대한 제어가 역전 되었죠?


제어의 역전이란 말 그대로 제어권을 역전 시키는 것으로써,

제 3자에게 오브젝트에 대한 제어권을 넘겨주고, 자신은 제 3자가 선택하고 생성한 오브젝트를 받아서 사용하는 수동적인 상태가 되는 것을 말합니다.

이는 스프링 프레임워크가 아닌 다른 곳에도 이미 폭 넓게 적용되어 있는 개념입니다.


아까 전달을 누가해주지? 라는 의문을 가졌었죠..

IOC의 개념을 알고나면 답이 나오게 됩니다. 제 3자에게 전달을 받는 것이죠.

new MySqlDBConnection; 와 같은 코드를 이제 제 3자가 처리해줘야 합니다.

제 3자라고 별 다를것 없습니다. 그냥 클래스로 만들죠. 이름은 DaoFactory 정도로 하겠습니다.


public class DaoFactory{
    public CarDAO carDAO(){
        CarDAO carDao = new CarDAO();
        carDao.setDbConnection(getConnection());
        
        return carDao;
    }
    
    public DBConnection getConnection(){
        return new MySqlDBConnection_real(); // 변경되는 부분
    }
}

제 3자가 사용할 클래스를 선택하고, 전달해준 뒤, 반환해주고 있습니다.

이로 인해 DB 정보가 변경되더라도 변경될 곳은 제 3자인 DaoFactory로 좁혀지게 됩니다.

각 오브젝트를 관심사에 따라 적절하게 분리한 결과물입니다.. 정말 대단하군뇨..(토비 만세)


DAO를 사용하는 클래스들의 방식도 조금 변경되게 되겠네요.

이때까지 CarDAO를 사용하는 클래스, 소위 말하는 클라이언트들은 CarDAO를 직접 생성해서 사용했을 겁니다.

하지만 위와 같이 제 3자가 개입하므로써, CarDAO 클래스를 직접 생성할 필요가 없고

제 3자를 생성해서 요청하는 방식으로 변경되게 됩니다.



DI

그리고 방금, DaoFactory를 만드는 과정에서 DI를 발견하실 수 있습니다.

말씀드리기 전에, 일단 DI의 개념을 먼저 알고 가시죠.


DI란 Dependency Injection의 약자로, 의존관계 주입 이라는 의미를 가지고 있습니다.

의존관계란 별다른 뜻이 없습니다. 말 그대로 오브젝트가 서로 의존하고 있는 관계를 말합니다.

객체지향에서 의존하고 있다란 의미는, 하나의 오브젝트에서 다른 오브젝트를 사용할 때를 말합니다.

A라는 클래스에서 B라는 클래스를 사용할 경우, A클래스는 B클래스에 의존하고 있다 라고 표현합니다.

( 의존하고 있기 때문에 B클래스의 변경은 A클래스에 영향을 미칩니다.)


B클래스를 생성해서 A클래스에 넣어주는 과정, 이를 의존관계 주입이라고 보시면 됩니다.

의존관계에 있는 오브젝트를 생성해서 주입 해준다고 보시면 되는거죠.

주입은 누가 해주느냐? 제 3자가 해줍니다. IOC의 개념이네요.

결국 DI는 IOC의 세부적인 개념입니다!


개념을 알고나시니 DaoFactory에서 DI가 보이십니까?

carDao.setDbConnection(dBConnection());

바로 이 부분입니다!

CarDAO는 DBConnection 인터페이스에 의존하고, getConnection 메서드는 의존체인 DBConnection을 생성해줍니다.

그리고 제 3자인 DaoFactory가 CarDAO가 의존하는 DBConnection을 생성하고, setter를 통해 넣어줍니다.

이것이 마치 제 3자가 메서드를 통해 주입해주는 것과 같다고 해서 이를 의존관계 주입이라고 부릅니다.



후.. 기나긴 설명이 끝이 낫네요.

다음에는 스프링의 DI에 대해 포스팅하겠습니다.

읽어주셔서 감사합니다!

'Spring' 카테고리의 다른 글

템플릿 콜백 패턴  (0) 2016.06.28
테스트와 JUnit  (3) 2016.06.27
싱글톤과 스프링  (0) 2016.06.24
스프링 DI  (0) 2016.06.22
스프링 dataSource 예제 (Mysql, Oracle)  (0) 2016.06.09