이 장에서는 어려운 용어들과 번역체로 인해 알아듣기 힘든 내용이 많았다. 따라서 이 글에서는 상당한 의역을 통해 책의 내용을 이해하기 쉬운 말로 바꾸어 보았다.
시스템은 적절한 추상화와 모듈화를 통해 각 구성요소가 효율적으로 관리되도록 해야한다.
시스템 제작과 사용을 분리하라.
- 준비과정(시작단계, 객체를 제작하고 의존성을 '연결'하는 과정) 과 런타임 로직을 분리해야 한다.
- 관심사(concern) : 컴퓨터 프로그램의 코드에 영향을 미치는 특정한 정보 집합이다.(위키백과)
- 관심사 분리 : 그냥 관련있는 것 끼리 잘 뭉치라는 뜻. 반대로 관련 없는 것들은 분리.
- 잘못된 예 : 초기화 지연 기법
public Service getService()
{
if(service = null)
service = new MyServiceImpl(...);
return service;
}
- 문제점1: getService가 MyServerImpl 생성자 인수에 명시적으로 의존한다.
- 문제점2: service가 null인 경우와 아닌 경우 모두 테스트해야함. 즉 책임이 둘이다. 이는 단일 책임 원칙을 위배함.
- 문제점3: MyServerImple 객체가 모든 맥락에 알맞은지 객체인지 알 수 없음.
좀스러운 기법을 함부로 남용하면 시스템의 모듈성을 깨뜨린다. 객체를 생성하거나 의존성을 연결할 때도 마찬가지. 설정 논리와 일반 실행 논리와 분리해야 모듈성이 높아진다.
Main분리
생성과 관련된 코드는 모두 main이나 main이 호출하는 모듈로 옮긴다.
나머지 시스템(애플리케이션)은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정한다.
애플리케이션은 main이나 객체가 생성되는 과정을 전혀 모른다.
즉 먼저 main쪽에서 모든 객체 생성과 의존성 연결 작업을 완료해 두고, 애플리케이션은 이를 그저 사용할 뿐이라는 뜻.
팩토리
그러나 위 Main분리 절에서의 상황과 달리, 애플리케이션에서 객체 생성의 타이밍을 결정해야할 경우도 있다.
이 때는 추상 팩토리 패턴을 사용한다. 애플리케이션은 구체적인 방법은 모른 채 Factory 인터페이스를 통해 객체를 생성할 수 있게 된다.
의존성 주입
이 책에서는 어려운 단어가 많아 오히려 이해하기 힘든데, 의존성 주입(Dependency Injection)이란 소프트웨어 개발 패턴으로, 객체가 자신이 필요로 하는 의존 객체를 자신이 직접 생성하지 않고, 외부에서 제공해주는 것을 말한다.
https://tecoble.techcourse.co.kr/post/2021-04-27-dependency-injection/ 에 잘 설명되어 있으니 참조.
또한 이 기법을 사용하더라도 초기화 지연을 사용할 수 있다.
확장
처음부터 올바르게 시스템을 만들 수는 없다. 주어진 상황에 맞춰 시스템을 구현하고 점차 확장해 나가야한다. 테스트 주도 개발, 리팩토링, 깨끗한 코드는 코드 수준에서 이를 쉽게 만들어준다.
하지만 시스템 수준에서는 어떨까? 시스템 수준에서는 관심사를 잘 분리해야 점진적으로 발전이 가능하다.
책에서는 관심사를 잘 분리하지 못한 예로 EJB1과 EJB2를 들고있다. EJB2 애플리케이션의 컨테이너와 강하게 결합된 비즈니스 논리 떄문에 독자적인 단위 테스트도 어렵고 프레임워크 밖에서 재사용하기도 사실상 불가능하다.
횡단(cross-cutting) 관심사
이 절에서도 온갖 모르는 용어(영속성, 도메인 논리, AOP 등)가 많다. 일단 어려운 용어에 대해 간단히 설명하고 넘어가겠다.
- 영속성이란 프로그램이 종료된 뒤에도 남아있는 것을 말한다. 예를 들면 메모장에 저장된 텍스트 로그 등이 있겠다.
- 영속성 방식이란 데이터를 저장하고 관리하는 방식을 말한다.
- 도메인 논리는 비지니스 논리, 즉 프로그램의 기능적 로직과 대략 비슷한 의미다.
- AOP는 관점 지향 프로그래밍으로, 프로그램의 공통 기능(횡단 관심사)을 분리해, 프로그램의 각 부분에서 재사용 가능하도록 하는 프로그래밍 패러다임이다.
- 횡단 관심사란 간단히 말하면 프로그램 전반에 걸쳐 사용되는 관심사(기능)이다. 예를 들면 로깅, 권한 확인 등의 기능이 있다. 이 책에서는 영속성 기능을 주로 말하고 있다.
EJB 아키텍처는 일부 영역(영속적인 동작 등)에서는 거의 완벽하게 관심사를 분리한다.
영속성과 같은 관심사는 모든 객체가 전반적으로 동일한 방식으로 사용하도록 만들어야한다. 즉, 원칙적으로 이런 영속성 기능들도 모듈화되고 캡슐화된 방식으로 만들어야한다. 그러나 영속성 방식을 구현한 코드들이 프로그램 전체에 걸쳐 사용되고, 도메인 논리와 세밀하게 겹쳐있다 보니 이것이 힘들 수 있다.
EJB에서 횡단 관심사를 처리하는 방식은 AOP를 예견했다고 보인다.
AOP는 이런 식이다. 영속성을 예로 들면, 영속적으로 저장할 객체를 선언한 후 영속성 책임을 영속성 프레임워크에 맡긴다. 그러면 비지니스 로직 사이에 영속성 저장 코드를 추가하거나 변경할 필요 없이 AOP 프레임워크가 동작 방식을 변경한다. 이후 절에서는 자바에서 AOP를 구현하는 몇 가지 매커니즘(기법)을 알아본다. 다만 이 부분들은 내가 JAVA에 별 관심이 없기 때문에 대강 설명하고 넘어간다.
자바 프록시
자바 프록시는 단순한 상황에 적합하다. 메서드 호출을 감싸는 방식이다. 이 책에서 소개한 예제와는 조금 다르지만 비슷한 예제를 다음 링크의 글에서 잘 설명해준다. https://3months.tistory.com/74
Aspect Oriented Programming(관점지향프로그래밍) 소개
AOP란 무엇일까 저는 스프링을 공부하다 AOP를 처음 알게되었습니다.AOP를 접하고 AOP가 도대체 뭔지, 어떻게 구현하는건지 궁금해서 몇 일 공부해봤습니다.이 포스팅이 AOP가 무엇인지 궁금하신분
3months.tistory.com
핵심만 설명하자면 invoke(C++의 함수 포인터 같은)를 활용한 Proxy 객체와 invokationHandler 인터페이스를 사용해 AOP를 구현하는 것이 자바 프록시입니다. invokationHandler를 상속받은 클래스에서 invoke 함수를 구현하고, newProxyInstance 함수를 호출할 때 3번째 인자로 invokationHandler 객체를 넘겨주면 newProxyInstance 함수에 1,2번째 인자로 넘겨준 클래스의 메서드들이 호출될 때 자동으로 invoke가 호출되는 구조이다. 이를 통해서 횡단 관심사들을 한 데 묶어 관리할 수 있게 됩니다.
그러나 프록시는 코드의 양이 많아지고 시스템 단위로 실행지점을 명시하는 메커니즘도 제공하지 않는다.
순수 자바 AOP 프레임워크
스프링 AOP, JBoss AOP 등은 내부적으로 위에 설명한 프록시를 사용하는 AOP 프레임워크들이다.
프로그래머는 설정파일 혹은 API를 사용해 프레임워크 사용에 필요한 기반 구조를 구현한다.
예를 들어 스프링에서는 XML 설정파일을 작성한다. 이를 통해 마치 러시아인형(마트료시카) 같은 구조를 만든다. 이러한 구조에 따라 클라이언트가 Bank 객체의 getAccounts() 함수를 호출한다고 생각하지만 Decorator 객체의 최외곽과 통신한다. EJB3는 XML 설정 파일을 사용한다.
AspectJ 관점
관심사를 관점으로 분리하는 가장 강력한 도구는 AspectJ 언어 이다. AspectJ는 언어 차원에서 AOP구현을 지원한다. 단점은 새로운 언어 문법과 사용법을 익혀야한다는 점.
테스트 주도 시스템 아키텍쳐 구축
코드 수준에서 아키텍처 관심사를 분리할 수 있다면, 진정한 테스트 주도 아키텍처 구축이 가능해진다. 점진적으로 아키텍처를 복잡하게 키워갈 수 도 있다.
의사 결정을 최적화하라
모듈을 나누고 관심사를 분리하면 지엽적인 관리, 결정이 가능해진다. 가능한 마지막 순간까지 결정을 미루어 최대한 정보를 모아 최선의 결정을 내릴 수 있다. 결정의 복잡성도 줄어든다.
명백한 가치가 있을 때 표준을 현명하게 사용하라
표준이라는 이유만으로 사용하지 마라. 그 표준이 너무 방대하면 시간이 너무 오래걸린다. 작은 프로젝트는 가볍고 간단한 설계만으로 충분하다.
시스템은 도메인 특화 언어가 필요하다.
대충 DSL(Domain Specific Language)가 좋다는 뜻.
결론
시스템은 깨끗해야한다.
모든 추상화 단계에서 의도가 명확히 표현돼야 한다.
시스템이든 모듈이든 실제로 돌아가는 가장 단순한 수단을 사용하자.