chapter1에서는 Spring FrameWork의 관심사인 Object 설계와 구현 및 동작원리에 초점을 두고 있다. Dao 설계를 이상적인 설계인 ‘변경이 용이한 구조’를 만들기 위해 순차적으로 코드를 Refactorying 하여서 가장 이상적인 설계를 설명하고 이와 밀접히 관련되어 있는 스프링의 주요 개념인 IoC(Inversion of Control) 개념을 소개한다.
ps.close(); c.close(); } public User get(String id)throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); Connection c = DriverManager.getConnection( "jdbc:mysql://localhost:3306/spring_study?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8 ","root","1234");
PreparedStatement ps = c.prepareStatement( "select * from users where id = ?" ); ps.setString(1,id); ResultSet rs = ps.executeQuery(); rs.next(); User user = new User();
너무 많은 관심사가 하나의 method안에 있다. ex) DB Connection을 어떡할것인지, 리소스 처리를 어떡할것인지 관심사의 분리는 별도의 class로 모듈화 함으로 분리될 수 있다. 책에서는 순차적으로 중복된 code를 제거하기 위해 method extraction 을 소개하고 있다.
method 추출을 통해 중복된 코드를 하나의 method로 분리하였다. 중복된 코드를 줄임으로서 얻는 장점은 변경의 원인도 중복이 아닌 한곳이라는 것이다. 즉 변경이 생겼을떄 한곳만 변경하면 된다.
여기서 나아가 변화에 용이한 구조를 만들기 위해 상속구조를 소개하였다. DB Connection 정보를 추상화하여, Client들 별로 사용하고 싶은 DB Connection을 구현한다.
ex) UserDao의 DB connection을 A,B,C,D사에게 넘겨주고, 구현은 알아서 하고, UserDao는 DB Connection의 구체구현을 몰라도 그냥 DB Connection이 반환되었구나 라고 알면된다는 소리다.
1 2 3 4 5 6 7 8 9 10 11
publicabstractclassUserDao{ // 추상 클래스를 만들어 add,get 은 구현하고 connection은 하위 클래스에게 구현의 책임을 맡긴다. publicabstract Connection getConnection()throws ClassNotFoundException,SQLException;
위 코드의 주요 장점은 확장성이다. 새로운 DB Connection을 구현하고 싶다면 그냥 상속받아서 구현하면 된다.
위와 같이 부모class에서 기본적인 logic흐름이 있고, 그 일부를 추상 method나 overrding가능한 protected method로 만들고 자식class에서 구현해서 사용하는 방법을 Template Method Pattern이라고 한다. 자세한 내용은 Design Pattern 카테고리에 별도로 첨부하였다
하지만 DB연결방식을 하위class로 그 외Data Access 로직을 상위 class로 잡은 상속 구조에도 단점이 있다.
변화의 이유 , 시기, 주기가 다르다. ex) 데이터를 어떻게 전처리 할 것인지 로직이 변경되어도 DB Connection을 얻는 로직은 변경되지 않는다.
이러한 문제를 책에서는 상속구조가 아닌 별도의 class로 관심사를 분리하는 형태로 접근하였다.
private SimpleConnectionMaker simpleConnectionMaker = new SimpleConnectionMaker(); // 1. Data를 어떻게 추가할 것인가에 대한 관심 publicvoidadd(User user)throws ClassNotFoundException, SQLException { Connection c = simpleConnectionMaker.makeNewConnection(); // ~
} } // 2. DB 연결에 대한 관심 publicclassSimpleConnectionMaker{ public Connection makeNewConnection()throws ClassNotFoundException, SQLException{ Class.forName("com.mysql.jdbc.Driver"); Connection c = DriverManager.getConnection( "jdbc:mysql://localhost:3306/spring_study?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8 ","root","1234"); return c; }; } ```` 관심사를 별도의 class로 분리하였지만, 상속구조에서 가졌던 기능 확장이 다시 불가능해졌다. 예를 들면 DB 연결 method명이 변경되면 일일히 Dao 에서 변경해야 한다. 또한 UserDaoClass가 어떤 DBconnection을 받는지 알아야 한다. ```java MariaDBConnectionMaker.makeNewConnection(); MysqlDBConnectionMaker.makeNewConnection(); OracleDBConnectionMaker.makeNewConnection();
정리를 하면 UserDao가 DB connection을 가지고 오는 class에 대해 너무 많이 알면, 종속적으로 되기 때문에 DB connection이 변경되면 같이 변경된다. 이를 소프트웨어공학에서는 SDP(stable dependency principle) 을 따르지 않는다고 하는데, 변경될 가능성이 큰 구체 class를 의존하는 것을 말한다.
책에서는 class를 분리하면서도 UserDao가 DB Connection에 대해 종속적이지 않는 구조를 만들기 위해 다음과 같이 추상화 계층을 만들었다.
위 구조는 구체 class인 B의 공통적인 틀을 정의하는 추상클래스를 도출하고, 하위 구현방법은 B의 구체 class에서 구현하였다. 그리고 B와 의존성 관계에 있는 A class는 B의 추상클래스에 의존함으로써, B의 구체 class의 서비스 로직이 변경된다고 하더라도 영향받지 않는다.
책에서도 마찬가지로 DB연결정보를 인터페이스를 통해 공통적인 성격을 추상화하고,UserDao는 추상화한 인터페이스에 접근하여 실제로 DB연결정보가 어떻게 구현되어있는지는 종속적이지 않도록 구조를 변경하였다
위와 같이 구조 변경을 통해서 이제 확장성까지 갖추게 되었다. 다른 DB연결정보로 확장하고 싶으면 UserDao에서 new Dconnection() 대신 new xConnection(); 으로 변경해주면 된다. 하지만 바로 이부분이 문제이다.
1 2 3 4 5 6 7
publicclassUserDao_V4{ private ConnectionMaker connectionMaker; publicUserDao(ConnectionMaker connectionMaker){ // UserDao에서 구체적인 하위 class를 명시해야 한다. this.connectionMaker = new DconnectionMaker(); } }
UserDao에서 대부분의 종속성은 제거하였지만, 생성자에서 외부 객체를 주입받는 과정에서 어떤 객체를 사용할지 구체클래스를 명시를 하고 있다. 즉 어떤 연결정보를 사용할지에 따라 DconnectionMaker인지, 다른 connectionMaker인지 UserDao에서 명시해야 한다는 단점이 있다.
1
ConnectionMaker connectionMaker = new DconnectionMaker();
위 관계는 class사이의 관계가 만들어진것이 아니라 객체사이에 관계가 맺어진것으로 class사이의 관계와는 구분해야 한다.
class사이의 관계는 클래스 명이 type으로 드러나야하는 반면 object사이의 관계는 다형성 특징을 통해 해당 class를 알지 못하더라도, 해당 class가 구현한 interface를 사용했다면 그 class의 object를 부모 type으로 받아서 사용할 수 있다.
어쨌거나, 어느 DB 커넥션을 사용할지는 UserDao Client의 관심으로 object 사이의 관계를 설정해주어야 한다.
현재 UserDao를 main method 에서 사용하므로, 사용하는 client는 main이라고 할 수 있다.
// UserDao Client가 어느 DB연결을 쓸지 결정하여 UserDao에게 DI해준다. publicclassUserDaoTest{
publicstaticvoidmain(String[] args){
ConnectionMaker connectionMaker = new DconnectionMaker(); // Client가 오브젝트 사이의 의존관계를 설정해준다. UserDao userDao = new UserDao(connectionMaker); } }
이제 관심사를 분리하였으며, 확장도 가능한 구조로 변경을 완료하였다. 만약에 DB연결정보를 변경한다면 다음과 같이 UserDao에 손대지 않고 변경이 가능하다.
1 2 3 4 5 6 7 8 9
publicclassUserDaoTest{
publicstaticvoidmain(String[] args){ //Dconnection말고 NConnection으로 변경해주세요 //Dconnection -> Nconnection ConnectionMaker connectionMaker = new NconnectionMaker(); UserDao userDao = new UserDao(connectionMaker); } }
정리하면 위와 같은 최종구조를 갖는다. 위 구조는 디자인 패턴에서 전략 패턴(startegy pattern)에 속한다. 자세한 전략패턴에 대한 설명은 별도로 정리해두었다. (전략패턴)
위 구조는 객체 지향 설계 원칙 중에 하나인 OCP (SOLID의 O, Open closed Principle) 을 구현했다고 할 수 있는데,
OCP(Open Closed Principle,개방폐쇄원칙)는 모듈이 변경에는 닫혀있고, 확장에는 열려 있어야 한다.
여기서 점진적으로 발전시킨 UserDao class는 변경에는 닫혀있다. “코드에 어떠한 수정없이 DB연결정보를 확장시킬수 있다” 라는 측면에서 OCP를 준수하였다라고 말할 수 있다. 책에서는 OCP를 준수했다는 것을 높은 응집도(coherence)와 낮은 결합도(coupling)를 갖는다고도 표현한다.
간략하게 정리하면 다음과 같다.
- 응집도(Coherence) : 모듈 내부의 요소들이 관련되어 있는 정도, 높은 응집도를 갖는다는 것은 모듈의 내부 요소들이 하나의 책임과 관련이 있고, 변경의 원인이 하나라는 의미. 즉 관심사가 하나다.
- 결합도(Coupling): 모듈간의 서로 상호 의존하는 정도 , 낮은 결합도를 갖는 다는 것은 다른 모듈에게 변화를 요구하지 않고, 변경이 전파되지 않는다는 것을 의미.
위와 같이 Dao를 v1~v5까지 refactorying 하면서 ocp를 준수하도록 설계하였다. 그렇다면 이와 관련되어있는 spring의 IoC개념은 무엇일까? 간단하게 설명하면 program의 제어 흐름이 뒤바뀐 것이다. 자세한 내용은 다음 포스팅에서 살펴보도록 하겠다.