Wails 실전 강좌 #10 마감과 완성도 — 설정·다크모드·데이터 백업

5 분 소요

시리즈의 마지막 글입니다. #9까지 기능은 다 됐습니다. 하지만 기능이 된 앱과 매일 다시 열고 싶은 앱 사이에는 마감의 차이가 있습니다. 이번 글은 그 마감, 곧 설정·다크모드·창 상태·데이터 안전을 채우고, 열 편 전체를 관통한 원칙을 회고하며 시리즈를 닫습니다.

이 시리즈는 총 10편, 2부 구성입니다.

설정: 무엇을 어디에 저장하나 #

앱이 커지면 사용자가 바꾸고 싶은 값이 생깁니다. 폰트 크기, 테마, 자동저장 지연, 단축키 조합 같은 것입니다. 이 설정은 노트 데이터와 성격이 다릅니다. 검색할 필요도, 관계도 없는 단순 키-값이므로 #2의 SQLite에 넣기보다 JSON 파일 하나가 맞습니다. 위치는 입문 #5에서 다룬 os.UserConfigDir 아래, 노트 DB 옆입니다.

settings.go — 설정 구조체와 기본값
type Settings struct {
	Theme         string `json:"theme"`         // "system" | "light" | "dark"
	FontSize      int    `json:"fontSize"`
	AutosaveDelay int    `json:"autosaveDelay"` // ms, #7의 디바운스와 연결
}

func defaultSettings() Settings {
	return Settings{Theme: "system", FontSize: 15, AutosaveDelay: 600}
}

설정을 서비스로 노출하는 방식은 노트와 같습니다. GetSettings·SaveSettings를 바인딩으로 두면 프론트엔드가 설정 화면에서 읽고 씁니다. 중요한 것은 기본값을 코드에 두는 것입니다. 설정 파일이 없거나(첫 실행) 일부 키가 빠져 있어도 기본값으로 채워, 앱이 항상 온전한 설정으로 시작하게 합니다.

다크모드: 시스템을 따라가기 #

테마는 설정의 대표 항목입니다. 세 값(system·light·dark)을 두되, 기본은 system, 곧 OS 설정을 따라가는 것이 실전의 기본값입니다. 사용자가 OS를 다크로 바꾸면 앱도 따라 어두워지는 것이 자연스럽습니다. 프론트엔드에서는 CSS의 prefers-color-scheme로 시스템 값을 받고, 사용자가 명시적으로 light/dark를 고르면 그 선택이 우선합니다.

theme.js — 설정과 시스템을 합치기
function applyTheme(setting) {
  let theme = setting; // "light" | "dark" | "system"
  if (setting === "system") {
    theme = window.matchMedia("(prefers-color-scheme: dark)").matches
      ? "dark" : "light";
  }
  document.documentElement.dataset.theme = theme; // CSS가 이 값으로 색을 바꾼다
}

색은 CSS 변수로 한 곳에 모아 두고 data-theme로 전환하면, 화면 곳곳에 색을 흩뿌리지 않고 테마를 관리할 수 있습니다.

창 상태 기억 #

