직관적으로 간추린 토비의스프링 1장.2 - 초난감 DAO를 개선해보자

디퐁·2022년 12월 25일
0
post-thumbnail

일러두기

  • 토비의스프링3.1 Vol.1을 읽고 정리한 글입니다.
  • 책을 이미 읽으신 분이 빠르게 훑으며 복습하는 용도로 보시면 좋을 것 같습니다.
  • 임의로 핵심만 간추렸습니다. 책과 설명하는 순서가 바뀌거나 장/절 목차 이름이 바뀌거나 아예 빠진 내용이 많습니다.

1 초난감 DAO를 개선해보자

초난감 DAO

User

  • id, name, password 세 개 프로퍼티를 가진 클래스

UserDao

  • DAO 클래스
  • 새로운 사용자를 생성하는 add() 메서드
    * User를 받아 그대로 DB에 만든다
  • 사용자 정보를 읽어오는 get() 메서드
    * id를 받아 해당 User를 DB에서 가져온다

main() DAO 테스트 코드

  • UserDao의 오브젝트를 생성해서 add()와 get() 메서드를 검증한다

스프링을 공부한다는 것은

아래와 같은 의문에 대한 답을 찾아나가는 과정이다

  • 왜 이 코드에 문제가 많다고 하는 것일까?
  • 잘 동작하는 코드를 굳이 수정하고 개선해야 하는 이유는 뭘까?
  • 그렇게 DAO 코드를 개선했을 때의 장점은 무엇일까?
  • 그런 장점들이 당장에, 또는 미래에 주는 유익은 무엇인가?
  • 또 객체지향 설계의 원칙과는 무슨 상관이 있을까?
  • 이 DAO를 개선하는 경우와 그대로 사용하는 경우, 스프링을 사용하는 개발에서 무슨 차이가 있을까?

스프링은 단지
이런 고민을 제대로 하고 있는지 끊임없이 확인해주고,
좋은 결론을 내릴 수 있도록 선구자들이 먼저 고민하고 제안한 방법에 대한 힌트를 제공해줄 뿐이다.

초난감 DAO를 개선하면서 답을 같이 찾아나가보자.

2 분리와 확장

객체지향의 세계에서는 모든 것이 변한다

  • 오브젝트에 대한 설계
  • 이를 구현한 코드
  • 사용자의 비즈니스 프로세스
  • 그에 따른 요구사항
  • 애플리케이션이 기반을 두고 있는 기술
  • 운영되는 환경

이 모든 것이 시간이 흐름에 따라 바뀌어간다.

미래의 변화를 어떻게 대비할 것인가

  • 이것이 개발자가 객체를 설계할 때 가장 염두에 둬야 할 사항이다.
  • 가상의 추상세계를 효과적으로 구성해야 자유롭고 편리하게 변경, 발전, 확장시킬 수 있다.
    • 효과적으로 구성하는 데 번거로운 작업이 다소 필요할 수 있지만
    • 그래야 변화에 효과적으로 대처할 수 있다

분리와 확장이 필요한 이유

  • 모든 변경과 발전은 한 번에 한 가지 관심사항에 집중해서 일어난다
    • 예를 들면 아래처럼 여러 관심사항에 대한 변경 요청이 보통 한번에 발생하진 않는다
    • DB를 오라클에서 MySQL로 바꾸면서, 웹 화면의 레이아웃은 다중 프레임에서 Ajax로 바꾸고, 매출이 일어날 때 지난달 평균 매출액보다 많으면 감사 시스템의 정보가 웹 서비스로 전송되는 동시에 로그의 날짜 포맷을 6가지에서 Y2K를 고려해 8자리로 바꿔라
  • 그런데 변경을 위해 수정이 필요한 부분은 한 곳에 집중되지 않는 경우가 많다
    • 예: 트랜잭션 기술을 다른 기술로 바꾸려면 수백 개의 DAO를 수정해야 하는 경우
  • 그래서 '관심사'를 분리하여, 한 곳에 한 가지 관심사만 모이게 해야한다
    1. 한 객체 안에 몰아넣거나
    2. '연관 객체'에 넣기

커넥션 만들기의 분리 ✨

  • 위 코드처럼 Connection을 가져오는 코드가 DAO의 각 메서드마다 중복되어 있으면
  • 나중에 Connection 가져오는 방법을 바꾸려면 DAO의 모든 메서드의 코드를 고쳐야 한다
    • 지금은 2개의 메서드밖에 없지만 만약 100개, 200개라면?

  • getConnection() 메서드로 분리해냈다.

