2026. 3. 25. 17:01ㆍ개발로그
1. 왜 이 프로젝트에서 아키텍처를 고민하게 되었는가?
이번에 회사에서 맡게된 프로젝트는 오디오, 비디오, 텍스트, 이미지가 한번에 다루어지는 멀티 에디터 제품이다. 평소에도 DAW(Digital Audio Workstation)를 좋아했던 나는(나는 과거에 작곡전공이었고, 작곡을 시작하게 된 이유는 DAW보면 흥분했기 때문이다.) 내가 꼭 해보고 싶었던 프로젝트라 어필하였고, 감사히도 회사에서는 아직 1년차 밖에 되지 않은 나를 믿고 맡겨주었다.
현재 개인프로젝트로 Drop.daw 라는 웹브라우저 기반 DAW를 준비하고 있고(이 내용에 대해서는 추후 아주 긴 시리즈로 글을 쓰게될 것 같다) 작업하던 프로젝트의 아키텍쳐를 3번 갈아엎고 있던차여서, 본능적으로 직감할 수 있었다.
이 프로젝트는, 아키텍쳐 설계를 단단히 하고 들어가야겠다.
우선 생각할 것 들은 이 제품에 들어가야 하는 기능들. 상세하게 밝힐 순 없지만 대략 아래와 같다.
영상편집, 사운드 편집, 텍스트 편집, 이미지 편집, 이 모든 편집들을 한 타임라인안에서 편집해야하고 해당 결과물을 내보내기하는 기능까지. 레퍼런스 사이트는 veed 사이트이다. (https://www.veed.io)
하나의 원본소스를 각기 다른 기능들이 바라보고 있어야한다는점, 각 기능들이 서로 의존하고 있으면 안된다는 점 두가지는 반드시 지켜야겠다고 생각했다. 그리고나서 아키텍처에 어떤종류가 있는지 찾아보았다. 그중 양성훈 님의 개발 블로그인 펼치는 공간에 작성된 글의 도움을 많이 받았다. ( https://de-velop-humus.tistory.com/32)

위 기준으로 생각한 결과, 결정된 아키텍쳐는 레이어드 아키텍쳐. 레이어가 분명히 나누어져있어야한다. 또한 모든 기능들이 하나의 소스를 바라보고 있어야 하므로 단방향 데이터 흐름 아키텍처로 결정 하였다. 각 레이어를 분명히 나누기 위해 FACADE 패턴과 같은 기술들을 도입할 수 있겠다고 생각했다. (이 내용에 대해서는 추후 더욱 자세히 다뤄볼 예정이다)
퍼사드 패턴(Facade Pattern)은 구조 패턴(Structural Pattern)의 한 종류로써, 복잡한 서브 클래스들의 공통적인 기능을 정의하는 상위 수준의 인터페이스를 제공하는 패턴이다.
출처: https://velog.io/@bagt/Design-Pattern-Facade-Pattern-%ED%8D%BC%EC%82%AC%EB%93%9C-%ED%8C%A8%ED%84%B4
또한 흥미로운 개념을 알게되었다.
아키텍처를 나누는 큰 이유중, 변경이 발생할 때 코드베이스 전체가 아닌 특정 계층만 수정하면 되어서 추후 유지보수에 유리하다고 알고있었는데, 실제로는 개발을 할 때 컴포넌트, 비즈니스, 스토어 계층 등 모든 계층에 걸쳐 수정이 필요하다. 즉, 아키텍처로 분리하더라도 여전히 코드베이스 전체가 변경의 영향을 받을 수 밖에 없는데, 이 같은 상황에서 변경 유효범위를 제한하기 위해서는 기능에 따른 수평 분할 외에도 도메인 단위의 수직 분할을 적용하는것이 좋은 해결법이 될 수 있다는 것이다. 이 내용은 내가 생각해낸 것이 아니라 양성훈 님의 개발 블로그인 펼치는 공간을 참고하였다.
아직 경험과 배움이 많이 부족한 나는 이러한 글들을 최대한 많이 참고하고 공부하며 아키텍쳐를 설계하기로 하였다. 비즈니스 레이어와 스토어레이어, 유틸리티 레이어를 분리하는것, 추가적으로 페이지레이어, 컴포넌트 레이어를 분리한다는것이 평소 작업하면서 무의식적으로 그런 방향으로 만들고는 있었지만, 이렇게 설계단계에서 계획하고 들어가면 더 좋을 것 같다.

여기서 또 하나 중요게 지켜져야 할 원칙이 있다. 이것은 앞서 말한 개인프로젝트인 drop.daw 를 진행할때 알게된 개념인데, 이 프로젝트에서도 역시 똑같이 사용될 것 같다. 그것은 "단일진실공급원(Single Source of Truth)"로, 생소한 이름이지만 간단히 말하자면, 모두가 동일한 데이터를 참고하도록 시스템적으로 제한하는 것이다. 안드로이드 케이스이긴하지만, 다음 블로그에 아주 상세히 이 개념에 대해 다루고 있으니 궁금하신분들은 참고하시면 좋을것 같다. (https://declan.tistory.com/103)
단일 진실 공급원(SSOT)은 정보 시스템 설계 및 이론 중 하나로 정보와 스키마를 오직 하나의 출처에서만 생성, 편집하도록 하는 방법론이다. 단일 출처를 통해 데이터를 생성, 편집, 접근하므로 데이터의 정합성을 지키고 잘못된 데이터 유통을 방지하고 모두가 동일한 데이터를 참고하게 하고 있다.
출처: https://onlyfor-me-blog.tistory.com/696
2. 설계원칙
위 모든 생각들을 정리하니 아래와 같은 설계원칙이 정리되었다.
1. 레이어드 + 수직 분할 - 수평(계층)과 수직(도메인) 모두 분할 적용 (영역별 분리(1차), 기능별 분리(2차))
2. 하나의 원본 소스를 모든 기능이 바라본다(SSOT)
3. 단방향 데이터 흐름 - 데이터는 항상 위에서 아래로 흐른다
4. 기능 간 직접 의존 금지 - Store를 통한 간접 교류(Facade 패턴 이용)
이제 이 설계원칙들과 위에서 공부한 개념들을 활용해 우리 프로젝트에 적용해보자.
2-1. 수평분할하기: 레이어드 아키텍처
우선 분명한건, 오디오 엔진, 비디오 인코딩, Canvas 합성 등을 담당하는 서비스 레이어는 순수하게 분리되어야할 것 같다. 서비스레이어는 스토어를 몰라야한다. 그 위에 스토어가 있고, 비즈니스, 컴포넌트, 페이지 레이어가 순서대로 있으면 될 것 같다.
2-2. 수직 분할하기: 도메인 모듈
들어가기에 앞서, 이 프로덕트의 ui 는 대략 이러한 구조이다.


ui 에서 보이는대로, Header, Menu bar, Source menu, Video preview, Timeline 도메인모듈 단위로 수직 분리를 크게 1차적으로 한번 하고, 세부 기능별로도 모듈을 분리하는것으로 원칙을 세웠다. 이렇게 결정한 가장 큰 이유는, 이 프로젝트에 사용되는 각 기능들(예를들면 segment)은 생소한 개념이 많아서 처음 보았을때 즉각적으로 파악하기 쉬운것은 ui단으로 분리하는것이 가장 직관적이라고 판단했기 때문이다.
2단계 분할
"타임라인 자르기 버그 어디 고치지?" | timeline/ → segment/ → facades/
"프리뷰에 크롭 기능 추가해야해" | preview/ → crop/ (새 폴더 생성) |
→ 해당 코드가 어느 영역인지 화면에 따라 분리된 파일 구조로, 코드에 대한 온보딩 비용이 줄어든다.
우선 큰 원칙만 세워두고, 스펙에 따라 어떤 기능들이 필요할지는 작업을 하면서 정해가도록 한다. 각 영역끼리는 공유해야할 상태들이 많기때문에(현재 재생위치 등) Zustand를 이용해 동기화작업이 필요할 것이라 예상된다.(해당 내용에 대해서도 추후 블로그글에서 상세히 다루도록 하겠다)
2-3. 데이터가 꼬이면 끝이다 : 단방향 데이터 흐름과 SSOT(Single Source of Truth)
멀티 에디터에서 가장 무서운 것은 데이터 흐름이 꼬이는 것 이다. 따라서 데이터가 단방향으로 흐름을 유지하는것이 중요한데, 그렇지 않으면 다음과 같은 무서운 일이 벌어진다.
타임라인에서 세그먼트 이동
→ 프리뷰가 바뀜
→ 프리뷰에서 위치 조정
→ 타임라인 세그먼트 데이터 변경
→ 다시 프리뷰 갱신
→ ??? (어디서 시작된 변경인지 추적 불가)
단방향 흐름에서는 이 문제가 원천적으로 사라진다.
사용자 액션 → Facade → Store(SSOT) → 구독한 모든 컴포넌트 자동 갱신
이것이 이 프로젝트에서 특히 중요한 이유는, 영상/사운드/텍스트/이미지 4가지 미디어 타입이 하나의 타임라인위에 공존하기 때문이다. 타임라인에서 세그먼트를 자르면, 프리뷰에서도 바로 반영되어야하고, QuickBar 의 상태도 바뀌어야한다. 이 모든것이 하나의 Store를 바라보고 있으면, 데이터를 한 곳만 업데이트하면 나머지는 자동으로 따라온다.

이렇게 분리된 레이어들과 데이터들은 서로를 의존하지 않아야하고, 데이터는 항상 위에서 아래로 흐르고 컴포넌트는 Store를 구독할 뿐 서로를 직접 업데이트하지 않아야한다.
3-1. 추가 고민 1: Layout과 Domain의 경계
최종 아키텍처 다이어그램을 그려보았다.
그런데 잠깐, Menu bar 가 굳이 도메인으로 나누어져야 할 이유가 있나?
그리고 또 한가지의 찝찝함, 여태껏 Header 나 Menu bar 는 레이아웃으로 따로 빼왔는데, 굳이 하나의 도메인 모듈로 분리하는것이 옳은것인가? 그리고 간과한점, 그럼 export로직은 어디에 속해야하는거지? 현재 프로젝트에 대한 제목같은 정보는??
domains/
├── menubar/ ← 이게 도메인이 맞나?
│ └── navigation/
├── menu/
├── preview/
├── timeline/
└── project/
다시 생각해보니, Header나 Menu bar 같은 부분들은 도메인이 아니다. 도메인은 비즈니스 로직을 갖고 있어야 한다. 반면, Project에 대한 정보는 비즈니스 로직을 통해 관리되어야 한다.
소프트웨어 아키텍처 관점의 도메인
Domain Layer : 시스템의 핵심 비즈니스 모델(메모, 유저)
Application Layer(UseCase) : 유저의 요청으로 기능을 수행(메모 생성, 메모 삭제)
Interface Layer(UI, Controllers) : 유저와 상호작용하는 입구(프레임워크, 라이브러리 등)
출처: https://ldd6cr-adness.tistory.com/entry/도메인-Domain은-뭘까 [🥔 감자의 오묘한 개발 🥔:티스토리]
3-2. 추가 고민 2: Domain 은 핵심 비즈니스 모델로 나누어야한다.
여기까지 설계하고 나서, 한가지 근본적인 의문이 생겼다.
domains/
├── menu/ ← UI 영역 아닌가?
├── preview/ ← UI 영역 아닌가?
├── timeline/
└── project/
Menu, Preview, Timeline, Project로 도메인을 나누기에는 스펙에는 video도 있고, audio, text, image에 대한 기능도 있었다. 막연하게 화면에 보이는 부분들로 핵심 비즈니스 모델들이 나누어져 있을것이라 생각했던게 오산이었다. 다시 스펙을 살펴보았다.
스펙 자체가 이미 답을 갖고 있었다.
1순위 기능
영상 편집 - 불러오기, 오디오 뮤트, 이동
사운드 편집 - 불러오기, 오디오 뮤트, 이동
텍스트 편집 - 이동, 텍스트 입력
이미지 편집 - 불러오기, 이동
공통 - 재생, 내보내기, 자동 저장
모든 기능이 영상/사운드/텍스트/이미지/공통 5가지로 분류되어있었다. 이게 진짜 도메인이었다. (공통 수준은 project 도메인으로 분리했다.)
4. 아키텍처 최종: 수평 분할 x 수직 분할
결국 최종적으로 결정된 아키텍처는 아래와 같다.

4 -1. 계층 간 호출 규칙(수평 분할)

4-2. 도메인 간 호출 규칙(수직 분할)

이와같은 아키텍처가 기능들을 붙이면서 또 어떻게 변화될지 모르지만, 설계원칙들은 끝까지 잘 지켜졌으면 하는 바람이다. 그것이 결국 개발 효율을 높이고 개발자와 제품 모두를 위한 일이니까.
경험이 부족한 만큼 더 나은 접근법이 있다면 댓글로 알려주신다면 언제든 환영이다. (실제 기능을 붙이면서 이 구조가 어떻게 변하는지는 후속 글에서 다루겠다)
참고:
레퍼런스 사이트
참고 블로그 / 문서
- https://de-velop-humus.tistory.com/32 (양성훈 님, 펼치는 공간 — 프론트엔드 아키텍처)
- https://velog.io/@bagt/Design-Pattern-Facade-Pattern-%ED%8D%BC%EC%82%AC%EB%93%9C-%ED%8C%A8%ED%84%B4 (퍼사드 패턴)
- https://declan.tistory.com/103 (SSOT — 안드로이드 케이스)
- https://onlyfor-me-blog.tistory.com/696 (단일 진실 공급원 개념)
- https://ldd6cr-adness.tistory.com/entry/도메인-Domain은-뭘까 (도메인 개념 정리)
- https://velog.io/@khjbest/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84 (프론트엔드 아키텍처 설계)
'개발로그' 카테고리의 다른 글
| [Part 1] 브라우저에서 멀티미디어 export하기- 문제 정의와 아키텍처 설계 (0) | 2026.04.13 |
|---|---|
| [AI] 에이전트 코딩 시대에 front-end 개발자로서의 역할에 대한 고찰 (0) | 2026.04.06 |
| [Part 2.] 웹브라우저 멀티에디터 만들기 - 아키텍쳐 설계 변화 (0) | 2026.03.26 |
| 오디오 파형 로딩 지연 해결기: 동적 stride를 이용한 Peak 연산 최적화 (0) | 2026.02.04 |
| rAF로 오디오 에디터 줌 기능 버벅거림 잡기 (0) | 2026.02.02 |