토비의 스프링 책을 읽고 ‘책 읽기 모임’에 참석하고 계신 분들과 Q&A 하고 토론하는 시간을 가졌습니다. 새로운 배움이 있었던 내용들을 정리해 봅니다.
1. 어디서나 끌어올 수 있는 싱글톤 빈의 단점
Question
싱글톤 (디자인)패턴을 구현한 객체는 애플리케이션 전역적인 접근을 허용하는 문제가 있는데 스프링의 싱글톤 빈 역시 getBean()을 사용하기 시작하면 동일한 문제를 안고 있는 것이 아닌가요? 어떤 차이점이 있는지 궁금합니다.
Discussion
- 일반적으로 구성 파일(Configuration)에 의존 관계를 명확하게 하고 사용함으로 위와 같은 문제는 없다고 볼 수 있다. getBean()을 통한 의존관계 검색(Lookup) 방법은 클라이언트 객체가 빈이 될 수 없는 등의 아주 예외적인 경우에만 사용하도록 제한하길 권한다.
- 싱글톤 패턴을 구현한 객체를 한 번 생성하고 읽기만 한다면 문제가 되지 않지만 읽고 쓰기를 여러군데서 하면 문제가 된다. 싱글톤 빈은 상태를 가지지 않도록 구현하기를 권장함을 생각해 보자.
2. 인터페이스 없이 구체 클래스를 직접 DI(Dependancy Injection)하는 것에 대해
Question
변경이 일어날 가능성이 적은 의존 오브젝트는 인터페이스로 분리 없이 클라이언트 코드에 직접 드러내며 DI하는 경우가 있는 것 같습니다. 의존관계 주입의 세 가지 조건에 “클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다” 라는 내용이 있는데 모든 의존 오브젝트를 인터페이스로 분리하여 구현할 필요는 없을 것 같다는 생각도 듭니다. 현업에서 어떻게 사용하시는지 궁금하고, 어떤 의견들을 가지고 계신지 궁금합니다.
Discussion
- 구체 클래스를 인터페이스 없이 직접 주입 받아서 쓰는 코드는 그냥 new 해서 쓰는거랑 다른 점이 있는가? 그런 경우라면 클래스 이름이나 클래스 인터페이스가 바뀌면 클라이언트 코드가 의존해서 다 같이 바뀌지 않는가? 다양성을 활용해 확장해서 사용할 수도 없지 않은가?
- 변경이 일어날지 여부를 떠나서 인터페이스를 항상 만들어 DI 받을 것을 권한다. 우리가 습관적으로 멤버 변수를 private으로 선언하고 멤버 메서드를 통해 접근하도록 한다. 그냥 public 하게 선언해서 코드 어디에서나 편하게 접근하게 할 수 있다. 그러나 그걸 지키지 않으면 나중에 멤버에 접근하는 방법에 변경이 발생한다면 애플리케이션 전반에 변경과 부수효과가 발생함을 기억해야 한다. 인터페이스를 통해 DI 받게 하는 것도 똑같다고 생각한다. 그냥 습관을 가지고 해야한다.
- Controller는 우리가 작성하는 애플리케이션 코드에 의존하는 대상이 없다. 그래서 인터페이스를 만들지 않는다. 그러나 Service는 대게 항상 인터페이스를 정의해서 접근한다.
- 인터페이스로 잘 구분하면 객체의 사용자에 따라 인터페이스를 분리하라는 객체지향의 원칙에도 충실할 수 있다.
- 변경에 얼마나 영향을 받는지 판단하라. 때때로 코드 구현에 의존하는 구체 클래스를 new 해서 명시할지 판단해 보라.
Thinking
- ‘구체 클래스를 인터페이스 없이 DI 받는 코드라도 애플리케이션에서 직접 new 한 객체를 주입 받는 것과는 차이가 있는 것 같다. new를 하기 시작하면 책에서 나오는 Factory 객체 역할의 코드를 애플리케이션에서 직접 작성해 주어야 한다. 인터페이스 없이 구체 클래스를 직접 DI 받는 역할을 DI 컨테이너에게 위임하면 그 역할 자체가 분리되는 장점은 분명히 가져가는 것 같다. 물론 변경에 여전히 취약하고, 확장은 어려운 코드지만, DI 컨테이너라는 제3의 역할을 프레임워크에게 위임한다는 장점은 있는 것 같다.’ 이런 생각을 잠시 했지만 스프링 DI 컨테이너를 제대로 활용하며 오랜 기간 소프트웨어를 유지보수한 경험이 없기에 지금 나의 생각을 오랜 뒤에 다시 리뷰해 보자.
3. 생성자 vs 수정자 주입 방식
Question
생성자 주입 방식에는 다음과 같은 장점이 있는데, 스프링에서 왜 Setter 주입 방식을 사용하는 경우가 있나?
- 생성자만 보면 의존성이 명시적으로 드러난다.
- 의존성이 생성되는 시점 이외에 바뀌는 경우가 거의 없다.
- 수정자 주입 방식을 사용하면 초기 생성 후 한 동안 null 값을 가지는 불안정한 상태가 되지 않는가?
Discussion
- 생성자 주입 방식의 단점도 있다. 같은 타입의 주입 객체가 2개 이상 있다면 순서가 뒤바뀌는 것을 체크하기가 어렵다. 그래서 Setter를 이용한 Builder 패턴 같은 것이 등작하기도 했다.
- 수정자 주입 방식을 사용해서 잠시 null 상태를 가져서 문제가 되는 경우는 다른 체크들을 통해 해결할 수 있고 런타임 시에 오류라고 드러나기 때문에 크게 문제가 된 경우를 못 보았다.
- 간혹 의존 객체가 Optional 한 경우 Setter를 사용하는 케이스가 있다.
4. 캡슐화
Question
Getter, Setter 만들지 않는 편인데…
Discussion
- 직접적으로 멤버에 접근하는 Setter 대신, 외부에 추상화된 기능을 제공하는 것을 캡슐화라고 한다.
- 보통 서비스 오브젝트들은 싱글톤 빈으로 생성하고 상태를 가지지 않게 하는 것이 일반적이다. 대신 상태는 외부 스토리지에 저장하겠죠.
5. XML 구성파일
Question
XML 구성파일의 장단점
Discussion
- 일단 현재의 트렌드는 Java 코드 또는 애노테이션으로 의존관계를 구성하는 것이다. XML은 거의 사용하지 않는다.
- Java 코드로 의존관계를 변경하면 빌드를 다시 해야한다. XML은 코드를 빌드하지 않아도 된다. 처음에 이것이 굉장히 혁신적으로 받아들여 졌다.
- 하나의 XML 파일에 충돌이 생기면 충돌 해결이 어려운 문제가 있었다.
- XML은 런타임 시작 시 오류를 인지하지만, 최근의 IDE의 지원으로 잘못 작성된 XML 설정이 쉽게 인지된다.
6. Annotation vs XML
Question
XML로 의존정보를 한 곳에 모아 두는게 좋지 않나요?
Discussion
- Annotation 을 쓰면 실제 클래스 구현 코드 위에 @Component 만 붙이면 된다.
- XML 에는 클래스 이름을 한 번 더 써야 하는 의외의 중복이 하나 붙는다.
- ‘분리하는게 좋냐? 모아 놓는게 좋냐?’는 끊임 없는 프로그램 세계에서의 주제이다.
- 트랜드는 XML을 안쓰는 겁니다.
7. 싱글톤 레지스트리 성능
Question
- 싱글톤 (디자인)패턴을 직접 구현해서 사용하는 것과 스프링의 싱글톤 레지스트리를 사용하는 것에 성능 차이가 있는가?
Discussion
- 거의 미미해서 성능을 논하지 않아도 될 정도라고 생각한다.
8. 추천하는 책
Question
다섯 손가락 안에 드는 Top5 책에는 어떤 것이 있을까요?
Discussion
- 켄트벡 - 테스트 주도 개발
- 마틴 파울러 - 리팩토링 1판
- 로버트 C 마틴 - 클린 소프트웨어
- 로버트 C 마틴 - UML 실전에서는 이것만 쓴다
9. 기타 학습
- 빈은 상태를 가지지 않도록 구현하고, 서버의 성능관점에서 요청마다 객체를 생성하지 않고 싱글톤 빈을 통해 재사용한다. 서비스 객체는 대부분 이렇게 만든다고 생각하자.
- 요즘은 Dao 대신 Repository 라는 용어를 씁니다.
- CountingConnectionMaker 예제는 데코레이션 패턴의 한 예를 보여주었다. 어떤 빈 객체의 부가기능을 이런 형태로 구현할 수 있다는 걸 보여드리려 했다. 멀티쓰레드 환경에 집중해 보면 확실히 Race Condition발생하는 stateful 한 잘못된 예제가 맞습니다. 다음 시간까지 해결 방법을 찾아서 공유해 주면 좋겠다.
- IoC, DI, AOP 외에도 Spring에는 Reactive라는 새로운 프로그래밍 모델이 등장한 것 같다.
10. 다음 시간에는
- 2장 부터 테스트에 대한 이야기가 나온다. 책을 쓰던 당시 JUnit 같은 도구를 사용해 테스트 코드를 만드는 개념이 잘 전해져 있지 않은 시기였다. 그래서 2장 부터 테스트에 대해 먼저 쓰게 되었다.
- 다음 시간에는 JUnit 5 최신 기술 얘기를 해도 좋을 것 같다.
- 켄트벡의 테스트 주도 개발 책은 꼭 한 번 읽어보아야 한다. xUnit 통째로 만드는 예제가 나온다. 다른 언어로 이것을 한 번 해보면 TDD의 인사이트를 얻을 수 있다.
xUnit 테스팅 프레임워크를 TDD로 만들어보자