DAO의 확장 ✨

이번엔 좀 더 나아가서 변화에 대응하는 수준이 아니라, 아예 변화를 반기는 DAO로 만들어보자.

상속을 통한 확장



  • getConnection()을 추상 메서드로 만들고
  • 서브 클래스에서 상속하여 구현하게끔 한다
  • 이제 단순히 변경이 용이하다라는 수준을 넘어서 손쉽게 확장될 수 있는 수준이 됐다.
    • UserDao의 기능 구현은 그대로 두고, 커넥션을 가져오는 방법만 확장하여 손쉽게 사용할 수 있다
  • 필요한 경우
    • UserDao의 기능들을 N사와 D사가 필요로 하는데
    • N사와 D사가 다른 DB를 사용해서 커넥션 가져오는 방법의 변경이 필요하다.
    • UserDao의 getConnection()을 변경할 수도 있지만, 이런 사례가 수백개가 된다면?
    • getConnection만 추상 메서드로 정의해 확장 가능하게 하면, UserDao의 코드를 수정할 필요 없이 getConnection의 구현만 오버라이드 하면 된다.

디자인 패턴

  • 설계 시 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 솔루션
  • 위에서 템플릿 메서드 패턴, 팩토리 메서드 패턴이 자연스럽게 사용되었다
    • '팩토리 메서드 ⊂ 템플릿 메서드'로 템플릿 메서드가 더 큰 개념이다

템플릿 메서드 패턴

  • 슈퍼 클래스에 기본적인 로직의 흐름을 만들고, 기능의 일부를 서브클래스에서 필요에 맞게 구현해서 사용하도록 하는 방법

팩토리 메서드 패턴

  • 템플릿 메서드를 통해 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것

상속을 통한 확장의 한계점

  • 다른 목적을 위해 상속을 이미 사용하고 있다면 위 두 패턴은 사용 불가하다
  • 슈퍼클래스 내부에 변경이 있을때 서브클래스를 다시 개발해야 할 수도 있다

커넥션 만들기의 분리✨

  • 이번에는 관심사가 다르고 변화의 성격이 다른 두 코드를 좀 더 화끈하게 분리해볼 생각이다.
    • 관심사1: 데이터 액세스 로직을 어떻게 만들 것인가
    • 관심사2: DB 연결을 어떤 방법으로 할 것인가



  • DB 연결과 관련된 부분을 아예 별도의 클래스에 담고
  • 이렇게 만든 클래스를 UserDao가 이용하게 하면 된다.
  • 정말 간단하지 않은가?

문제점

  • 성격이 다른 코드를 화끈하게 분리하기는 잘했다.
  • 하지만 이번엔 다른 문제가 발생했다.
    • UserDao의 코드가 SimpleConnectionMaker라는 특정 클래스에 종속되어 있어
    • 상속한 자식 클래스에서 DB 연결 방식을 변경할 수 없다
      • 예를 들어 ComplexConnectionMaker를 사용하고 싶다면
      • 상속으로는 사용할 수 없고 반드시 UserDao의 코드를 고쳐야만 한다.
  • 이 문제의 근본적인 원인은 UserDao가 바뀔 수 있는 정보에 대해 너무 많이 알고 있기 때문이다
    • DB 커넥션 클래스로 어떤 클래스를 사용할 것인지
    • 그 클래스에서 커넥션을 가져오는 메서드의 이름은 무엇인지까지 알고있다.
    • 이 때문에 UserDao는 DB 커넥션을 가져오는 특정한 구체적인 방법에 종속되어 버린 것이다.

인터페이스의 도입 ✨

  • 해결책은 두 개의 클래스 사이에 추상적인 느슨한 연결고리를 만들어주는 것이다.
    • 인터페이스!


  • 이제 UserDao는 자신이 사용할 클래스가 어떤 것인지 몰라도 된다.
  • 단지 인터페이스를 통해 원하는 기능을 사용하기만 하면 된다.

문제점

  • UserDao 클래스가 여전히 DConnectionMaker라는 구체적인 클래스에 종속되어 있다
    • new DConnecitonMaker()
  • 여전히 UserDao가 너무 많은 정보를 알고 있다
  • UserDao가 어떤 ConnectionMaker 구현 클래스의 오브젝트를 이용하게 할지에 대한 관심사를 분리해줘야 한다.
    • 그렇지 않으면 UserDao는 결코 독립적으로 확장 가능한 클래스가 될 수 없다.

