Wails 실전 강좌 #1 실전 프로젝트 설계 — 무엇을 만들고 어떻게 나눌까

5 분 소요

Wails로 데스크톱 앱 만들기 입문 시리즈에서 바인딩, 런타임 통합, 빌드까지 개념을 익혔습니다. 다만 그 시리즈의 할 일 앱은 데이터가 메모리에만 있어서 앱을 끄면 사라지는, 학습용 뼈대였습니다. 이 실전 시리즈는 그 뼈대에 살을 붙여 실제로 설치해 쓸 수 있는 로컬 노트 앱을 여섯 편에 걸쳐 완성합니다. 첫 글은 코드를 짜기 전에 무엇을 만들지 정하고 구조의 경계를 긋는 설계입니다.

총 10편, 2부 구성입니다. 1부에서 배포 가능한 앱을 만들고, 2부에서 리피터가 생기는 완성도를 다집니다.

1부 — 앱을 만들고 배포하기

  • #1 실전 프로젝트 설계 — 무엇을 만들고 어떻게 나눌까 ← 이번 글
  • #2 SQLite 로컬 데이터베이스 — CGO 없이 순수 Go로
  • #3 전문 검색과 데이터 흐름 — FTS5와 이벤트 기반 갱신
  • #4 트레이 상주와 전역 단축키 — 백그라운드에서 빠르게 캡처
  • #5 서명과 공증 — 배포된 앱이 신뢰받는 법
  • #6 CI/CD 자동 릴리스 — GitHub Actions로 세 플랫폼 배포

2부 — 완성도와 리피터

  • #7 노트 에디터 실전 — 마크다운 미리보기와 목록 UX
  • #8 테스트 전략 — 서비스와 저장소를 검증하기
  • #9 자동 업데이트 구현 — 새 버전을 안전하게 전달
  • #10 마감과 완성도 — 설정·다크모드·데이터 백업

무엇을 만드는가 — 로컬 노트 앱 #

