복잡성을 없애는 은탄환은 없다: 그래서 설계에는 ‘이유’가 필요하다.
복잡성을 없애는 은탄환은 없다: 그래서 설계에는 ‘이유’가 필요하다
오늘 주니어 개발자 분에게 들려드린 이야기를 정리해 보고자 합니다.
“복잡성을 해결할 은탄환 같은 건 없다.”
기술 스택을 바꾸거나, 새로운 아키텍처를 도입하거나, 특정 패턴을 적용한다고 해서 복잡성이 갑자기 사라지지는 않습니다.
대신 우리가 할 수 있는 건 한 가지입니다.
시간이 지날수록 늘어나는 복잡성을 ‘관리 가능한 형태’로 유지하도록 설계하는 것.
그리고 그 설계가 팀 안에서 오래 유지되려면, 코드 자체만큼 중요한 게 있어요.
**“왜 이렇게 설계했는지 설명할 수 있어야 한다”**는 점입니다.
깊은 관계의 도메인에서 자주 나오는 선택: “메모가 상위 관계를 전부 가진다”
오늘 주니어 개발자분과 이야기했던 사례는, 파트너 메모가 ‘예약’에 붙는 구조였습니다.
그런데 예약이 하나로 고정된 게 아니라, 비즈니스 흐름상 메모가 아래 중 어느 예약이든 연결될 수 있었어요.
- 카드 예약
- 무통장 예약
- 오프라인(자체) 예약
이런 상황에서는 구현하다가 이런 생각이 자연스럽게 나오곤 합니다.
“메모가 예약에 붙는 거라면, 메모가 연결 키를 다 가지고 있으면 조회가 편하지 않을까?”
즉, 메모가 “예약 연결”을 하나로 추상화하기보다, 예약 유형별로 연결 필드를 따로 두는 방식을 택하는 거죠.
이 선택은 분명 실용적인 편의가 있습니다. 다만 동시에 부작용도 같이 따라옵니다.
그래서 코드 리뷰 요청을 한다면, 단순히 “편해서요”가 아니라
**‘왜 이 방식이 지금 도메인에서 합리적인가’**를 설명할 수 있어야 합니다.
설계의 이유는 “장점”이 아니라 “트레이드오프”로 말해야 한다
여기서 중요한 포인트는 이거예요.
- “조회가 쉬워요”는 이유가 될 수 있습니다.
- 하지만 그 말만으로는 충분하지 않습니다.
설계는 언제나 선택이고, 선택에는 반드시 비용이 따르니까요.
리뷰에서 설득력 있는 설명은 보통 아래처럼 “얻는 것”과 “감수하는 것”을 같이 놓습니다.
✅ 내가 얻는 것(Pros)
- 자주 쓰는 조회가 단순해지고 빨라질 수 있습니다(조인 감소, 쿼리 단순화)
- 특정 화면/운영 기능 요구사항에 최적화됩니다
- 데이터 접근 패턴이 명확해져서 구현 속도가 올라갑니다
⚠️ 내가 감수하는 것(Cons)
- 규칙이 흐려질 수 있습니다: “정확히 하나만 연결”이 보장되지 않으면 모델 의미가 애매해집니다
- 정합성 관리 비용이 생깁니다: 연결 규칙을 DB/코드/운영 정책 중 어디에서 어떻게 강제할지 결정해야 합니다
- 확장 비용이 올라갑니다: 예약 유형이 늘어날수록 컬럼/제약/인덱스가 계속 추가될 수 있습니다
- 삭제/이력 정책과 충돌할 수 있습니다: 하드 삭제, 소프트 삭제, 이력 보존 등과 함께 설계가 맞물려야 합니다
이 트레이드오프가 정리되지 않으면, 리뷰어 입장에서는 그 설계를 “이유 없는 편의”로 보게 됩니다.
반대로 트레이드오프가 정리되어 있으면, 리뷰는 “맞냐/틀리냐”가 아니라 “위험을 어떻게 관리하냐”로 바뀝니다.
‘정확히 하나만 연결한다’를 리뷰 가능한 규칙으로 바꾸기
앞의 메모 사례에서 예약 유형별 연결 필드를 선택한다면, 나는 최소한 다음 질문에는 답을 남기겠다. 아래는 원래 대화의 설계 문제를 구체화한 검토 예시이며 실제 운영 스키마를 공개한 것은 아니다.
| 입력 또는 사건 | 선택한 규칙이 설명해야 할 결과 |
|---|---|
| 어떤 예약도 연결하지 않음 | 독립 메모를 허용하는가? 허용하지 않으면 저장을 거절한다 |
| 카드 예약과 무통장 예약을 동시에 연결 | 하나의 예약에 속하는 메모라면 거절한다 |
| 존재하지 않는 예약 식별자를 입력 | 연결 개수만 검사해서는 잡을 수 없으므로 참조 무결성도 확인한다 |
| 예약을 삭제 | 메모도 지울지, 삭제를 막을지, 이력과 함께 보존할지 정한다 |
PostgreSQL이라면 같은 행의 연결 개수는 CHECK로, 연결 대상의 존재는 FOREIGN KEY로 책임을 나눌 수 있다. CHECK가 다른 테이블의 상태까지 보장한다고 생각하면 안 된다. PostgreSQL 제약 조건 문서는 이 경계와 삭제 시 참조 동작을 설명한다.
예를 들어 “예약 유형이 당분간 세 개로 고정되고 메모가 반드시 그중 하나에 속한다”가 전제라면 여러 외래 키와 연결 개수 제약을 검토할 수 있다. 반면 예약 유형이 계속 늘고 여러 기능에서 같은 예약을 참조한다면 공통 예약 모델을 검토할 이유가 커진다. 유형과 식별자만 저장하는 방식은 컬럼 추가를 줄이지만, 서로 다른 대상 테이블의 존재를 일반 외래 키 하나로 보장하기 어렵다는 비용이 있다.
리뷰에는 “지금은 전자의 전제가 맞아서 선택했고, 예약 유형 추가나 공통 조회 증가가 생기면 재검토한다”처럼 선택이 유효한 조건과 다시 볼 시점까지 적는 편이 좋겠다. 조회가 빨라진다는 주장은 실제 쿼리와 실행 계획을 비교한 뒤에 붙일 수 있다.
코드 리뷰 요청할 때, 설계 설명은 이렇게 남기면 좋다
리뷰 요청에 아래 4가지만 적어도 퀄리티가 확 올라갑니다.
- 문제 정의: 무엇이 어려워서 이 설계를 고민했는지
- 선택한 설계: 어떤 구조를 택했는지(그리고 어떤 규칙을 강제하는지)
- 대안: 다른 방법(정규화 유지, 공통 예약 모델, 뷰/읽기 모델 등)을 왜 안 택했는지
- 트레이드오프 & 방어책: 정합성/확장/삭제 정책을 어떻게 관리할지(제약/트리거/서비스 로직/배치 등)
결론: 설계는 코드를 잘 짜는 게 아니라, 복잡성을 ‘설명 가능하게’ 만드는 것이다
복잡성은 늘어납니다.
좋은 설계는 복잡성을 제거하는 게 아니라, 관리 가능한 형태로 정리하고 팀이 납득할 수 있는 이유를 남기는 것입니다.
그래서 누군가가
“메모가 상위 관계를 전부 들고 있게 했어요”라고 말했을 때, 질문은 “그게 맞아요?”가 아니라 이렇게 바뀌어야 합니다.
“그 선택으로 얻는 이점과, 감수하는 비용을 어떻게 관리하실 건가요?”
2026-09-14 보완: 연결 제약과 설계 재검토 조건을 구체화했습니다.