그래놀라
그래놀라는 TodoMVC의 데스크탑 버전입니다. 시간을 덤덤하게 사용하고 싶은 마음에 저만의 투두 프로그램을 만들었습니다.
그래놀라에서 사용하는 데이터는 주제(bowl)와 할 일 템플릿(flake), 그리고 할 일 인스턴스(todo)로 구성되어 있어요.
사용한 기술 스택
Wails(Webview on Rails): wails는 GO를 백엔드로, webkit window를 프론트엔드로 구성한 프레임워크입니다. 상호 통신은 IPC로 이루어지고, Bind라는 컨셉을 통해 struct의 메소드를 타입스크립트 인터페이스로 노출시킬 수 있습니다. 일렉트론을 안쓰고 Wails를 사용한 이유는 새로운 언어에 익숙해지고 싶은 마음과, 이왕이면 경량화된 프로그램을 만들어보고싶은 마음 때문입니다.
GO와 관련된 라이브러리: 유저가 만든 모든 것들은 유저의 파일 시스템 어딘가에 직접적으로 저장이 되어야 합니다. 데이터베이스 레이어에 무거운 프로그램을 추가하기보다는 작지만 데이터베이스의 역할을 충실히하는 SQLITE3을 사용했습니다. ORM은 따로 사용하지 않고 표준 database/sql 을 사용하였고, 마이그레이션과 관련된 로직은 임베드를 사용했어요.
타입스크립트: Wails에 바인딩 된 인터페이스는 타입스크립트 파일로 산출됩니다. 이 인터페이스는 TRPC나 타입스크립트 기반의 백엔드 라이브러리가 제공하는 RPC Client와 비슷한 모양을 취하는데요, 어느정도 타입의 안정성을 보장하면서 자바스크립트의 함수를 호출하는 방식처럼 보이기 때문에 프론트엔드 개발의 복잡성을 많이 줄여줍니다. 이러한 편리함 외에도 타입을 통해 프로덕트 개발의 룰을 만드는 것은 안정성에도 도움이 되기 때문에 주저없이 타입스크립트를 선택했습니다.
React.js와 관련된 라이브러리: 프론트엔드 베이스를 리액트로 선택한 이유는 최신 버전(19)에 대해 적응을 해보고 싶었기 때문입니다. 이 글을 작성하는 시점에서는 피어 디펜던시 이슈로 18.3.1로 다운그레이드 했지만 말이에요.
- 라우팅과 관련된 툴은 Tanstack Router를 사용했습니다. 이 라이브러리를 선택하게 된 배경이 있는데요, 많은 리액트 기반 프레임워크가 SSR을 위해 규정해놓은 라우팅 규칙들을 CSR에서도 얼마나 잘 호환되는지 몰랐습니다. 여기서 말하는 라우팅 규칙은 File First(파일 시스템의 디렉토리 구조로 라우터를 만듬) 이거나 Code First(개발자가 직접 path를 지정함) 더라구요. 저는 File First 방식을 취했는데요, Tanstack Router는 컨벤션에 맞게 라우터를 작성하면 HMR에서 돌아가는 CLI 플러그인이 자동으로 path를 만들어줍니다. 이 path가 전부 타입스크립트로 추론이 되어 상당히 개발 경험이 좋았습니다. 이 라이브러리가 제공하는 loader라는 기능은 React Router 6.28에서 등장한 loader의 패턴에 Tanstack/query에서 사용하는 옵션을 추가한 것 같습니다. 간단한 앱을 만들 때에는 굳이 Tanstack/query를 쓸 필요가 있을까 싶었어요.
- UI와 관련된 툴은 shadcn/ui와 headlessUI를 사용하였습니다. 아주 멋진 유저 플로우를 기획하고 디자인을 만들면 좋았겠지만 직접 만들면서 모든 걸 해보고 싶었던 마음이 컸어요. 기존의 기능들을 엎기도 했지만 결과적으로 이 두 개를 선택한 것은 만족스러웠습니다. 그 외에 필요한 툴은 직접 개발했습니다.
- 백엔드 상태를 캐싱하기 위해 Zustand를 사용하였습니다. 이 툴은 이터레이션 막바지에 도입한 것인데요, 원래는 Tanstack Router의 loader만을 이용해서 마치 서버 상태를 캐싱하는 것처럼 상태를 관리하였습니다. 그러나 지금 단계에서는 Startup시 클라이언트에서 처음에 캐싱할 데이터를 부트스트랩 단계에서 밀어넣는 것이 더 좋다고 생각했어요. 이 결정을 한 다음, 저는 Context를 사용한 상태관리를 고민하지 않고 바로 Zustand를 도입했는데요, 셀렉터를 이용해서 정말로 바뀐 값만을 비교하여 리렌더링을 트리거한다는 점, 스토어에서 값과 값의 조작을 한 눈에 볼 수 있다는 점(이건 저의 취향인 것 같아요) 등을 봤을 때 구태여 Context를 사용할 필요가 있을까 생각했어요. 지금 단계에서는 주제와 템플릿, 그리고 할 일은 전부 전역적으로 관리되는게 관리하기가 편하더라구요.
기술적 도전과 해결 과정
언제나 새로운 것을 도전하는 건 즐겁습니다! 1차 이터레이션에서 도전했던 항목은 다음과 같습니다.
- 데스크탑 아티팩트가 컴파일되는 과정에서 제 컴퓨터의 path으로 데이터베이스 path가 잡히고 빌드가 되는 문제가 있었어요. 이 path를 결정하고, 데이터베이스와 관련된 로직은 런타임에서 진행되어야만 합니다. wails는 OnStartUp 옵션을 제공합니다. 이 옵션은 런타임에서 실행되고, window가 올라가기 전에 실행되므로 제 니즈에 맞았습니다. 그래서 모든 데이터베이스와 관련된 로직을 이 옵션에 할당하는 방식으로 문제를 해결했어요.
이터레이션을 마치며
1차 이터레이션은 새로운 기술 스택을 탐험하고 실험해볼 수 있는 좋은 기회였습니다. GO와 Wails를 사용하여 경량화된 데스크탑 애플리케이션을 만들면서, 웹 기술과 네이티브 애플리케이션의 장점을 결합하는 방법을 배웠습니다. 프로덕트와 관련된 측면에서 얻었던 교훈도 있었어요. 기능 자체와 기능과 관련된 플로우를 먼저 기획하자는 것입니다. 세상에는 만들고 싶은 기능이 너무 많고, 만들고 싶은 모습도 너무 많은데 모든 걸 다 취할 수 있는건 없더라고요. 개발을 할 때 유저가 필요한 것은 무엇일까, 그걸 어떤 플로우로 구현을 할 것인가 먼저 생각해보려고 합니다.
2차 이터레이션은 안정성을 높이는 작업을 진행할 예정이에요. 에러 처리가 제대로 되어있지가 않은데, 이와 관련된 플로우를 구상해보면서 이것부터 잡고 추가적인 기능을 넣어보려고 합니다.
-끝-
'dev-log' 카테고리의 다른 글
| GO의 자동화 테스트 정리 (0) | 2025.01.14 |
|---|---|
| [Granola] 002 (0) | 2025.01.07 |
| Two Events (0) | 2025.01.06 |
| [Granola] 001 (0) | 2025.01.03 |
| [Javascript] Promise.all (1) | 2024.12.09 |