{ "title": "`application-core`는 왜 Spring DI만 허용했을까", "document_type": "technical_blog", "sections": [ { "id": "01-problem-scene", "intent": "problem_scene", "title": "코드보다 먼저 드러난 문제", "reader_question": "독자가 공감할 수 있는 구체적인 상황에서 어떤 문제가 드러났는가?", "purpose": "추상적인 글쓰기 계약이 아니라 실제 장면, 증상, 비용으로 시작한다.", "must_include": [ "구체적인 상황", "문제가 만든 비용", "이 글에서 풀 질문", "`application-core`에서 Spring DI는 허용하면서 transaction, web, persistence 의존은 금지한 이유와 트레이드오프를 설명할 수 있다", "framework-free라는 구호보다 의존 목적을 좁히고 자동 검증하는 편이 이 프로젝트의 문제에 맞았다. bean 등록을 위한 Spring DI는 허용하되 transaction, transport, persistence 정책은 application 경계 밖에 남겼다.", "ca-tmpl의 `application-core` 의존성 결정", "Spring DI 허용 이유", "Gradle과 ArchUnit을 통한 경계 검증", "모든 Clean Architecture 프로젝트의 보편 규칙", "SLF4J 사용 이유", "운영 환경 성능 검증" ], "evidence_ids": [ "Lbe6cb7d8e8", "Lf440ea562d", "Ld4394f2f14", "L8db0ff5b86" ], "decision_requirements": [], "transition_to_next": "이 답을 바탕으로 다음 독자 질문으로 자연스럽게 연결한다." }, { "id": "02-constraints", "intent": "constraints", "title": "문제를 어렵게 만든 제약", "reader_question": "단순한 해법을 막은 프로젝트 제약은 무엇이었는가?", "purpose": "현재 구조, 독자에게 필요한 배경, 확인된 사실과 미확인 영역을 분리한다.", "must_include": [ "현재 구조", "제약", "확인된 사실과 사실 경계" ], "evidence_ids": [ "Lbe6cb7d8e8", "Lf440ea562d", "Ld4394f2f14", "L8db0ff5b86" ], "decision_requirements": [], "transition_to_next": "이 답을 바탕으로 다음 독자 질문으로 자연스럽게 연결한다." }, { "id": "03-options", "intent": "options", "title": "검토한 선택지와 막힌 지점", "reader_question": "어떤 대안들을 검토했고 각각 어디에서 비용이 생겼는가?", "purpose": "최소 두 선택지를 같은 기준으로 비교하고, 실패한 시도나 제외 이유를 숨기지 않는다.", "must_include": [ "대안", "비교 기준", "제외 이유 또는 실패한 시도", "수동 bean 등록의 조립 코드 비용", "Spring DI 허용 범위", "`spring-tx`, Spring Web, JPA 금지", "`TransactionPort`", "Gradle dependency matrix", "ArchUnit rule과 정적 분석 한계" ], "evidence_ids": [ "Lbe6cb7d8e8", "Lf440ea562d", "Ld4394f2f14", "L8db0ff5b86" ], "decision_requirements": [ "상황·제약", "선택", "선택 이유", "검토한 대안", "수용한 비용", "보완 가드레일" ], "transition_to_next": "이 답을 바탕으로 다음 독자 질문으로 자연스럽게 연결한다." }, { "id": "04-decision-rationale", "intent": "decision_rationale", "title": "선택의 이유와 지킨 경계", "reader_question": "왜 이 선택을 했으며 무엇을 일부러 포기하거나 금지했는가?", "purpose": "선택을 제약, 이유, 대안, 수용 비용, 보완 가드레일까지 한 묶음으로 설명한다.", "must_include": [ "선택", "왜 선택했는가", "대안", "수용한 비용", "가드레일" ], "evidence_ids": [ "Lbe6cb7d8e8", "Lf440ea562d", "Ld4394f2f14", "L8db0ff5b86" ], "decision_requirements": [ "상황·제약", "선택", "선택 이유", "검토한 대안", "수용한 비용", "보완 가드레일" ], "transition_to_next": "이 답을 바탕으로 다음 독자 질문으로 자연스럽게 연결한다." }, { "id": "05-mechanism", "intent": "mechanism", "title": "선택이 코드와 흐름에 반영되는 방식", "reader_question": "결정이 모듈, 인터페이스, 제어 흐름에 어떻게 반영되는가?", "purpose": "실제 이름과 경계를 사용해 인과 흐름을 설명하고, 하나의 구체적인 예시를 끝까지 따라간다.", "must_include": [ "실제 구성요소", "제어 또는 데이터 흐름", "구체적인 예시", "불변조건", "수동 bean 등록의 조립 코드 비용", "Spring DI 허용 범위", "`spring-tx`, Spring Web, JPA 금지", "`TransactionPort`", "Gradle dependency matrix", "ArchUnit rule과 정적 분석 한계" ], "evidence_ids": [ "Lbe6cb7d8e8", "Lf440ea562d", "Ld4394f2f14", "L8db0ff5b86" ], "decision_requirements": [], "transition_to_next": "이 답을 바탕으로 다음 독자 질문으로 자연스럽게 연결한다." }, { "id": "06-evidence-verification", "intent": "evidence_verification", "title": "결정이 지켜지는지 확인하는 방법", "reader_question": "설명한 경계와 결과가 실제로 유지되는지 어떻게 확인하는가?", "purpose": "테스트, 빌드 규칙, 관측값을 주장과 연결하고 검증 범위를 과장하지 않는다.", "must_include": [ "검증 절차", "성공 기준", "검증하지 못한 범위", "수동 bean 등록의 조립 코드 비용", "Spring DI 허용 범위", "`spring-tx`, Spring Web, JPA 금지", "`TransactionPort`", "Gradle dependency matrix", "ArchUnit rule과 정적 분석 한계" ], "evidence_ids": [ "Lbe6cb7d8e8", "Lf440ea562d", "L8db0ff5b86", "Ld4394f2f14" ], "decision_requirements": [], "transition_to_next": "이 답을 바탕으로 다음 독자 질문으로 자연스럽게 연결한다." }, { "id": "07-tradeoffs", "intent": "tradeoffs", "title": "얻은 것, 잃은 것, 적용하지 않을 때", "reader_question": "이 선택의 비용과 한계는 무엇이며 언제 다른 선택이 나은가?", "purpose": "프로젝트 지역 결정을 보편 법칙처럼 쓰지 않고, 적용 조건과 남은 위험을 제시한다.", "must_include": [ "얻은 것", "잃은 것", "적용 조건", "남은 위험" ], "evidence_ids": [ "Lbe6cb7d8e8", "Lf440ea562d", "Ld4394f2f14", "L54271e62b5" ], "decision_requirements": [ "상황·제약", "선택", "선택 이유", "검토한 대안", "수용한 비용", "보완 가드레일" ], "transition_to_next": "이 답을 바탕으로 다음 독자 질문으로 자연스럽게 연결한다." }, { "id": "08-conclusion", "intent": "conclusion", "title": "결국 지키려던 것은 무엇이었나", "reader_question": "세부 기술을 걷어냈을 때 남는 판단은 무엇인가?", "purpose": "앞 내용을 반복하지 않고, 문제와 선택을 연결하는 한 문장 판단으로 닫는다.", "must_include": [ "압축된 판단", "독자가 자신의 환경에서 확인할 질문" ], "evidence_ids": [], "decision_requirements": [], "transition_to_next": "이 답을 바탕으로 다음 독자 질문으로 자연스럽게 연결한다." } ], "planning_notes": [ "Each section answers one reader question.", "The order moves from reader goal to context, model, mechanism, evidence, limits, and action as applicable.", "Required section intents are a contract; a model may refine wording but must not remove or reorder them." ] }