전술적 프론트엔드 설계
아키텍처는 결국 코드 안에서 구체적인 형태를 가져야 합니다.
전략적인 설계는 시스템의 도메인 경계와 책임을 정의하고, 어떤 부분이 함께 변화해야 하는지 또는 어느 정도 독립적으로 유지되어야 하는지를 결정할 수 있습니다. 하지만 실제 구현 단계에 들어가면 조금 다른 질문이 필요해집니다.
데이터는 어느 파일에서 가져와야 할까요? ViewModel은 어디에서 만들어야 할까요? Command는 누가 시작해야 할까요? Mapping, Navigation, Notification은 어느 책임에 속할까요? 그리고 어떤 작업은 처음부터 Component가 담당하지 않는 편이 더 좋을까요?
이런 질문을 다루는 영역을 여기서는 전술적 프론트엔드 설계라고 부릅니다.
왜 “전술적” 설계인가
섹션 제목: “왜 “전술적” 설계인가”이 표현은 Domain-Driven Design의 Tactical Design에서 아이디어를 가져왔습니다.
DDD에서는 Domain과 Boundary, Context 사이의 관계를 다루는 전략적 설계와, 그 경계 안에서 Entity, Value Object, Aggregate, Repository 같은 구체적인 구현 요소를 다루는 전술적 설계를 구분합니다.
Frontend에서 이 개념들을 그대로 복제할 필요는 없습니다. Angular 프로젝트의 폴더에 DDD 용어를 더 많이 사용한다고 해서 자동으로 좋은 Architecture가 만들어지는 것은 아닙니다.
하지만 그 안에 있는 기본적인 생각은 충분히 유용합니다.
Architecture는 결국 코드 안에서 구체적인 형태로 표현되어야 합니다.
Frontend에서는 그 형태가 조금 다를 수 있습니다. Read Flow는 UI를 위한 도메인 중심 Projection을 만들 수 있고, ViewModel은 View가 실제로 필요로 하는 데이터를 명확하게 표현할 수 있습니다. Command는 하나의 변경과 그 결과를 모델링할 수 있으며, Mapping은 외부 데이터 모델이 Frontend 전체로 그대로 퍼지는 것을 막아줄 수 있습니다.
Notification, Navigation, Invalidation 역시 HTTP 요청이 끝난 위치에 우연히 추가하기보다는, 책임을 설명할 수 있는 명확한 위치를 갖는 편이 좋습니다.
그래서 여기서 말하는 Tactical Design은 이미 정해진 도메인 경계 안에서 코드의 책임을 어떻게 나눌 것인가에 초점을 둡니다.
Architecture Idea에서 Code Slice까지
섹션 제목: “Architecture Idea에서 Code Slice까지”많은 Frontend Architecture 문제는 팀이 적절한 용어를 몰라서 시작되는 것은 아닙니다.
실제 프로젝트에서는 Responsibility가 요구사항과 함께 조금씩 당시 가장 편한 위치로 이동하는 경우가 많습니다.
처음에는 Page Component가 데이터를 가져오는 일만 담당합니다. 이후 Loading State가 들어오고, Error Handling이 추가됩니다. 그다음에는 Mapping, Toast, 저장 후 Navigation, 목록 Reload, 그리고 몇 가지 예외 처리가 더해질 수 있습니다.
그러다 보면 어느 순간 Component가 작은 Use Case Orchestrator처럼 동작하게 됩니다.
Store도 비슷한 방향으로 성장할 수 있습니다. 처음에는 State를 관리했지만, 시간이 지나면서 HTTP, Mapping, Commands, Form Logic, Routing, Domain Decision까지 함께 담당하게 될 수 있습니다.
이런 구조도 상당히 오랫동안 정상적으로 동작할 수 있습니다. 그래서 문제가 커지기 전에 Responsibility를 어떻게 나눌지 생각해보는 것이 의미가 있습니다.
이 영역의 예제는 개별 Pattern 하나만 설명하기보다는 작지만 완전한 Code Slice를 보여줍니다. Store, Mapper, Command가 존재하는지보다 중요한 것은 이들이 어떻게 협력하고 각자의 Responsibility가 어디에서 끝나는가입니다.
개별 개념에 대한 기본 설명은 영어 버전의 ViewModel Aggregation, Event-driven Projection, Anti-Corruption Layer에서 확인할 수 있습니다.
여기서는 그 다음 단계를 다룹니다. Architecture Idea를 실제로 실행 가능한 코드 구조로 옮기는 과정입니다.
하나의 정답은 없습니다
섹션 제목: “하나의 정답은 없습니다”예제는 Angular 21, NgRx 21, Signals, NgRx Signal Store를 사용하는 현대적인 Angular 환경을 기준으로 합니다.
이 버전들은 사용할 수 있는 도구를 결정하지만, Architecture 자체를 결정하지는 않습니다.
장기간 유지되고 Read Projection이 복잡하며 여러 Component가 같은 State를 사용하는 Domain Slice라면, 별도의 Read Store와 Mapper, ViewModel, Tests가 좋은 선택이 될 수 있습니다.
반대로 작고 안정적인 Backoffice Dialog에서는 같은 구조가 필요 이상으로 복잡할 수도 있습니다.
그래서 여기의 예제는 다음과 같은 의미가 아닙니다.
“이 방식이 정답입니다.”
대신 실제로 검토하고 토론할 수 있을 만큼 구체적인 하나의 설계를 제안합니다.
어떤 구조가 적합한지는 Domain Complexity, 예상 수명, 변경 빈도, Team Experience, Test Strategy, 그리고 이미 존재하는 Architecture에 따라 달라집니다.
좋은 Architecture에는 적절한 Structure가 필요합니다.
하지만 Structure가 많다고 해서 자동으로 좋은 Architecture가 되는 것은 아닙니다.
Code Slices
섹션 제목: “Code Slices”첫 번째 예제들은 익숙한 CRUD Flow를 사용합니다. 읽기와 쓰기가 Frontend에서 실제로 얼마나 다른 Responsibility를 가질 수 있는지 보여주기에 좋은 출발점이기 때문입니다.
영어 버전의 Retrieve Slice: Loading Data Without Component Logic은 Infrastructure에서 ViewModel까지 이어지는 Read Flow를 보여줍니다.
Create Slice: Writing as a Command Flow은 Create를 단순한 POST가 아니라 결과와 후속 효과를 가진 변경으로 다룹니다.
Update Slice: Form State Is Not a DTO는 Edit State, Backend Model, Command 사이의 경계를 살펴봅니다.
Delete Slice: Deletion Is a Domain Decision은 단순해 보이는 Delete Flow도 HTTP 요청 이상의 의미를 가질 수 있다는 점을 다룹니다.
하나의 Slice를 넘어서는 반응도 있습니다. Notification Flow, Routing After a Successful Command, Reload and Invalidation After Success에서는 이런 후속 반응을 처음 Command를 실행한 Component로 다시 밀어 넣지 않고 어디에서 모델링할 수 있는지 살펴봅니다.
결국 중요한 것은 Responsibility입니다
섹션 제목: “결국 중요한 것은 Responsibility입니다”Tactical Frontend Design은 두 가지 극단 사이에 있습니다.
한쪽에는 “명확한 Boundary가 필요하다”는 Architecture 원칙이 있습니다. 중요한 이야기지만, 실제 Feature를 구현할 때 어느 Responsibility를 어느 파일에 둘 것인지까지는 알려주지 않습니다.
다른 쪽에는 “NgRx Signal Store와 withMethods, withHooks를 사용한다”는 기술적인 선택이 있습니다. 이것도 좋은 선택일 수 있지만, Store가 무엇을 책임해야 하는지는 여전히 별도의 문제입니다.
이 영역은 그 중간을 다루려고 합니다.
실제로 코드를 작성할 수 있을 만큼 구체적이면서도, 왜 이런 구조를 선택했는지 이해할 수 있을 만큼은 추상적인 수준입니다.
여기의 예제들이 최종적인 정답은 아닙니다.
실제 프로젝트에서 검토하고, 비판적으로 살펴보고, 필요하다면 다시 나눌 수 있을 정도로 구체적인 출발점에 가깝습니다.