티스토리 뷰





반응형

DAO(Data Access Object)는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트다.

 

사용자 정보를 저장할 때는 자바빈 규약을 따르는 오브젝트를 이용하면 편리하다. 먼저 사용자 정보를 저장할 User 클래스를 만든다. 

 

자바빈디폴트 생성자가 있어야한다. 파라미터가 없는 디폴트 생성자는 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문이다. 그리고 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 set으로 시작하는 수정자 메소드와 get으로 시작하는 접근자 메소드를 이용해 수정 또는 조회할 수 있다.

 

사용자 정보를 DB에 넣고 관리할 수 있는 DAO 클래스를 만들어보자. 사용자 정보를 관리하는 DAO이므로 UserDao라는 이름으로 클래스를 생성한다. 사용자 정보의 등록과 아이디를 가지고 사용자 정보를 가져오는 두 개의 메소드를 만들어보기 위한 순서는 아래와 같다.

 

1. DB 연결을 위한 Connection을 가져온다.

2. SQL을 담은 Statement(또는 PreparedStatement)를 만든다.

3. 만들어진 Statement를 실행한다.

4. 조회의 경우 SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트에 옮겨준다.

5. 작업 중에 생성된(Connection, Statement, ResultSet)은 반드시 닫아준다.

6. JDBC API가 만들어내는 예외를 잡아서 직접처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.

 

위와 같은 형식으로 만들면 제법 복잡해 보이는 코드가 만들어진다. 그런데 이 클래스가 제대로 동작하는지 어떻게 확인할까? 먼저 웹 애플리케이션을 만들어 서버에 배치하고, 웹 브라우저를 통해 DAO 기능을 사용해보는 것이다. 하지만 간단한 UserDao코드가 동작함을 보려는 작업치고는 너무 부담이 크다.

main()을 이용한 DAO 테스트 코드

main 메소드를 만들어서 그 안에서 UserDao의 오브젝트를 생성해서 add()와 get()메소드를 검증하자.

이 코드는 보는 사람을 몹시 당황하게 만들 수 있는 초난감 코드의 조건을 두루 갖춘 DAO 코드다. 이제부터 객체지향 기술의 원리에 충실한 스프링 스타일의 코드로 개선해 볼 것이다.

관심사의 분리

세상에는 변하는 것과 변하지 않는 것이 있다. 하지만 객체지향의 세계에서는 모든 것이 변한다. 여기서 변한다는 것은 변수나 오브젝트 필드의 값이 변한다는 게 아니다. 오브젝트에 대한 설계와 이를 구현한 코드가 변한다는 뜻이다. 소프트웨어에서 끝이란 개념은 없다. 요구 사항이나 비즈니스 프로세스가 끊임없이 변하고 발전한다. 예를 들어 DB 접속용 암호를 변경하라는 요구사항이 생겼는데 이로인해 DAO 클래스 수백 개를 모두 수정하거나 다른 개발자가 개발한 코드에 변경이 일어날 때 마다 내가 만든 클래스도 함께 수정을 해줘야한다면 얼마나 끔직할지 모른다. 그렇기 때문에 관심이 같은 것끼리느 모으고, 관심이 다른 것은 따로 멀어져 있게 하는 것이다.

 

add()와 get()의 가장 큰 문제점은 커넥션이 두 메소드 모두 구현되어 있다. 그럼 앞으로 새로운 메소드를 생성할 때마다 똑같은 코드를 만들어야 하고 만약 2천개의 메소드가 만들어진 상태서 수정을 하려면 2천개의 메소드를 모두 수정해야하는 어마어마한 일이 벌어진다. 그래서 먼저 커넥션의 기능을 분리해 getConnection()이라는 메소드로 뽑아서 넘겨주면된다.

 

그리고 UserDao에 add(), get(), getConnection()의 정보를 숨기고 고객사한테 팔고 싶은데 DB의 정보가 자주 바뀐다고하면 어떻게 수정을 할 것인가? 바로 상속을 통한 방식으로 넘겨서 확장하게끔 하면 된다.

이렇게 슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메서드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법을 디자인 패턴에서 템플릿 메소드 패턴이라고 한다. 서브클래스에서 구체적인 오브젝트를 생성 방법을 결정하는 것은 팩토리 메소드 패턴이라고 한다.

 

