패키지 구조 표준 원칙 2는 모든 패키지가 common을 봐도 되는 이유에 조건을 하나 걸었다.
common이 프레임워크나 특정 계층에 의존하지 않는 한,
모든 패키지가 common을 봐도 의존 방향은 깨지지 않는다.
그런데 같은 글 원칙 5는 common/error/ApiExceptionHandler와 common/security/*를 common에 둔다. 조건과 배치가 서로 맞는지를 컴파일러에게 물었다.
주장 — common 구성은 Spring Web·Security 없이는 컴파일되지 않는다
표준이 common에 두기로 한 구성은 Spring Web과 Spring Security 없이는 컴파일되지 않는다
— 즉 common이 프레임워크에 의존한다.
근거 종류는 1(기술 사실)이다. 의존은 의견이 아니라 클래스패스에서 빼면 드러나는 것이다.
반증 조건 — 두 jar를 빼고도 전부 컴파일되면 반증이다
- refuted — (나) 없을 때에도 다섯 파일이 전부 컴파일된다
- supported — (나)에서
ApiExceptionHandler나ApiAuthenticationEntryPoint가 컴파일 에러를 낸다 - inconclusive — (가) 있을 때 컴파일되지 않는다(클래스패스를 잘못 짠 것이다)
설계 — 같은 소스를 클래스패스 두 벌로 각각 컴파일한다
표준 원칙 5의 common 구성을 최소로 재현했다. HttpApiResponse, ErrorCode, ErrorCodeException, ApiExceptionHandler(@RestControllerAdvice), ApiAuthenticationEntryPoint(implements AuthenticationEntryPoint) 다섯 파일이다.
빌드 도구의 의존성 해석이 끼어들지 않게 컴파일은 javac를 직접 불렀다. Gradle은 jar를 받아 두 디렉터리로 나누는 데만 썼다.
(가) 있을 때 jar 39개
(나) 없을 때 jar 37개 — spring-web-6.2.19.jar, spring-security-web-6.5.11.jar 만 뺐다
전이 의존은 그대로 두고 정확히 두 jar만 뺐다. 이렇게 해야 「프레임워크에 의존한다」가 아니라 「이 둘에 의존한다」까지 좁혀 말할 수 있다.
결과 — 두 jar를 빼자 두 파일이 죽었다
PROBE_ENV withjars=39 withoutjars=37 removed=spring-security-web-6.5.11.jar,spring-web-6.2.19.jar
PROBE case=with exit=0 errors=0 files=5 failed=-
PROBE case=without exit=1 errors=10 files=5 failed=ApiAuthenticationEntryPoint.java,ApiExceptionHandler.java
DIAG without | ApiExceptionHandler.java:4: error: package org.springframework.http does not exist
DIAG without | ApiExceptionHandler.java:6: error: package org.springframework.web.bind.annotation does not exist
DIAG without | ApiAuthenticationEntryPoint.java:7: error: package org.springframework.security.web does not exist
판정은 supported다. 대조군(가)이 에러 0으로 전부 컴파일됐으므로 클래스패스를 잘못 짠 경우도 아니다. 나머지 세 파일(HttpApiResponse·ErrorCode·ErrorCodeException)은 두 경우 모두 컴파일된다.
예상과 달랐던 점 — 갈리는 선이 패키지가 아니라 파일 단위로 났다
common이 통째로 프레임워크에 묶인 것은 아니었다. 다섯 중 셋은 순수하고 둘만 묶여 있다. ErrorCode가 HTTP 상태를 HttpStatus가 아니라 int로 담기로 한 결정이 여기서 실제로 값을 했다 — 그 결정이 없었다면 셋도 같이 죽었을 것이다.
그래서 원칙 2의 조건문은 지금 이렇게 읽힌다.
| 원칙 2가 건 조건 | 실측 |
|---|---|
common이 프레임워크에 의존하지 않는 한 |
common/error/ApiExceptionHandler와 common/security/*가 의존한다 |
그러면 모든 패키지가 common을 봐도 방향이 안 깨진다 |
전제가 성립하지 않으므로 이 결론은 이 글로 뒷받침되지 않는다 |
domain이 실제로 Spring Web 타입에 닿는지는 이 실험이 재지 않았다. 잰 것은 「common에 둔 파일이 그 jar 없이는 컴파일되지 않는다」까지다.
이 결과가 표준에 남기는 것 — 모순 이슈의 사실 절반이 확정됐다
이 어긋남은 이미 kind: 모순 이슈로 열려 있었다. 그때는 글의 두 문장을 대조한 것이었고, 이제 컴파일러가 같은 답을 냈다. 남은 것은 판단이다 — common을 쪼갤지, 원칙 2의 조건문을 고칠지, 아니면 「common은 프레임워크에 의존해도 된다」로 정할지.
판정이 supported라 표준 글에는 > 실측(supported): 줄로 단다. 규칙을 옹호하는 줄이 아니라, 규칙끼리 부딪히는 자리를 드러내는 줄이다.
정리
spring-web과spring-security-web을 빼자ApiExceptionHandler와ApiAuthenticationEntryPoint가 에러 10개로 죽었다HttpApiResponse·ErrorCode·ErrorCodeException은 두 경우 모두 컴파일된다.ErrorCode가 상태를int로 담기로 한 결정 덕이다- 원칙 2가 건 조건(
common은 프레임워크에 의존하지 않는다)은 원칙 5의 배치에서 성립하지 않는다 - 재지 않은 것 —
domain이 그 타입에 실제로 닿는지, 쪼갰을 때의 비용
자신만의 철학을 만들어가는 중입니다.
댓글남기기