작지만 체감이 큰 마감이 창 상태입니다. 사용자가 창 크기를 조절하고 위치를 옮겼는데 다음에 열 때 원점으로 돌아가 있으면, 매번 다시 맞춰야 합니다. 앱을 닫을 때(또는 #4의 숨김 훅에서) 창의 크기·위치를 설정 파일에 저장하고, 시작할 때 그 값으로 복원합니다. Wails 런타임의 창 크기·위치 조회·설정 함수로 읽고 씁니다. 다중 모니터에서 저장된 위치가 화면 밖이 되는 경우만 방어하면 됩니다.

데이터 안전: 백업·내보내기·가져오기 #

노트 앱에서 가장 잃으면 안 되는 것은 사용자의 노트입니다. 마감의 핵심은 데이터 안전이고, 세 가지를 갖춥니다.

  • 백업: 앱이 주기적으로 DB 파일을 복사해 두면, 손상이나 실수 삭제에서 되돌릴 수 있습니다. SQLite는 파일 하나이므로 백업이 곧 파일 복사입니다.
  • 내보내기: 사용자가 자기 노트를 앱 밖으로 꺼낼 수 있어야 합니다. 전체 노트를 마크다운 파일들이나 JSON 하나로 내보내면, 앱을 떠나도 데이터는 사용자 것으로 남습니다. 이것은 신뢰의 문제이기도 합니다.
  • 가져오기: 내보낸 것을 다시 불러오거나 다른 기기에서 옮겨 올 수 있어야 합니다. 입문 #4의 파일 다이얼로그로 파일을 고르고, 서비스가 파싱해 저장소에 넣습니다.
export.go — 전체 노트를 JSON으로 내보내기
func (s *NoteService) ExportJSON() ([]byte, error) {
	notes, err := s.repo.List()
	if err != nil {
		return nil, err
	}
	return json.MarshalIndent(notes, "", "  ") // 사람이 읽을 수 있는 형태로
}

내보내기·가져오기는 #2의 저장소 위에 자연스럽게 얹힙니다. 층을 나눠 둔 덕에, 노트를 꺼내고 넣는 새 기능이 서비스에 메서드 몇 개를 더하는 일로 끝납니다.

열 편의 원칙 회고 #

열 편을 관통한 결정들을 한 줄씩 돌아봅니다.

  • #1 층 분리: 바인딩·서비스·저장소를 나누고 의존을 한 방향으로. 이 하나가 #8의 테스트와 #10의 내보내기를 쉽게 만들었습니다.
  • #2 순수 Go SQLite: CGO를 피한 선택이 #6의 크로스컴파일과 #8의 CI 테스트에서 보상으로 돌아왔습니다.
  • #3 진실은 Go에: 데이터를 이벤트로 흘리는 설계가 #4의 백그라운드 캡처와 #7의 목록 갱신을 한 줄로 해결했습니다.
  • #4 백그라운드 상주, #5 서명·공증, #6 CI 자동화로 앱을 실제로 배포 가능하게 만들었고,
  • #7 에디터, #8 테스트, #9 자동 업데이트, #10 마감으로 다시 열고 싶은 완성도를 채웠습니다.

관통하는 교훈은 하나입니다. 초반의 구조 결정(층 분리, 순수 Go, 진실은 Go에)이 후반의 모든 기능을 싸게 만든다는 것입니다. 실전은 화려한 기능이 아니라, 이 결정들이 서로를 받쳐 주는 데서 나옵니다.

마무리 #

입문 시리즈가 Wails의 문법과 개념을 다뤘다면, 이 실전 시리즈는 하나의 앱을 설계부터 배포, 그리고 사용자가 다시 찾는 완성도까지 끌고 갔습니다. 층을 나누고, CGO를 피하고, 검색을 얹고, 백그라운드에 상주시키고, 서명하고, 자동화하고, 테스트하고, 업데이트를 붙이고, 마감했습니다. 여기까지 왔다면 아이디어 하나를 실제 사용자가 매일 쓰는 데스크톱 앱으로 만드는 전체 여정을 손에 넣은 셈입니다. 이상으로 Wails 실전 강좌를 마칩니다.

정리 #

  • 설정은 검색·관계가 없는 단순 키-값이므로 SQLite가 아니라 JSON 파일 하나에 둡니다. 기본값을 코드에 둬서 파일이 없어도 온전히 시작합니다.
  • 다크모드의 기본은 시스템을 따라가는 것입니다. 색은 CSS 변수로 모으고 data-theme로 전환합니다.
  • 창 크기·위치를 저장·복원해 매번 다시 맞추지 않게 합니다. 화면 밖 위치만 방어합니다.
  • 데이터 안전은 백업·내보내기·가져오기로 갖춥니다. 내보내기는 신뢰의 문제이고, 층 분리 덕에 저장소 위에 쉽게 얹힙니다.
  • 열 편의 교훈은 하나입니다. 초반의 구조 결정이 후반의 모든 기능을 싸게 만듭니다. 이상으로 시리즈를 마칩니다.
X