관계설정 책임의 분리

  • UserDao 클래스 내부에서 DConnectionMaker라는 구체적인 객체를 만들어주고 있기에 종속성 문제가 있다.
  • 종속성 문제를 해결하려면
    • UserDao 내부가 아닌 외부에서 객체를 만들고
    • 만든 객체를 UserDao에 관계 설정을 해주면 된다.

생성자를 통해 주입받기



  • 드디어 UserDao에 있으면 안 되는 관심사항을 클라이언트로 떠넘기는 작업이 끝났다.
  • 상속을 통한 확장 방법보다 더 깔끔하고 유연한 방법으로
    • UserDao와 ConnectionMaker 클래스들을 분리하고
    • 서로 영향을 주지 않으면서도
    • 필요에 따라 자유롭게 확장할 수 있는 구조가 됐다

3 원칙과 패턴

  • 지금까지 초난감 DAO 코드를 개선해온 결과를
  • 객체지향 기술의 여러 가지 이론을 통해 설명하려고 한다.

개방 폐쇄 원칙

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

개선 전 초난감 DAO

  • DB 연결 방법을 확장하기 매우 불편하다
  • DB 연결 방법을 확장하고자 하면 DAO 코드를 변경해야 한다
    • 변화에 구멍이 나 있다
  • => 개방 폐쇄 원칙을 잘 따르지 못했다

개선 후 초난감 DAO

  • DB 연결 방법이라는 기능을 확장하는 데는 열려있다
  • 핵심 기능을 구현한 코드는 그런 변화에 영향을 받지 않으므로 변경에 닫혀있다

높은 응집도

  • 하나의 모듈, 클래스가 하나의 책임/관심사에만 집중되어 있다
  • 불필요한 외부의 관심/책임이 없으며
  • 하나의 공통 관심사는 한 클래스에 모여있다
  • 변화가 일어날 때 해당 모듈에서 변하는 부분이 크다

DB 커넥션 만드는 기능을 다른 방법으로 변경하려는 경우

  • 개선 전 초난감 DAO
    • 변경이 필요한 부분을 찾아내는 것도 번거로우며
    • 그렇게 변경한 것이 혹시 DAO의 다른 기능에 영향을 줘서 오류를 발생시키지 않는지 일일이 확인해야 한다
    • 테스트가 필요한 경우 Connection을 사용하는 초난감 DAO의 모든 메서드에 대해 테스트가 필요하다.
  • 개선 후 초난감 DAO
    • 그저 ConnectionMaker 구현 클래스를 새로 만들기만 하면 된다.
    • 무엇을 변경할지 명확하며, 그것이 UserDao 등 다른 클래스의 수정을 요구하지 않는다. UserDao의 기능에 영향을 주지도 않는다.
    • 테스트가 필요한 경우 새로 만든 ConnectionMaker 구현 클래스만 테스트하면 그만이다.

낮은 결합도

  • 하나의 오브젝트가 변경이 일어날 때에 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도가 낮다는 것
  • 느슨하게 연결돼있다는 것
    • 관계를 유지하는 데 꼭 필요한 최소한의 방법만 사용
    • 나머지는 서로 독립적이고 알 필요도 없게 만들기
  • 하나의 변경이 발생할 때 여타 모듈과 객체로 변경에 대한 요구가 전파되지 않는 상태
  • 책임과 관심사가 다른 오브젝트/모듈과는 낮은 결합도를 유지하는 것이 바람직하다!

개선 후 초난감 DAO

  • ConnectionMaker와 UserDao의 결합도가 낮다.
  • 사용할 ConnectionMaker 구현 클래스가 바뀌어도 UserDAO의 코드를 수정할 필요가 없다.

전략 패턴

  • 자신의 기능 맥락에서
  • 필요에 따라
  • 변경이 필요한 알고리즘을
  • 인터페이스를 통해 통째로 외부로 분리시키고
  • 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 패턴
  • 우리가 개선한 초난감 DOA에서 전략 패턴을 사용헀다.
    • Main-UserDao-ConnectionMaker
    • UserDao: 컨텍스트
    • Main: 클라이언트
    • ConnectionMaker: 전략
profile
Backend 취준생

0개의 댓글