Q1-1
VAC 패턴이 뭐고 왜 도입하셨나요?
핵심 포인트
- 3D 엔진은 명령형 API (
engine.setColor(matId, hex)같은 호출형), React는 선언형(상태→뷰)이라는 패러다임 충돌이 출발점 - VAC = View(순수 UI) / Action(엔진 명령 래퍼 훅) / Container(비즈니스 로직·상태 조합) 3계층
- "엔진 API 스펙이 바뀌어도 UI 코드는 안 건드린다"는 것이 핵심 목표
- MVP 기간 내 70개 이상 에디터 컴포넌트를 안정적으로 찍어내야 했던 제약이 설계의 동력
모범 답안먼저 답해보고 펼치기
3D 엔진 개발자분들이 만든 엔진 API가 명령형이었어요. engine.setMaterialColor(id, hex) 처럼 호출하면 뷰가 바뀌는 방식이죠. 반면 React는 선언형이라서 "상태가 이러면 화면은 이거다"가 자연스러운 사고예요. 이 두 패러다임을 그냥 섞으면 컴포넌트 안에 useEffect 가 명령형 호출로 가득 차고, 엔진 API가 한 번 바뀔 때마다 70개 컴포넌트를 다 뒤져야 하는 상황이 됩니다.
그래서 세 계층으로 책임을 나눴어요. View는 진짜로 받은 prop을 그리기만 하는 dumb 컴포넌트, Action은 엔진의 명령형 API를 선언형 훅으로 래핑하는 어댑터 레이어, Container는 Atom 상태와 Action 훅을 조합해서 "지금 사용자가 무엇을 하려는가"라는 비즈니스 로직을 담당합니다. 이렇게 분리하니까 엔진 API 스펙이 바뀌어도 어댑터 훅 한 군데만 수정하면 됐고, 70개 넘는 에디터 UI는 영향 없이 계속 동작했어요.
이 답변, 어땠나요?
꼬리 질문
- Q-a. VAC가 일반적으로 알려진 패턴인가요, 아니면 직접 명명하신 건가요?
Container/Presentational 패턴(Dan Abramov)에서 영감을 얻었고, 거기에 "Action = 외부 명령형 API 어댑터"라는 레이어를 추가해서 팀 내부 명칭으로 정한 패턴입니다. 표준 GoF 패턴이라기보다는 어댑터 패턴 + Container/Presentational의 조합이라고 설명하는 편이 정확합니다.
- Q-b. MVC, MVVM, Flux 같은 기존 아키텍처와 어떻게 다른가요?
MVC는 View↔Controller 양방향이라 React에 잘 안 맞고, MVVM은 데이터 바인딩이 핵심이라 React 모델과 충돌. Flux/Redux는 단방향이지만 외부 명령형 엔진을 다루는 어댑터 계층 개념이 없습니다. VAC는 단방향(Container→Action→Engine→Atom→View) + 어댑터 패턴을 명시적으로 분리한 게 차이입니다.
- Q-c. 70개 컴포넌트를 일관되게 만들 수 있었던 비결은요?
Action 훅의 시그니처를 표준화했어요. 모든 엔진 훅이
useXxx(materialId)형태로 selector + setter를 묶어서 반환하게 통일했고, 컨테이너에서는 그걸 그대로 갖다 쓰면 되도록 만들었습니다. 컨벤션을 코드 레벨에서 강제한 셈이에요. - Q-d. 테스트는 어떻게 짰나요?
Action 훅은 엔진을 mocking 해서 단위 테스트, View는 storybook으로 시각 회귀, Container는 atom 초기값을 Provider로 주입해서 통합 테스트하는 식으로 계층별로 테스트 전략을 분리했습니다. (※ 실제 비중은 솔직하게 답변)
CS · 이론
- 명령형(Imperative) vs 선언형(Declarative) 패러다임
- 명령형: "어떻게(How)" 단계별 지시 —
canvas.fillRect(...), jQuery - 선언형: "무엇(What)"을 원하는지 기술 — React JSX, SQL
- 두 패러다임을 섞으면 부수효과 추적이 어려워지므로 경계(boundary) 에 어댑터를 둔다.
- 명령형: "어떻게(How)" 단계별 지시 —
- 어댑터 패턴 (Adapter, GoF)
- 호환되지 않는 두 인터페이스를 중간 객체가 변환해 주는 구조 패턴
- VAC에서 Action 훅이 정확히 이 역할 — 엔진의 명령형 메서드를 React의 선언형 훅 인터페이스로 변환
- Container/Presentational 분리 (Dan Abramov, 2015)
- 본인이 후회한다고도 했지만 여전히 유효한 멘탈 모델
- "데이터를 알고 있는 컴포넌트"와 "JSX만 그리는 컴포넌트"의 분리
- OCP (Open/Closed Principle)
- "확장에는 열려, 수정에는 닫혀" — 새 엔진 메서드 추가는 쉽고, 기존 UI 수정은 없어야 함
- Anti-corruption Layer (DDD 용어)
- VAC의 Action 레이어는 정확히 ACL 역할 — 외부 도메인(엔진)의 변화로부터 내부 도메인(UI)을 격리