콘텐츠로 이동
✍️ 수정가능누구나 고쳐도 됩니다. 고치면 하단 frontmatter의 갱신일·작성자·변경요약을 남겨 주세요.작성 Claude · 2026-06-05 · v12 재편·raw 컴파일·인간화

기술 의사결정

기술 결정을 모아 보는 곳이다. 무엇을 골랐는지보다 왜 골랐는지를 남기는 데 무게를 둔다. 전체 ADR 카탈로그는 팀 의사결정 기록에 있고, 개별 결정의 정본은 각 ADR-NNNN 페이지다.


1. ADR 운영 원칙

ADR(Architecture Decision Record)에는 중요한 기술적·구조적 결정과 그 이유를 적는다.

  • 대상: 아키텍처 구조, 기술 스택(DB·메시징·스토리지), 공통 모듈/플랫폼 설계, 재사용·확장 전략, 명확한 대안이 있던 선택.
  • 미대상: 단순 구현 방법, 코드 리팩토링, 사소한 설정, 버그 수정.
  • 불변 규칙: 삭제 X · 수정 X · 결정이 바뀌면 새 ADR 작성(이전을 supersede).
  • 상태: Proposed → Accepted → (Deprecated / Superseded).

2. 기술계 ADR 인덱스 (한 줄 요약)

ADR 결정 한 줄
ADR-0001 비대칭 monorepo 통합(git subtree) 백+프를 한 레포로 합침 — ADR-0003으로 대체됨(superseded)
ADR-0003 대칭 monorepo 구조 mvp-back/ + mvp-front/ 대칭 트리로 전환(폴더 시각적 정돈)
ADR-0004 Backend FRD 위치 분리 구현 명세(FRD)를 mvp-back/docs/frd/로 이관(wiki/concepts §4에서 분리)
ADR-0005 FRD 폴더 구조 + 8단계 워크플로 + versioning 기능별 폴더·archive, 신규 기능 개발 8단계, 3 시나리오 버저닝 정책
ADR-0009 AI 코칭 v0.2 단계형 힌트 폐기 → 대화형 사고 유도 + 학생 직접 답 선택(AI 답변 구조)
ADR-0010 통합 개발 라이프사이클 확장 8단계 → 12단계 + 게이트 + 버전 통일 정책
ADR-0017 Dev 환경 AWS → 온프레미스 이전 Dev 백엔드를 온프레 Docker로(비용·통제), 리스크·교훈 동반 → infra

위키 운영계 ADR(0002·0011~0015·0018 등)은 본 인덱스 범위 밖 — 팀 의사결정 기록 참조.


3. 빌드 vs 바이(buy) · 도입 기준

새 기술·서비스를 직접 만들지(build) 사올지(buy) 판단할 때:

기준 build 쪽 buy 쪽
핵심 차별화 도메인 핵심 로직(헥사고날·도메인 모델) → 직접 비차별 인프라 → 외부
운영 비용 통제·커스터마이징 필요 관리형으로 운영 부담 절감
규모·팀 여력 소규모라 단순·신속 우선 고급 기능 필요 시

적용 예: - DB: Dev는 비용과 통제를 위해 온프레 Docker MySQL을 직접 운영하고, Prod는 Lightsail MySQL을 쓴다. 관리형 RDS의 비용·세부설정 제약을 저울질한 끝의 결정이다(infra). - 인증: 외부 IdP를 들이지 않고 JWT stateless를 직접 구현했다 — 팀 규모와 개발비용을 고려한 선택(backend-architecture). - 관측성: SaaS APM 대신 Prometheus/Loki/Grafana를 오픈소스로 self-host한다. 이유는 비용(infra). - AI 코치 LLM: 모델 자체는 사오고(OpenAI/Gemini API), 서빙 구조와 프롬프트는 직접 만든다(ADR-0009 연계). - 프론트 호스팅: Vercel을 쓰되 public 미러를 거치는 패턴(infra).


4. 인증·인가 동작 방식

외부 IdP 없이 JWT stateless를 직접 구현하기로 한 결정이 실제로 어떻게 도는지를 정리한다. 상세 코드와 SecurityConfig 권한 매핑의 정본은 backend-architecture에 있고, 여기서는 왜 이런 구조인지만 짚는다.

필터 체인 (Spring Security 표준)DelegatingFilterProxy(서블릿 ↔ 스프링 빈 연결) → FilterChainProxy → 요청별 SecurityFilterChain 선택 → 보안 필터들 순차 실행. 인증 정보는 SecurityContextHolderAuthentication에 보관한다. 인증 자체는 AuthenticationManager(일반 구현체 ProviderManager)가 AuthenticationProvider 리스트에 위임하고, username/password는 DaoAuthenticationProvider(→ UserDetailsService)가 처리한다.

