본문 바로가기

스프링

[spring] xml_인터페이스 상속을 통한 기능확장_4

* 원칙적으로 권장되지는 않지만 서버가 운영 중인 상태에서 서버를 재시작하지 않고 긴급하게 애플리케이션이 사용 중인 SQL을 변경해야하는 경우가 있을 수 있다. 이 부분을 추가한다 생각하고 고민해보자.

> SQL XML 파일을 수정한다고 해서 메모리상의 SQL 정보가 갱신되지 않는다.

 

* DI

> 커다란 오브젝트 하나만 존재해서는 안 된다. 최소한 두 개 이상의, 의존관계를 가지고 서로 협력해서 일하는 오브젝트가 필요하다.

> 의존 오브젝트는 자유롭게 확장될 수 있어야 한다.

> DI는 런타임시 다이내믹하게 연결해줘서 유연한 확장이 목적이다.

> 두 개의 오브젝트가 인터페이스를 통해 느슨하게 연결되어야 한다.

 

* 인터페이스를 사용하는 이유

> 다형성을 얻어 여러 개의 구현을 바꿔가면서 사용할 수 있게하는 것

> 프록시, 데코레이터, 어댑터, 테스트 대역 등의 다양한 목적을 위해 다형성이 활용됨.

> 사용하지 않는다면 이후의 개발, 디버깅, 테스트, 기능의 추가, 변화 등에서 적지 않은 부담을 안게 될 것이다.

 

* 인터페이스 분리 원칙(interface Segregation Principle)

> A라는 오브젝트가 B라는 인터페이스를 통해 DI를 받는다면 B 인터페이스를 구현한 B1, B2 오브젝트는 A에게 DI가 가능해진다. 그런데 B1 오브젝트가 B 인터페이스와 C라는 인터페이스 2개 이상을 구현할 수 있다. 왜이럴까???

- C라는 인터페이스가 그려주는 창으로 B1을 바라보는 다른 종류의 클라이언트가 존재하기 때문이다. 이 때 A 오브젝트가 C라는 인터페이스에 정의된 내용에는 아무런 관심이 없으므로 직접 의존할 이유가 없다. 게다가 C 인터페이스의 메소드에 변화가 발생하면 그에 관심 없는 A오브젝트의 코드에 영향을 줄 수도 있다. 인터페이스가 아닌 클래스를 직접 참조하는 방식으로 DI했다면, 인터페이스 분리 원칙과 같은 클라이언트에 특화된 의존관계를 만들어낼 방법 자체가 없는 것이다.

 

* 인터페이스 상속

> 하나의 오브젝트가 구현하는 인터페이스를 여러 개 만들어서 구분하는 이유 중의 하나는 오브젝트의 기능이 발전하는 과정에서 다른 종류의 클라이언트가 등장하기 때문이다. 때로는 인터페이스를 여러 개 만드는 대신 기존 인터페이스를 상속을 통해 확장하는 방법도 사용된다.

> 인터페이스 분리 원칙이 주는 장점은 모든 클라이언트가 자신의 관심에 따른 접근 방식을 불필요한 간섭 없이 유지할 수 있다는 점이다. 그래서 기존 클라이언트에 영향을 주지 않은 채로 오브젝트의 기능을 확장하거나 수정할 수 있다.

 

> 여태 코딩해온 SqlService의 기본 구현인 BaseSqlService 클래스의 설계구조를 살펴보자.

BaseSqlService와 그 서브 클래스는 SqlReaderSqlRegistry라는 두 개의 인터페이스를 통해 의존 오브젝트들을 DI하도록 되어 있다. BaseSqlServiceSqlRegistry라는 인터페이스를 통해 MySqlRegistry 클래스의 오브젝트에 접근하기 때문에 MySqlRegistry의 구현 내용이 변경을 통해 확장될지라도 BaseSqlService 클래스는 변경없이 유지될 수 있다.

 

