[BroBean] BroBean 프로젝트 #11 개인과 커플을 모두 위한 모드 분리 - Pinia 기반 개인/커플 모드 전환 구현
👨🌾 개인과 커플을 모두 사용할 수 있는 농장 만들기
앞선 글까지 BroBean은 커플이 함께 자산을 관리하는 서비스라는 방향을 중심으로 기능을 확장해왔다.
가계부를 기록하고,
저축 목표를 세우고,
챌린지를 진행하고,
예산을 관리하고,
고정지출을 설정하고,
마지막으로 두 사람이 함께 농장을 키워가는 구조다.
그런데 실제 기능을 하나씩 구현하다 보니 한 가지 문제가 생겼다.
BroBean을 꼭 커플만 사용할 수 있어야 할까?
커플이 아닌 사용자도 자신의 소비와 저축을 관리할 수 있어야 한다면,
기존의 커플 중심 데이터 구조를 그대로 사용할 수는 없었다.
예를 들어 한 사용자가 혼자 가계부를 작성한다고 생각해보자.
개인적으로 지출을 기록하고,
개인적인 저축 목표를 만들고,
개인 챌린지를 진행하고,
개인 예산을 설정하고,
개인 고정지출을 관리할 수 있어야 한다.
반대로 파트너와 연결한 사용자는 같은 기능을 커플 단위의 데이터로 관리해야 한다.
결국 하나의 서비스 안에서 다음 두 가지 사용 경험을 동시에 제공해야 했다.
개인 모드
↓
나 혼자 사용하는 농장
커플 모드
↓
파트너와 함께 사용하는 농장
그래서 이번에는 BroBean에 개인 모드 / 커플 모드라는 개념을 도입했다.
🌱 1. 왜 개인 모드와 커플 모드를 나누게 되었을까?
처음에는 BroBean의 대부분의 기능을 커플 기준으로 생각했다.
예를 들어 가계부 데이터도:
사용자
↓
커플
↓
가계부
와 같은 구조로 생각할 수 있었다.
하지만 개인 사용자까지 지원하려면 상황이 달라진다.
개인 사용자는:
사용자
↓
개인 농장
↓
개인 가계부
를 사용해야 하기 때문이다.
즉, 단순히 파트너가 있느냐 없느냐만으로 데이터를 구분하는 것은 부족했다.
사용자가 현재 어떤 공간에서 서비스를 이용하고 있는지를 나타내는 개념이 필요했다.
그것이 바로 mode다.
PERSONAL
↓
개인 농장 / 개인 가계부 / 개인 목표
COUPLE
↓
커플 농장 / 커플 가계부 / 커플 목표
이렇게 현재 사용자가 어떤 모드에 있는지를 기준으로 데이터를 조회하고 저장하도록 구조를 변경하기 시작했다.
🔄 2. 개인/커플 모드는 무엇이 달라지는가?
이번에 가장 중요하게 생각한 부분은 단순히 화면의 제목만 바꾸는 것이 아니었다.
서비스의 주요 데이터 자체가 모드에 따라 분리되어야 한다.
현재 기준으로 다음 영역을 모두 개인/커플 모드로 구분한다.
| 기능 | 개인 모드 | 커플 모드 |
|---|---|---|
| 🌱 농장 | 개인 농장 | 커플 농장 |
| 💰 가계부 | 개인 가계부 | 커플 가계부 |
| 🌳 저축 목표 | 개인 목표 | 공동 목표 |
| 🏆 챌린지 | 개인 챌린지 | 커플 챌린지 |
| 📊 예산 | 개인 예산 | 커플 예산 |
| 🔁 고정지출 | 개인 고정지출 | 커플 고정지출 |
즉,
모드
↓
농장
가계부
저축
챌린지
예산
고정지출
전체가 같은 모드 기준으로 움직인다.
이 구조를 선택한 이유는 사용자가 현재 개인 모드인지 커플 모드인지에 따라 서비스 전체의 컨텍스트가 바뀌어야 하기 때문이다.
🏡 3. 개인 농장과 커플 농장은 다른 공간이다
BroBean에서 농장은 단순한 꾸미기 화면이 아니다.
가계부 기록과 저축 활동이 농장의 성장과 연결되어 있기 때문에 농장 자체가 사용자의 금융 활동을 보여주는 중요한 공간이다.
따라서 개인 모드에서는:
내 소비
+
내 저축
+
내 챌린지
+
내 활동
↓
내 농장
이 되고,
커플 모드에서는:
내 소비
+
파트너의 소비
+
공동 저축
+
커플 챌린지
+
두 사람의 활동
↓
우리 농장
이 된다.
따라서 모드 전환은 단순히 메뉴 하나를 바꾸는 기능이 아니라 사용자가 바라보는 농장 자체를 바꾸는 기능이라고 볼 수 있다.
💰 4. 가계부도 개인과 커플을 분리해야 한다
가장 직접적으로 영향을 받은 기능은 가계부다.
개인 모드에서는:
내가 오늘 사용한 10,000원
을 기록한다.
커플 모드에서는:
나의 지출
+
파트너의 지출
↓
우리의 가계부
라는 개념이 된다.
따라서 같은 expense 데이터를 사용하더라도 어떤 모드에서 작성되었는지를 구분할 수 있어야 한다.
개념적으로는 다음과 같은 구조가 된다.
Expense
├── personal
│ └── 개인 사용자의 지출
│
└── couple
└── 커플 공간의 지출
이렇게 해두면 사용자가 개인 모드에서 기록한 소비가 커플 가계부에 섞이는 문제를 방지할 수 있다.
반대로 커플 모드에서 기록한 소비는 파트너와 공유되는 공간의 데이터가 된다.
🌳 5. 저축 목표도 모드에 따라 달라진다
저축 목표 역시 개인과 커플을 나눠야 한다.
개인 모드에서는:
내 여행 자금
내 비상금
내 노트북 구매
처럼 개인적인 목표를 관리할 수 있다.
커플 모드에서는:
우리 여행 자금
신혼집 자금
데이트 비용
공동 목표
처럼 함께 달성해야 하는 목표를 관리할 수 있다.
즉, 같은 저축 기능이라도 목표의 주체가 달라진다.
PERSONAL
→ 나의 목표
COUPLE
→ 우리의 목표
이 차이를 서비스 전반에서 일관되게 유지하는 것이 중요했다.
🏆 6. 챌린지도 개인/커플로 분리
챌린지 역시 마찬가지다.
예를 들어 개인 모드에서는:
일주일 동안 배달 음식 줄이기
카페 지출 줄이기
매일 가계부 작성하기
같은 개인 챌린지를 진행할 수 있다.
커플 모드에서는:
이번 달 외식비 줄이기
함께 저축하기
일주일 동안 충동구매 하지 않기
처럼 두 사람이 함께 참여하는 챌린지를 만들 수 있다.
따라서 챌린지의 진행 상태 역시 현재 모드와 연결되어야 한다.
📊 7. 예산 설정도 모드에 따라 분리
예산은 특히 개인과 커플의 차이가 명확하다.
개인 모드:
이번 달 식비
300,000원
교통비
100,000원
쇼핑
150,000원
커플 모드:
이번 달 식비
500,000원
데이트
300,000원
생활비
700,000원
처럼 기준 자체가 달라질 수 있다.
따라서 사용자가 개인 모드로 전환했는데 커플 예산이 표시된다면 서비스 입장에서는 굉장히 큰 오류가 된다.
이런 문제를 방지하기 위해 예산 역시 현재 모드를 기준으로 데이터를 조회하는 방향으로 구조를 맞추고 있다.
🔁 8. 고정지출도 분리
고정지출도 개인과 커플을 구분해야 한다.
개인 모드에서는:
휴대폰 요금
구독 서비스
개인 보험
월세
등을 관리할 수 있다.
커플 모드에서는:
공동 관리비
공동 구독 서비스
월세
통신비
생활비
등을 관리할 수 있다.
특히 고정지출은 매달 자동으로 가계부 데이터에 영향을 줄 수 있는 영역이기 때문에 모드가 섞이면 데이터 정합성에도 영향을 줄 수 있다.
그래서 이 부분 역시 처음부터 모드 기준을 적용하는 방향으로 변경했다.
🔀 9. 그렇다면 모드는 어떻게 전환할까?
기능을 분리했다면 사용자가 쉽게 모드를 변경할 수 있어야 한다.
그래서 이번에는 헤더와 사이드 메뉴에 모드 전환 스위치를 추가했다.
헤더에서는 현재 모드를 빠르게 확인하고 전환할 수 있도록 하고,
사이드 메뉴에서도 같은 상태를 확인할 수 있도록 했다.
예를 들어:
┌─────────────────────┐
│ 🌱 BroBean │
│ │
│ 개인 모드 [ ●── ] │
│ │
├─────────────────────┤
│ 🏡 농장 │
│ 💰 가계부 │
│ 🌳 저축 │
│ 🏆 챌린지 │
│ 📊 예산 │
│ 🔁 고정지출 │
└─────────────────────┘
현재 모드가 개인 모드라면 모든 메뉴가 개인 데이터 기준으로 동작하고,
커플 모드로 변경하면 같은 메뉴가 커플 데이터 기준으로 동작한다.
즉, 메뉴는 동일하지만 데이터의 컨텍스트가 달라지는 구조다.
🧠 10. 모드 상태는 Pinia Store에서 관리
모드 전환 기능을 구현하면서 가장 중요했던 부분 중 하나가 상태 관리였다.
모드 정보가 여러 컴포넌트에 필요하기 때문이다.
예를 들어:
- Header
- Sidebar
- Farm
- Account Book
- Savings
- Challenge
- Budget
- Fixed Expense
등에서 현재 모드가 필요하다.
각 컴포넌트에서 따로 상태를 관리한다면 문제가 발생한다.
Header
→ personal
Sidebar
→ couple
Farm
→ personal
처럼 서로 다른 상태를 가지고 있을 수 있기 때문이다.
그래서 모드 상태는 전역 상태로 관리하기로 했다.
BroBean에서는 Pinia Store를 사용하고 있다.
개념적으로는 다음과 같은 형태다.
export const useModeStore = defineStore('mode', () => {
const mode = ref<'personal' | 'couple'>('personal')
const isPersonalMode = computed(() => {
return mode.value === 'personal'
})
const isCoupleMode = computed(() => {
return mode.value === 'couple'
})
function setMode(nextMode: 'personal' | 'couple') {
mode.value = nextMode
}
return {
mode,
isPersonalMode,
isCoupleMode,
setMode
}
})
이렇게 하나의 Store에서 현재 모드를 관리하면 각 화면에서는 동일한 상태를 사용할 수 있다.
🔄 11. Header와 Sidebar가 같은 상태를 바라보게 만들기
Header에서는:
const modeStore = useModeStore()
를 사용하고,
Sidebar에서도:
const modeStore = useModeStore()
를 사용한다.
그리고 현재 상태를 기준으로 UI를 표시한다.
<UButton
:variant="modeStore.isPersonalMode ? 'solid' : 'ghost'"
@click="modeStore.setMode('personal')"
>
개인
</UButton>
<UButton
:variant="modeStore.isCoupleMode ? 'solid' : 'ghost'"
@click="modeStore.setMode('couple')"
>
커플
</UButton>
이렇게 하면 Header에서 모드를 변경했을 때 Sidebar도 동일한 상태를 바라보기 때문에 즉시 변경된다.
반대로 Sidebar에서 변경해도 Header의 상태가 같이 변경된다.
Pinia Store
│
┌──────────┴──────────┐
↓ ↓
Header Sidebar
│ │
└──────────┬──────────┘
↓
현재 모드 상태
│
┌────────────┼────────────┐
↓ ↓ ↓
농장 가계부 예산
이런 구조를 사용하면 모드 전환 로직을 특정 UI에 종속시키지 않을 수 있다.
🧩 12. 컴포넌트에서는 모드만 알면 된다
또 하나 중요하게 생각한 부분은 각 컴포넌트가 모드 전환 자체를 직접 관리하지 않도록 하는 것이다.
예를 들어 농장 페이지에서:
const modeStore = useModeStore()
const currentMode = computed(() => modeStore.mode)
정도만 알고 있으면 된다.
농장 페이지가:
어디에서 모드가 변경되는지
누가 모드를 변경했는지
모드 상태를 어떻게 저장하는지
까지 알 필요는 없다.
Store가 상태를 담당하고 페이지는 현재 상태를 소비하는 구조로 분리한다.
이렇게 하면 나중에 모드 전환 UI가:
Header Switch
에서
Sidebar Switch
또는
Mobile Bottom Sheet
등으로 변경되더라도 Store 구조에는 영향을 최소화할 수 있다.
💾 13. 새로고침하면 모드가 사라지는 문제
Pinia Store에 상태를 넣으면서 또 하나의 문제가 생겼다.
브라우저를 새로고침하면 메모리에 있던 상태는 초기화된다.
예를 들어 사용자가:
커플 모드
로 사용하다가 새로고침하면:
개인 모드
로 돌아가 버릴 수 있다.
이건 실제 사용자 입장에서 상당히 불편하다.
특히 사용자가 평소에 커플 모드를 주로 사용한다면 페이지에 들어올 때마다 다시 모드를 변경해야 한다.
그래서 최종적으로는 사용자의 최근 모드를 서버에서도 기억하도록 하는 방향으로 설계하고 있다.
🗄️ 14. userSetting 테이블에 최근 모드 저장
BroBean에서는 사용자 설정을 관리하기 위한 userSetting 테이블을 사용하고 있다.
여기에 최근 사용한 모드 정보를 저장하는 방식이다.
개념적으로는:
User
↓
UserSetting
├── 알림 설정
├── 테마 설정
└── 최근 사용 모드
형태가 된다.
예를 들어:
type UserMode = 'personal' | 'couple'
그리고 userSetting에:
mode = "couple"
같은 값을 저장할 수 있다.
사용자가 커플 모드로 전환하면:
UI에서 모드 변경
↓
Pinia Store 변경
↓
userSetting 업데이트
↓
최근 사용 모드 저장
이라는 흐름으로 처리한다.
🔄 15. Pinia와 userSetting의 역할을 나누기
여기서 Pinia와 DB의 역할을 동일하게 생각하면 안 된다.
둘은 서로 다른 목적을 가진다.
Pinia Store
→ 현재 화면에서 사용할 상태
userSetting
→ 사용자의 설정을 영구적으로 저장
즉:
[앱 실행 중]
Pinia
↓
현재 모드 빠른 참조
[페이지 새로고침 / 재접속]
userSetting
↓
마지막 모드 복원
이라는 역할 분리가 된다.
이 구조가 좋은 이유는 UI에서 모드 상태를 사용할 때마다 서버를 조회할 필요가 없기 때문이다.
앱이 실행되고 사용자의 설정을 가져온 뒤에는 Pinia Store가 현재 상태를 가지고 있게 된다.
🚀 16. 초기 진입 시 최근 모드 복원
앞으로 최종적으로는 다음과 같은 흐름을 생각하고 있다.
사용자 로그인
↓
userSetting 조회
↓
최근 모드 확인
↓
Pinia Store 초기화
↓
Header / Sidebar 반영
↓
현재 모드에 맞는 데이터 조회
예를 들어 사용자가 마지막으로 커플 모드를 사용했다면:
userSetting
mode = couple
↓
Pinia
mode = couple
↓
화면
커플 모드
↓
커플 농장 / 가계부 / 저축 / 챌린지 / 예산
으로 자연스럽게 이어진다.
이렇게 하면 사용자는 매번 자신이 사용하던 모드를 다시 선택할 필요가 없다.
🎯 17. 모드 전환은 단순한 UI 기능이 아니다
처음에는 모드 전환 스위치를 하나 추가하면 끝나는 기능이라고 생각할 수도 있다.
하지만 실제로 구현해보니 그렇지 않았다.
모드라는 개념은 서비스의 여러 데이터와 연결된다.
Mode
│
┌──────────┼──────────┐
↓ ↓ ↓
Farm Account Savings
│ │ │
└──────────┼──────────┘
↓
Challenge
↓
Budget
↓
Fixed Expense
따라서 모드를 도입한다는 것은 사실상 서비스 전체에 데이터 컨텍스트를 하나 추가하는 작업에 가깝다.
이 부분을 초기에 제대로 정의하지 않으면 나중에 각 기능을 다시 수정해야 하는 상황이 생길 수 있다.
🧱 18. 데이터 조회 기준도 모드 중심으로 변경
앞으로 API나 Repository 계층에서도 현재 모드를 기준으로 데이터를 조회하게 된다.
예를 들어 가계부 데이터를 가져온다면:
const expenses = await getExpenses({
mode: modeStore.mode
})
처럼 사용할 수 있다.
저축 목표 역시:
const savings = await getSavingsGoals({
mode: modeStore.mode
})
예산 역시:
const budgets = await getBudgets({
mode: modeStore.mode
})
와 같이 동일한 기준을 사용할 수 있다.
이렇게 하면 각 기능이 서로 다른 방식으로 개인/커플을 처리하는 문제를 줄일 수 있다.
🔐 19. 클라이언트의 mode 값만 믿으면 안 된다
다만 여기서 중요한 부분이 하나 있다.
Pinia Store의 mode는 클라이언트 상태일 뿐이다.
따라서 서버에서는 절대로:
클라이언트가 personal이라고 보냈으니
personal 데이터겠지
라고 단순하게 신뢰하면 안 된다.
특히 커플 데이터는 다른 사용자의 데이터와 연결될 수 있기 때문에 서버에서 반드시 권한 검증이 필요하다.
개념적으로는:
Client
mode = couple
↓
Server
현재 사용자 확인
↓
커플 관계 확인
↓
해당 커플 데이터 접근 권한 확인
↓
데이터 반환
같은 흐름이 필요하다.
즉, mode는 사용자가 어떤 컨텍스트에서 서비스를 사용하고 있는지를 표현하는 값이고,
실제 데이터 접근 권한은 서버에서 별도로 검증해야 한다.
❤️ 20. 커플 모드가 없는 사용자도 개인 모드는 사용할 수 있어야 한다
이번 구조를 만들면서 또 하나 중요하게 생각한 부분이다.
모든 사용자가 반드시 커플 연결을 가지고 있는 것은 아니다.
따라서:
회원가입
↓
개인 모드
가 기본적으로 가능해야 한다.
그리고 이후 사용자가 파트너를 연결하면:
개인 모드
↓
파트너 연결
↓
커플 모드 사용 가능
으로 확장된다.
즉, 커플 기능은 개인 기능을 사용하는 데 필요한 조건이 아니라 추가로 선택할 수 있는 기능이 된다.
이 구조가 서비스의 사용자 범위를 넓히는 데도 훨씬 유리하다고 생각했다.
🌱 21. BroBean의 서비스 방향도 조금 달라졌다
처음 BroBean을 설계했을 때는:
커플이 함께 가계부를 관리하고 농장을 키운다.
라는 방향이 강했다.
하지만 개인/커플 모드를 도입하면서 서비스의 구조가 조금 더 확장됐다.
이제는:
혼자서도 자신의 농장을 키울 수 있고, 필요하면 파트너와 하나의 농장을 함께 가꿀 수 있다.
라는 방향으로 발전하게 되었다.
개인 사용자는:
나의 소비
나의 저축
나의 목표
나의 농장
을 관리하고,
커플 사용자는:
우리의 소비
우리의 저축
우리의 목표
우리의 농장
을 관리한다.
기능은 비슷하지만 서비스를 사용하는 단위가 다르다.
📱 22. 모바일에서는 모드 전환을 더 쉽게
BroBean은 모바일 사용을 중요하게 생각하고 있기 때문에 모드 전환 UI도 작은 화면에서 빠르게 접근할 수 있도록 구성했다.
헤더에서는 현재 모드를 한눈에 확인할 수 있도록 하고,
사이드 메뉴에서는 메뉴를 탐색하면서 모드를 바로 변경할 수 있도록 했다.
특히 중요한 것은 모드를 변경했을 때 단순히 스위치만 바뀌는 것이 아니라 현재 화면의 데이터 컨텍스트도 함께 변경되는 것이다.
예를 들어:
개인 모드
가계부
→ 개인 지출
커플 모드
가계부
→ 커플 지출
처럼 사용자가 모드 변경 결과를 바로 체감할 수 있어야 한다.
🧪 23. 모드 전환 테스트에서 확인해야 할 것
모드 기능을 추가하면서 단순히 스위치가 잘 바뀌는 것만 테스트해서는 부족하다.
최소한 다음과 같은 흐름을 확인해야 한다.
개인 → 커플
개인 모드
↓
커플 모드 전환
↓
커플 데이터 조회
↓
화면 변경
커플 → 개인
커플 모드
↓
개인 모드 전환
↓
개인 데이터 조회
↓
화면 변경
새로고침
커플 모드
↓
새로고침
↓
최근 모드 복원
↓
커플 모드 유지
파트너가 없는 사용자
개인 사용자
↓
개인 모드 사용
↓
커플 모드 접근
↓
파트너 연결 여부 확인
↓
필요한 경우 연결 안내
이런 흐름까지 확인해야 실제 서비스에서 모드가 섞이는 문제를 줄일 수 있다.
🧩 24. 앞으로 데이터 모델도 모드를 고려해야 한다
이번 작업은 프론트엔드 UI에서 시작했지만 실제로는 백엔드 데이터 모델에도 영향을 준다.
앞으로 각 데이터가 어떤 주체에 속해 있는지를 명확하게 해야 한다.
예:
Expense
→ 개인 사용자 데이터인가?
→ 커플 공간 데이터인가?
SavingsGoal
→ 개인 목표인가?
→ 커플 공동 목표인가?
Budget
→ 개인 예산인가?
→ 커플 예산인가?
이런 기준이 명확해야 한다.
결국 서비스 전체에서 다음과 같은 규칙을 가져갈 수 있다.
현재 mode
+
사용자 / 커플 권한
↓
데이터 접근 범위 결정
이 규칙을 기준으로 API와 DB 구조를 확장할 예정이다.
🚀 25. 다음 단계
이번 작업을 통해 BroBean은 단순히 커플 가계부에 머무르지 않고 개인 사용자까지 사용할 수 있는 구조로 한 단계 확장됐다.
현재는 먼저 사용자 인터페이스와 상태 관리 측면에서 개인/커플 모드를 분리하고 있다.
앞으로는 이 모드 정보를 실제 데이터 구조와 연결해야 한다.
특히:
- 🌱 개인/커플 농장 데이터 분리
- 💰 개인/커플 가계부 데이터 분리
- 🌳 개인/커플 저축 목표 분리
- 🏆 개인/커플 챌린지 분리
- 📊 개인/커플 예산 분리
- 🔁 개인/커플 고정지출 분리
- 🔐 커플 데이터 접근 권한 검증
- 💾
userSetting기반 최근 모드 저장 - 🔄 로그인 및 재접속 시 마지막 모드 복원
- 📱 모바일 모드 전환 UX 개선
등을 이어서 작업할 예정이다.
🎯 마무리
이번 기능을 구현하면서 가장 크게 느낀 것은 서비스에서 하나의 '모드'를 추가하는 일이 생각보다 큰 작업이라는 것이다.
처음에는 단순히:
[ 개인 ] [ 커플 ]
버튼 하나를 추가하면 된다고 생각할 수 있다.
하지만 실제로는:
모드
↓
농장
가계부
저축
챌린지
예산
고정지출
전체 데이터의 기준이 바뀐다.
그래서 이번에는 모드 상태를 각각의 컴포넌트에서 따로 관리하지 않고 Pinia Store를 통해 전역 상태로 관리하는 방향을 선택했다.
그리고 장기적으로는:
userSetting
↓
최근 사용 모드 저장
↓
로그인 / 재접속
↓
Pinia 초기화
↓
마지막 모드 복원
구조를 만들어 사용자가 이전에 사용하던 환경을 그대로 이어갈 수 있도록 할 예정이다.
결국 BroBean의 목표는 단순히 커플끼리 사용하는 가계부를 만드는 것이 아니다.
혼자서도 자신의 농장을 가꿀 수 있고, 필요할 때는 파트너와 하나의 농장을 함께 키워갈 수 있는 서비스를 만드는 것이다.
개인 모드와 커플 모드의 분리는 그 방향으로 가기 위한 중요한 구조적 기반이 되었다.
앞으로는 이 모드 정보를 실제 DB와 API에 연결하면서, 개인 데이터와 커플 데이터가 서로 섞이지 않고 각각의 컨텍스트에서 자연스럽게 동작하는 구조를 만들어갈 예정이다.
