이번 프로젝트에서 Electron과 Vue.js를 사용하여 복잡한 3D 뷰어 애플리케이션을 개발했다. 이 과정에서 반응형 UI와 무거운 3D 렌더링 로직을 어떻게 효과적으로 분리하고 관리할 수 있을지에 대한 깊은 고민이 있었다.
MVVM(Model-View-ViewModel)과 MVC(Model-View-Controller)를 함께 사용하는 하이브리드 아키텍처를 채택했습니다. 이 글에서는 내가 왜 이런 구조를 선택했고, 어떻게 두 아키텍처를 성공적으로 결합했는지 공유하고자 한다.
애플리케이션의 전체적인 구조는 MVVM 패턴을 따른다. Vue.js의 핵심 사상과 가장 잘 맞기 때문이다. 데이터 바인딩을 통해 View와 ViewModel을 연결하여 상태 관리를 매우 효율적으로 할 수 있다.
View: src/renderer/views 폴더의 .vue 파일들이다. 사용자가 직접 보는 UI를 담당하며, ViewModel의 상태를 화면에 그리고 사용자 입력을 ViewModel에 전달한다.
ViewModel: src/renderer/viewModels 폴더의 Pinia 스토어(...Store.ts)와 Command객체(...Command.ts)들입니다. View를 위한 상태와 비즈니스 로직을 가진다. View는 ViewModel의 데이터를 구독하고, 변경이 생기면 자동으로 업데이트된다.
Model: src/main/models의 데이터 구조 및 각종 서비스 로직을 포함한다. 이 프로젝트에서는 3D 뷰어 모듈 전체가 하나의 거대한 모델 역할을 하기도 한다.
src/renderer/
├── views/ # View (Vue Components)
├── viewModels/ # ViewModel (Pinia Stores, Commands)
└── models/ # Model (Data Structures)
```
프로젝트의 가장 핵심적이고 복잡한 부분은3D 뷰어다. 이 부분은
독립적인 컴포넌트처럼 작동하며, 내부 로직이 매우 복잡하다.
이러한 복잡성을 관리하기 위해 나는 뷰어 모듈 내부에 MVC 패턴을 적용했다. 이를 통해 뷰어의 데이터, UI, 그리고 제어 로직을 명확하게 분리할 수 있었다.
View: VTK.js와 같은 렌더링 라이브러리가 실제 View의 역할을 합니다. SceneManagerAdapter 클래스가 씬(Scene)을 관리하며 View를 제어합니다.
Controller: controllers 폴더의 클래스들이다. 외부로부터의 요청을 받아 Model(Service)과 View(SceneManager)의 업데이트를 조정하는 오케스트레이터 역할을 한다.
이 구조를 통해 데이터의 흐름이 Controller -> Service -> Repository로 명확해지며, 각 부분의 책임이 분리되어
테스트와 유지보수가 용이해졌다.
그렇다면 MVVM으로 구성된 외부 애플리케이션은 어떻게 MVC로 만들어진 3D 뷰어와 소통할까?
정답은 바로 Facade 패턴에 있다. 저희는 viewerFacade.ts라는 클래스를 만들어 3D 뷰어 모듈의 유일한 출입구(entry point)로 삼았다.
이러한 구조 덕분에 ViewModel은 3D 뷰어의 복잡한 내부 구현(Controller, Service, Repository 등)을 전혀 알 필요가 없다. 그저 ViewerFacade가 제공하는 단순하고 명확한 API만 호출하면 된다.
복잡한 애플리케이션을 만들 때 하나의 아키텍처 패턴만을 고집할 필요는 없다. 애플리케이션의 UI영역에는 MVVM을, 독립적이고 복잡한 3D 뷰어 모듈에는 MVC를 적용하는 하이브리드 전략을 선택했다. 그리고 Facade 패턴을 이용해 두 아키텍처를 완벽하게 분리하고 연결했다.
이러한 접근 방식은 다음과 같은 장점을 가져다주었다.