> MySqlRegistry는 또 다른 제 3의 클라이언트를 위한 인터페이스를 가질 수 있다. 물론 이를 위해 새로운 인터페이스를 생성할 수 도 있지만, 기존 SqlRegistry를 확장한 인터페이스를 이용할 수도 있다

 

BaseSqlService는 이 SqlRegistry 인터페이스를 구현하는 오브젝트에 의존하고 있다. 이 때, SQL변경할 수 있는 기능을 넣어서 확장하고 싶다고 생각해보자.

 이때는 이미 SqlRegistry 인터페이스를 이용해 접근하는 클라이언트인 BaseSqlService 클래스와 그 서브클래스(OxmSqlService)가 존재하기 때문에 SqlRegistry 인터페이스 자체를 수정하는 건 바람직한 방법이 아니다.

 

BaseSqlService 오브젝트는 SqlRegistry 인터페이스가 제공하는 기능이면 충분하기 때문에 DAO를 위한 SQL 조회 서비스인 BaseSqlService 입장에서 SQL을 업데이트하는 기능을 이용하는 클라이언트가 될 이유가 없다. 따라서 클라이언트의 목적과 용도에 적합한 인터페이스만을 제공한다는 인터페이스 분리 원칙을 지키기 위해서라도 이미 적용한 SqlRegistry는 건드리면 안된다.

 대신 새롭게 추가할 기능을 사용하는 클라이언트를 위해 새로운 인터페이스를 정의하거나 기존 인터페이스를 확장하는게 바람직하다. SQL 저장소에 담긴 SQL 정보를 변경하는 기능을 추가하기로 했다는 건, 그런 기능을 사용할 클라이언트가 존재해야 한다는 의미이기도 하다. 아마도 관리자가 사용할 SQL 관리 기능을 맡은 오브젝트가 새로운 클라이언트가 될 것이다.

 

 

새로운 클라이언트가 필요로 하는 인터페이스는 SQL에 대한 수정을 요청할 수 있는 메소드를 갖고 있어야 하며, 등록이나 검색 같은 기본적인 기능도 필요할 테니 기존 SqlRegistry 인터페이스에 정의된 메소드도 사용할 필요가 있다. 그렇다면 새로운 클라이언트를 위한 인터페이스에 정의된 메소드도 사용할 필요가 있다. 그렇다면 새로운 클라이언트를 위한 인터페이스는 SqlRegistry 인터페이스의 기능과 함께 새로운 SQL 수정 기능도 갖고 있어야 한다.

 그런데 이렇게 SQL 업데이트 기능을 가진 새로운 인터페이스를 만들었으니 BaseSqlService도 새로 만든 UpdateableSqlRegistry 인터페이스를 이용하게 해야할까? 그렇지 않다. 기존의 SqlRegistry 인터페이스를 통한 접근이면 충분하기 때문이다.

 

만약 SQL 수정기능만을 처리하는 클라이언트가 필요했다면 상속하지 않고 새로운 인터페이스를 추가해도 된다.

 

반면에 SQL 업데이트 작업이 필요한 새로운 클라이언트 오브젝트는 UpdatableSqlRegistry 인터페이스를 통해 SQL 레지스트리 오브젝트에 접근하도록 만들어야 한다.

 BaseSqlServiceSqlAdminService 는 동일한 오브젝트(MyupdatableSqlRegitry)DI 받아서 사용한다.

 

동일한 오브젝트에 의존하고 있지만 각각 SqlRegistryUpdatableSqlRegistry 라는 각자의 관심과 필요에 따라서 다른 인터페이스를 통해 접근한다. 인터페이스를 사용하는 DI이기 때문에 가능한 일이다.

 중요한 것은 클라이언트가 정말 필요한 기능을 가진 인터페이스를 통해 오브젝트에 접근하도록 만들었는가 이다. 이처럼 인터페이스를 적절하게 분리하고 확장하는 방법을 통해 오브젝트 사이의 의존관계를 명확하게 해주고, 기존 의존관계에 영향을 주지 않으면서 유연한 확장성을 얻는 방법이 무엇인지 항상 고민해야 한다.