하지만 상속방식에는 커다란 문제들이 있다 왜냐하면 이렇게 상속을 받게 되면 다중 상속이 안될 뿐더러 상속이라는 긴밀한 관계 때문에 문제가 생긴다. 즉, 부모 클래스를 변경하게 되면 상속받은 모든 클래스에 영향이 가기 때문에 부모클래스는 건들일 수 없는 상태가 될 수도 있다.

클래스의 분리

SimpleConnectionMaker라는 새로운 클래스를 만들고 DB 생성 기능을 그 안에 넣는다. 그리고 UserDao는 new 키워드를 사용해 SimpleConnectionMaker 클래스의 오브젝트를 만들어두고, 이를, add(), get() 메소드에서 사용하면 된다. 각 메소드에서 매번 새로 만든 클래스를 만들 수도 있지만, 그보다는 한 번만 오브젝트로 만들어서 저장해두고 이를 계속 사용하는 편이 낫다.

하지만 이렇게 되어버리면 UserDao 클래스만 공급하고 상속을 통해 DB 커넥션 기능을 확장해서 사용하게 했던 게 불가능해졌다. 왜냐면 UserDao의 코드가 SimpleConnectionMaker라는 특정 클래스에 종속되어 있기 때문에 상속을 사용했을 때처럼 UserDao 코드의 수정 없이 DB 커넥션 생성 기능을 변경할 방법이 없다. 다른 방식으로 DB 커넥션을 제공하는 클래스를 사용하기 위해서는 UserDao 소스코드의 다음 줄을 직접 수정해야한다. UserDao의 소스코드를 함께 제공하지 않고는 DB 연결 방법을 바꿀 수 없다는 문제에 직면하게된다.

simpleConnectionMaker = new SimpleConnectionMaker();

이렇게 클래스를 분리한 경우에도 상속을 이용했을 때와 마찬가지로 자유로운 확장이 가능하게 하려면 두 가지 문제를 해결해야 한다. 첫째는 SimpleConnectionMaker의 메소드가 문제다. 만약 고객사에서 만든 DB 커넥션 제공 클래스는 openConnection()이라는 메소드 이름을 사용했다면 다음과 같이 일일이 변경해야한다. 이게 수십, 수백개가 되면 작업의 양이 너무 많아진다.

Connection c = simpleConnectionMaker.openConnection();

 두 번째 문제는 DB 커넥션을 제공하는 클래스가 어떤 것인지를 UserDao가 구체적으로 알고 있어야 한다는 점이다. UserDao에 SimpleConnectionMaker라는 클래스 타입의 인스턴스 변수까지 정의해놓고 있으니, 고객사에서 다른 클래스를 구현하려면 어쩔수 없이 UserDao 자체를 다시 수정해야한다.

 

이런 문제의 근본적인 요인은 UserDao가 바뀔 수 있는 정보, 즉 DB 커넥션을 가져오는 클래스에 대해 너무 많이 알고 있기 때문이다. 어떤 클래스가 쓰일지, 그 클래스에서 커넥션을 가져오는 메소드의 이름이 뭔지까지 일일이 알고 있어야한다. 따라서 UserDao는 DB 커넥션을 가져오는 구체적인 방법에 종속되어 버린다.

인터페이스의 도입

이 문제에 해결하기 제일 좋은 방법은 두 개의 클래스가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 느슨한 연결고리를 만들어주는 것이다. 추상화란 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업이다. 이에 가장 유용한 도구가 바로 인터페이스다.

 

인터페이스는 자신을 구현한 클래스에 대한 구체적인 정보는 모두 감춰버린다. 결국 오브젝트를 만들려면 구체적인 클래스 하나를 선택해야겠지만 인터페이스로 추상화해놓은 최소한의 통로를 통해 접근하는 쪽에서는 오브젝트를 만들때 사용할 클래스가 무엇인지 몰라도 된다. 인터페이스를 통해 접근하게 하면 실제 구현 클래스를 바꿔도 신경 쓸 일이 없다.

 

인터페이스에는 어떻게 하겠다는 구현 방법은 나타나 있지 않다. 그것은 인터페이스를 구현한 클래스들이 알아서 결정할 일이다. UserDao가 인터페이스를 사용하게 한다면 인터페이스의 메소드를 통해 알 수 있는 기능에만 관심을 가지면 되지, 그 기능을 어떻게 구현했는지에는 관심을 둘 필요가 없다.