만들 것은 마크다운 노트를 로컬에 저장하고 빠르게 검색하는 데스크톱 앱입니다. 클라우드 없이 내 기기 안에서 완결되는 오프라인 우선 도구이고, 다음 기능을 목표로 합니다.

  • 노트 작성·수정·삭제, 로컬 데이터베이스에 영속 저장(#2)
  • 제목·본문 전문 검색(#3)
  • 트레이에 상주하며 전역 단축키로 어디서든 새 노트 캡처(#4)
  • 서명·공증된 설치 파일로 배포(#5)
  • 태그를 밀면 세 플랫폼 릴리스가 자동 생성(#6)

작지만 실제 앱이 갖춰야 할 요소(영속성, 검색, 백그라운드 상주, 배포 신뢰, 자동화)를 모두 통과하도록 고른 범위입니다. 화면은 간단합니다. 왼쪽에 노트 목록과 검색창, 오른쪽에 편집기 하나면 충분합니다.

경계 긋기 — 세 개의 층 #

실전 앱과 학습용 뼈대의 첫 번째 차이는 모든 로직을 앱 구조체 하나에 몰아넣지 않는다는 것입니다. 입문 시리즈에서는 App 구조체에 메서드를 바로 붙였지만, 기능이 늘면 그 구조체가 데이터 저장, 검색, 창 제어를 전부 아는 거대한 덩어리가 됩니다. 처음부터 세 층으로 나눕니다.

역할이번 시리즈의 예
바인딩 층(App)프론트엔드에 노출되는 얇은 진입점App.CreateNote, App.Search
서비스 층실제 도메인 로직NoteService — 검증, 검색, 규칙
저장소 층데이터 영속화NoteRepository — SQLite 접근

바인딩 층은 프론트엔드 요청을 받아 서비스로 넘기고 결과를 돌려주기만 합니다. 도메인 로직(제목이 비면 거부, 검색어 정규화 같은 규칙)은 서비스에, DB를 실제로 읽고 쓰는 코드는 저장소에 둡니다. 이렇게 나누면 저장소를 SQLite에서 다른 것으로 바꿔도 서비스가 그대로이고, 서비스 로직을 Wails 없이 순수 Go 테스트로 검증할 수 있습니다.

구조의 방향 — 위에서 아래로만 의존
// App(바인딩) → NoteService(도메인) → NoteRepository(저장)
type App struct {
	ctx     context.Context
	notes   *NoteService
}

func (a *App) CreateNote(title, body string) (Note, error) {
	// 바인딩 층은 서비스에 위임만 한다
	return a.notes.Create(title, body)
}

의존은 한 방향입니다. 바인딩이 서비스를 알고, 서비스가 저장소를 알되, 반대로 저장소가 바인딩을 아는 일은 없습니다. 이 규칙 하나가 앱이 커져도 구조가 무너지지 않게 지켜 줍니다.

도메인 모델 — 최소한의 노트 #

노트 한 건을 표현하는 타입을 먼저 정합니다. 실전에서는 식별자와 시간 정보가 반드시 필요합니다. 목록 정렬, 수정 추적, 나중의 동기화까지 이 필드들이 기반이 됩니다.

note.go — 도메인 모델
type Note struct {
	ID        int64     `json:"id"`
	Title     string    `json:"title"`
	Body      string    `json:"body"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
}

구조체 태그의 json:"..."입문 #3에서 다룬 대로 프론트엔드로 넘어갈 때의 필드 이름입니다. Go의 CreatedAt이 JavaScript에서 createdAt으로 오도록 카멜 케이스로 맞춰 둡니다.

프론트엔드 상태 — 무엇을 고를까 #

프론트엔드는 입문 시리즈와 같은 바닐라 조합으로도 되지만, 실전 앱은 상태가 늘어납니다. 목록, 선택된 노트, 검색어, 편집 중인 내용, 저장 상태가 동시에 움직입니다. 여기서 선택지가 갈립니다.

  • 바닐라 JS: 의존성이 없고 번들이 가장 작습니다. 상태가 단순할 때 적합하지만, 위 상태들이 얽히면 수동 DOM 갱신이 금세 복잡해집니다.
  • Svelte·Vue·React: 상태와 화면을 자동으로 묶어 줍니다. Wails는 프론트엔드를 가리지 않으므로 무엇이든 되고, 이 시리즈는 상태 관리 부담을 줄이기 위해 가벼운 컴포넌트 프레임워크를 전제로 설명합니다(코드는 프레임워크에 크게 의존하지 않게 유지합니다).

핵심 원칙은 하나입니다. 프론트엔드는 화면과 입력만 책임지고, 데이터의 진실은 항상 Go 쪽에 둡니다. 검색 결과도, 노트 목록도 프론트엔드가 자체적으로 관리하지 않고 Go에 물어봅니다. 이렇게 하면 데이터 정합성을 한 곳에서만 지키면 됩니다.

노트
이 시리즈는 안정판인 Wails v2 를 기준으로 합니다. v3 는 이 글 시점에 알파로, 멀티 윈도우와 개선된 시스템 트레이를 예고하고 있습니다. 여기서 다루는 서비스 층 분리, 저장소 설계, 서명·CI 개념은 v3 에서도 그대로 유효하므로, v2 로 익힌 구조는 이전 비용 없이 이어집니다.

정리 #

  • 실전 시리즈는 오프라인 우선 로컬 노트 앱을 여섯 편에 걸쳐 영속성·검색·트레이·서명·CI까지 완성합니다.
  • 학습용 뼈대와의 첫 차이는 층 분리입니다. 바인딩(App)·서비스·저장소 세 층으로 나누고 의존은 한 방향으로만 흐르게 합니다.
  • 도메인 모델에는 ID와 생성·수정 시간을 반드시 넣고, JSON 태그로 프론트엔드 필드 이름을 카멜 케이스로 맞춥니다.
  • 프론트엔드는 화면과 입력만 담당하고 데이터의 진실은 Go에 둡니다. 상태가 얽히면 가벼운 컴포넌트 프레임워크가 수동 DOM 갱신보다 낫습니다.
  • 다음 글에서 저장소 층을 SQLite로 구현하되, CGO 없이 크로스컴파일이 되는 순수 Go 드라이버를 씁니다.
X