JWT 흐름 (우리 커스텀): 1. 로그인 요청 → UsernamePasswordAuthenticationFilter 확장 필터에서 인증 → 성공 시 JWT 발급(HS256 서명, Authorization: Bearer …, 만료 60분). 2. 클라이언트가 JWT 보관 → 이후 요청마다 인가 필터(OncePerRequestFilter 확장)가 토큰 추출·검증 → SecurityContextHolderAuthentication 세팅. 3. 세션은 STATELESS(SessionCreationPolicy.STATELESS), CSRF 비활성 — JWT 무상태 방식이라 서버 세션을 두지 않음. 4. 권한은 토큰 claim의 역할(role) prefix(ROLE_TEACHER/ROLE_STUDENT/ROLE_PARENT 등)로 메서드·URL 단위 인가. 매 요청 DB 조회로 권한 확인하면 느려질 수 있어 Redis 캐싱이 옵션으로 검토됨.

왜 직접 필터 대신 Spring Security를 썼나 — 처음엔 학습 삼아 직접 AuthFilter로 URL별 인가를 짰지만, 이걸 손으로 유지하면 금세 복잡해지고 부담이 커진다. Spring Security를 쓰면 인증 자동화, CSRF·세션·실패 처리, 역할 기반 권한 통합 관리, PasswordEncoder(Bcrypt) 비밀번호 암호화를 한꺼번에 가져온다. (WebSecurityConfigurerAdapter는 5.7부터 deprecated라 컴포넌트 빈 방식을 택했다.)


5. 백엔드 기술결정 로그 (Notion ADR)

위 인덱스의 위키 ADR과는 별개로, 백엔드팀이 Notion에 따로 운영해 온 백엔드 기술결정 데이터베이스가 있다. 위키 ADR 카탈로그가 전 영역 횡단 결정의 정본이 된 뒤로, 이 Notion DB는 백엔드 내부 기술선택의 1차 기록처로 아카이브처럼 남겨 둔다. 앞으로 횡단 결정은 위키 ADR로, 백엔드 한정 구현 결정은 이 로그나 mvp-back:docs/frd로 나눠 적는다.


6. 코드 컨벤션 (결정으로서의 합의 과정)

실제 컨벤션 규칙(들여쓰기·네이밍·커밋 포맷 등)의 정본은 backend-architecture 계열이다. 여기서는 무엇을 왜 합의했는지만 남긴다.

  • 통일 자체가 목적이었다. "어떤 컨벤션이 더 우월한가"는 대개 끝나지 않는 언쟁이라, 우열을 가리기보다 팀이 하나로 통일하는 편이 낫다고 봤다. 후보는 Google과 Naver Java 컨벤션이었고, 한글 문서·세팅 가이드·4칸 들여쓰기를 이유로 Naver 컨벤션을 택했다. 사람 손에 맡기지 않으려고 Checkstyle(형식뿐 아니라 코드구조·네이밍·메서드 길이·파라미터 수까지 검사) + IntelliJ Save Action 포매터 + CI 검증으로 강제한다.
  • 레거시 컨벤션(대치온 시절)이 지금 규칙의 출발점이다. 4-space, 120자 열 제한, 3항 연산자 금지, 메서드 15라인·indent 2 이하(메서드 적극 분리), 상수는 Enum, <type>(<scope>): <subject> 커밋 포맷, feat/fix/refactor/setting 브랜치 규칙, 정적 팩토리 메서드로 엔티티 생성, 도메인 기반 FE 폴더구조. 일부는 그대로 계승했고 일부는 버렸다.
  • 컨벤션 DB는 빈 껍데기로 남았다. Notion에 컨벤션 항목을 태그(공통·git·폴더·code·기타)로 적재하려던 데이터베이스인데 거의 채워지지 않았다. 실질 컨벤션은 위 통일·레거시 문서와 코드 리포의 Checkstyle 설정으로 수렴했다.

7. 성능·리팩토링 결정

운영하면서 발견한 비효율을 무엇을 왜 바꿨는지 관점으로 정리한다.

  • 렌더링 최적화 (프론트) — 결론부터 말하면, BFF에서 응답을 합치는(aggregation) 방식으로는 별 개선이 없었다. 이미 React Query가 병렬로 호출하고 있었기 때문이다. 그래서 방향을 바꿨다. ① BFF를 순수 passthrough 프록시로 재정의해 ROLE_TEACHER→teacher 같은 도메인 변환 로직과 aggregation 유틸을 걷어냈다(도메인 개념이 프록시로 새는 것을 막기 위해). ② 멤버 정보를 서버에서 한 번만 fetch해 /member/info 클라이언트 중복 호출을 없앴다(SSR initialData + refetchOnMount: false). ③ 목록·상세 페이지를 선택적으로 SSR로 돌렸다(초기 데이터는 서버 fetch, 이후는 CSR). ④ React Query 옵션을 일관화하고(refetchOnMount 혼용 정리, staleTime 활용) Zustand selector 구독·디버그 로그를 정리했다.
  • 조회 로직 리팩토링 (백엔드)getTeacherHomeworkList 같은 조회 메서드를 "teacher 검증 → 특정 studyRoom 스코프" 흐름으로 정돈했다. N+1·배치쿼리 관점은 backend-architecture의 패턴을 따른다.
  • 엔티티·조회 리팩토링 일반 — 엔티티 리팩토링 가이드와 리팩토링 리스트를 묶는 작업이다. 단순 구현 리팩토링은 ADR 대상이 아니라(위 §1의 미대상 기준) 이 로그 계열로 관리한다.

8. 관련

팀 의사결정 기록 · backend-architecture · infra · version-control