위와 같이 인터페이스를 적용한 방식으로 한다면 고객사가 DB 접속용 클래스를 다시 만든다고 해도 UserDao의 코드를 뜯어 고칠 일은 없을 것이다. 왜냐하면 인터페이스 참조형 변수를 만들고 사용할 커넥션 클래스의 인스턴스를 생성해서 사용하기 때문이다. 그리고 클래스가 바뀐다고 한들 같은 인터페이스를 구현한다면 같은 메소드를 사용하기 때문에 메소드 이름이 변경될 걱정도 할 필요가 없다.

 

하지만 여전히 문제 하나가 존재하는데, 바로 UserDao의 생성자에서 DConnectionMaker()라고 인스턴스가 지정되어 있다는 점이다. 나중에 NConnectionMaker()로 변경 된다면 UserDao 코드를 줘야한다는 단점이 있다는 것이다. 즉 아직도 종속적인 부분을 완전히 해결 못한 경우다.

public UserDao(ConnectionMaker connectionMaker) {
 this.connectionMaker = connectionMaker;
}

그래서 생성자를 위와 같이 바꾸고 클라이언트에 해당되는 main()에서 어떤 커넥션을 사용할지 결정해서 인스턴스를 넘기는 방식을 사용할 수 있다.

개방 폐쇄 원칙(OCP, Open-Closed Principle)

클래스나 모듈은 확장에 열려 있어야 하고 변경에는 닫혀 있어야 한다.

인터페이스를 통해 제공되는 확장 포인트는 확장을 위해 활짝 개방되어 있다. 반면에 인터페이를 이용하는 클래스는 자신의 변화가 불필요하게 일어나지 않도록 굳게 폐쇄되어 있다.

스프링 IoC의 용어 정리

빈 또는 빈 오브젝트는 스프링이 IoC방식으로 관리하는 오브젝트라는 뜻이다. 관리되는 오브젝트라고 부르기도 한다. 주의할 점은 스프링을 사용하는 애플리케이션에서 만들어지는 모든 오브젝트가 다 빈은 아니라는 사실이다. 그 중에서 스프링이 직접 그 생성과 제어를 담당하는 오브젝트만을 빈이라고 부른다.

 

빈 팩토리

스프링의 IoC를 담당하는 핵심 컨테이너를 가리킨다. 빈을 등록하고, 생성하고, 조회하고 돌려주고, 그 외에 부가적인 빈을 관리하는 기능을 담당한다. 보통은 이 빈 팩톨리를 바로 사용하지 않고 이를 확장한 애플리케이션 컨텍스트를 이용한다. BeanFactory라고 붙여쓰면 빈 팩토리가 구현하고 있는 가장 기본적인 인터페이스의 이름이 된다. 이 인터페이스에 getBean()과 같은 메소드가 정의되어 있다.

 

애플리케이션 컨텍스트

빈 팩토리를 확장한 IoC 컨테이너다. 빈을 등록하고 관리하는 기본적인 기능은 빈 팩토리와 동일하다. 여기에 스프링이 제공하는 각종 부가 서비스를 추가로 제공한다. 빈 팩토리라고 부를 때는 주로 빈의 생성과 제어의 관점에서 이야기하는 것이고, 애플리케이션 컨텍스트라고 할 때는 스프링이 제공하는 애플리에키션 지원 기능을 모두 포함해서 이야기하는 것이라고 보면된다. 스프링에서는 애플리케이션 컨텍스트라는 용어를 빈 팩토리보다 더 많이 사용한다. ApplicationContext라고 적으면 애플리케이션 컨텍스트가 구현해야 하는 기본 인터페이스를 가리키는 것이기도 하다.

ApplicationContext는 BeanFactory를 상속한다.

 

컨테이너 또는 IoC 컨테이너

IoC 방식으로 빈을 관리한다는 의미에서 애플리케이션 컨텍스트나 빈 팩토리를 컨테이너 또는 IoC 컨테이너라고도 한다. 후자는 주로 빈 팩토리의 관점에서 이야기하는 것이고, 그냥 컨테이너 또는 스프링 컨테이너라고 할 때는 애플리케이션 컨텍스트를 가리키는 것이라고 보면된다. 컨테이너라는 말 자체가 IoC의 개념을 담고 있기 때문에 이름이 긴 애플리케이션 컨텍스트 대신에 스프링 컨테이너라고 부르는 걸 선호하는 사람들도 있다. 

 

 

 

 

 

 

 

 

반응형
댓글
반응형
최근에 달린 댓글
글 보관함
Total
Today
Yesterday
최근에 올라온 글
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31