Clean code TIL 11장. 시스템
* 11장부터는 노마드 코더 개발자 북클럽 챌린지가 아닌
개인적으로 읽고 작성한 후기입니다.
Java로 쓰여져 읽기 버거웠던 파트는
이 '시스템' 파트에 비하면 아무것도 아니었는데,
이 파트에서 예시로 든 Bank EJB용 EJB2 아키텍쳐를 이해하기에는
배경 지식이 턱없이 부족했기 때문이었다.
우선 내가 이해한 것부터 정리해보자.
내가 이해한 것
시스템 제작과 시스템 사용을 분리하라
소프트웨어 시스템은
준비 과정과 준비 이후의 런타임 로직을 분리해야한다.
준비 과정은 애플리케이션 객체를 제작하고 의존성을 서로 '연결'하는 과정이 될 수 있다.
준비 이후는 런타임 로직이다.
실제로 객체가 필요할 때까지 객체를 생성하지 않는 것을
초기화 지연(Lazy Initialization), 계산 지연(Lazy Evaluation)이라고 한다.
위 기법의 장점은 필요할 때까지 객체를 생성하지 않으므로
불필요한 부하가 걸리지 않고 그만큼 애플리케이션 시작 시간이 빨라진다.
단점은 테스트시 객체를 미리 생성하지 않기 때문에
적절한 테스트 전용 객체를 할당해야 한다.
설정 논리는 일반 실행 논리와 분리해야 모듈성이 높아진다.
주요 의존성을 해소하기 위한 방식, 즉, 전반적이며 일관적인 방식도 필요하다.
1. 생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮긴다.
그리고 나머지 시스템은 모든 객체가 생성되었고
모든 의존성이 연결되었다고 가정한다.
main 함수에서 시스템에 필요한 객체를 생성하고
애플리케이션에 생성된 객체들을 넘긴다.
애플리케이션은 객체를 사용한다.
*
내가 사용할 수 있는 대부분의 웹 애플리케이션 프레임워크
Django, Flask, FastAPI에서도 main함수를 위와 같이 사용한다.
2. 팩토리
main 함수가 아니라 애플리케이션에서 객체가 생성될 시점이 있다.
예제에서는 Abstract Factory 패턴을 사용했다.
main에서 LineItemFactory를 생성하는데,
LineItemFactory Interfcae를 따른다.
LineItem 팩토리(Abstract Factory)에서 LineItem 인스턴스를 생성하고
이 LineItem은 OrderProcessing에 추가된다.
여기까지 보면, OrderProcessing은 LineItem 객체가 어떻게 생성되었는지 알 수 없다.
그저 LineItemFactory에서 어떠한 attribute들이 부여되고
처리를 거쳐 생성된 LineItem객체가
OrderProcessing에 추가되었을 뿐이다.
3. 의존성 주입, DI
*FastAPI에 DI가 Dependency라는 이름으로 구현되어있다.
해당 프레임워크에서 DI를 사용해 본 개발자라면
책에서 설명하는 것과 동일한 특징을 보며 고개를 끄덕일 것이다.
<제어 역전 기법>
- 한 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 떠넘긴다.
- DI 컨테이너는 요청이 들어올 때마다
필요한 객체의 인스턴스를 만든 후
생성자 인수나 설정자 메서드를 사용해 의존성을 설정한다.
https://fastapi.tiangolo.com/tutorial/dependencies/#what-is-dependency-injection
FastAPI 공식 문서에서 설명하기를,
FastAPI에게 DI를 핸들링하게 함으로서 반복되는 코드를 피한다.
또한, 이 Dependency 사용법은
함수를 넘기긴 하지만 직접 call하는 형태로 넘기지 않는다.
foo() 이런식으로 넘기지 않는다.
FastAPI가 요청이 들어올 때마다(여기도 나왔죠)
Dependency Injected 로직을 핸들링한다.
JAVA 스프링 프레임워크에서는 객체 사이 의존성을 XML파일에 정의한다고 하는데
이 점이 FastAPI와 달라서 신기했다.
코딩보다 설계를 먼저 한다는 느낌이 강하게 들었다.
처음부터 올바르게 시스템을 만들 수 있다는 믿음은 미신이다.
그러나 매일 새로운 스토리에 맞춰 시스템을 조정하고 확장하면 된다.
소프트웨어 시스템은 수명이 짧고, 이로 인해 아키텍쳐의 점진적인 발전이 가능하다.
TDD와 리팩터링이 이 과정을 돕는다.
의사 결정을 최적화 하라
명백한 가치가 있을 때 표준을 현명하게 사용하라
시스템은 도메인 특화 언어(DSL)가 필요하다
필수적인 정보를 명료하고 정확하게 전달하는 어휘, 관용구, 패턴이 풍부해야한다.
DSL = 간단한 스크립트/표준 언어로 구현한 API.
좋은 DSL은 도메인 개념 - 개념을 구현한 코드 사이의 간극을 줄여준다.
시스템을 설계하든, 개별 모듈을 설계하든
모든 추상화 단계에서 의도를 명확히 표현해서
실제로 돌아가는 가장 단순한 수단을 사용하라.
내가 해보지 않았기에 이해하기 버거웠던 것
EJB1, EJB2 아키텍쳐.
XML 배포 기술자
- 영구 저장소(db와 같은)에서 객체-관계형 자료가 매핑되는 방식
- 트랜잭션 동작 방식
- 보안 제약 조건
등을 XML 배포 기술자에 작성한다.
비즈니스 논리
비즈니스 논리가 EJB2 애플리케이션 '컨테이너'에 강하게 결합된다.
클래스를 생성할 때는
반드시 컨테이너에서 파생해야하며
그 컨테이너가 요구하는 다양한 생명주기 메서드를 제공해야한다.
EJB2의 '컨테이너'가 무엇을 말하는 지 잘 모르겠지만
EJB2의 생태계에서 개발을 하려면
커스텀 클래스를 만들 때에도 반드시 이 '컨테이너'를 상속받고
이 '컨테이너'의 생명주기와 메서드를 따라야한다고 이해했다.
글쓴이는 이 강한 결합도 탓에
독자적인 단위 테스트가 어렵다고 말하고 있다.
관심사 분리. 그러나 현실과의 괴리.
그래서 AOP(관점 지향 프로그래밍)
원론적으로는 모듈화되고 캡슐화된 방식으로 영속성 방식을 구상할 수 있으나
현실적으로는 영속성 방식을 구현한 코드가 온갖 객체로 흩어진다고한다.
그리고 영속성 프레임워크를 모듈화하고
도메인 논리를 모듈화해도
완전히 분리되는 것이 아니라
이 두 영역이 세밀한 단위로 겹친다.
EJB 아키텍쳐가 영속성, 보안, 트랜잭션을 처리하는 방식과
AOP의 (관점, Aspect)이라는 모듈 구성 개념은
횡단 관심사에 대처해
모듈성을 확보한다는 면에서 닮았다.
프록시 ? ?
프록시 JAVA 예제에서
'프록시되었다'과 같은 용어를 이해하지 못했다.
프록시는 본래 '대신해서 어떤 동작을 해주는 것' 이라는 뜻인데
내가 알고있던 네트워크의 프록시-리버스 프록시 서버 이외에
여기서 설명하는 "메서드 호출을 감싸는 프록시"는 어떤 역할을 할까?
예제의 코드를 보면
InvocationHandler 인터페이스의 구현, BankProxyHandler 내부에서
InvocationHandler에 정의된 메서드를 구현하여
method의 종류에 따라 DB와 다른 통신을 수행한다.
어떤 동작을 하는 지 이해는 됐는데
이를 통해 디자인적으로 달성하고자 하는 것은 아직 이해를 못했다.
이해가 될 듯 말 듯 하여 Spring Framework의 프록시를 찾아보니까
Spring framework에서 프록시란
서버와 클라이언트 사이에서 통신을 대신해주는 역할을 하는 객체를 말한다.
그렇다.
얘도 어쨋든 통신을 "대신" 해주는 객체인 것이고
그렇기 때문에 예제에서 API서버와 DB서버 사이에서
통신을 대신 해주는 역할을 수행했다.
(DB서버 인스턴스 입장에서는 API 서버 인스턴스가 클라이언트다.)
Spring framework의 프록시를 제대로 파려면
여기엔 다 담을 수 없기에 지금은 넘어가자!
Spring framework에서의 XML
XML의 가독성 문제는 제쳐두고,
설정 파일에 "명시된 정책"이
"자동으로 생성되는 프록시" OR "관점 논리" 보다 단순하다.
이런 이유로 EJB3는 스프링 모델을 따른다.
후기
GoF(Gang of Four) 의
Head First Designpattern을 곁들여 읽으면
더 이해가 잘 될 것 같다.
Proxy pattern !
댓글
댓글 쓰기