Big Project · AI's EYE 24

Firebase 계정과 매장 자원: 독립된 생명 주기를 관리하는 법

Firebase 계정을 만들거나 삭제하면 그 사용자가 관리하던 매장 데이터도 자동으로 일관되게 바뀔 것 같다. 외부 인증 원본과 내부 매장 자원의 경계를 놓치기 쉽다.

근거 · 프로젝트 문서 · 코드 · 테스트

1장: 이중 원본, 이중 생명 주기

솔라가 노트북 화면의 한 문장에 시선을 고정한 채 미동도 하지 않았다. 시스템 아키텍처를 설명하는 내부 문서의 한 구절이었다. 잠시 생각에 잠겨 있던 솔라는 화면을 가리키며 옆에 있던 루나에게 물었다.

“언니, 이 문장 좀 이상하지 않아? ‘사용자 원본은 Firebase이고 PostgreSQL에는 매장 마스터, 설정, 메뉴·정책, 미디어와 분석 job이 저장된다.’ 이렇게 써 있어. 당연한 말 같은데, 왜 굳이 두 시스템을 나열한 거지? 마치 아무 관계없는 것처럼.”

루나는 솔라가 가리키는 문장을 조용히 읽어보았다.

“어떻게 당연하다고 생각했는데?”

“음, 이런 거 아닐까? 점주가 우리 서비스에 가입하면 Firebase로 계정을 만들고, 그럼 우리 데이터베이스에 그 점주의 매장 정보가 생기는 거잖아. 계정을 삭제하면 매장 정보도 같이 사라지고. 하나의 덩어리로 움직이는 거니까. 그런데 이 문장은 꼭 사용자 계정이랑 매장 데이터가 완전히 다른 세상에 사는 것처럼 말하고 있잖아.”

솔라의 말에는 ‘계정 생성과 매장 데이터 생성은 하나의 묶음’이라는 암묵적인 가정이 깔려 있었다. 계정의 생명 주기가 곧 그 계정이 소유한 모든 데이터의 생명 주기와 같을 것이라는, 지극히 자연스러운 생각이었다. 많은 서비스들이 실제로 그렇게 작동하는 것처럼 보이기 때문이다.

루나는 고개를 끄덕이며 솔라의 말을 경청했다. 그리고는 빈 종이 한 장과 펜을 가져와 테이블 위에 놓았다. 정답을 말해주는 대신, 다른 질문을 던졌다.

“그럼 우리, 각 정보가 사는 ‘집’을 한번 그려볼까? 어떤 정보가 진짜 주인인지, 그 원본이 어디인지 표시해보는 거야.”

루나는 종이 위에 커다란 사각형 두 개를 그리고 각각 ‘Firebase’와 ‘PostgreSQL’이라고 이름 붙였다.

“자, 솔라. 새로운 점주가 우리 서비스에 가입했어. 그 사람의 이메일 주소와 비밀번호는 어느 집에 저장될까? ‘이 사람이 정말 그 사람이 맞나?‘를 증명해주는 정보의 주인은 누구지?”

“그건 당연히 Firebase지.”

솔라는 망설임 없이 ‘Firebase’ 상자 안에 ‘사용자 계정 (이메일, 비밀번호)‘이라고 적었다. 사용자 인증은 Firebase의 핵심 역할이니까.

“좋아. 그럼 그 점주가 운영하는 매장의 이름, 주소, 메뉴판 이미지, 영업시간 같은 정보는 어디에 살까?”

“그건 우리 서비스의 핵심 데이터니까… PostgreSQL이지.”

솔라는 ‘PostgreSQL’ 상자 안에 ‘매장 정보 (이름, 주소)’, ‘미디어 (메뉴판 이미지)’, ‘운영 정책’ 등을 차례로 적어 넣었다. 손으로 직접 정보를 분류하며 적어 내려가자, 처음 문장을 읽었을 때와는 다른 감각이 느껴졌다.

루나가 결정적인 질문을 던졌다.

“여기까지는 명확하네. 그럼 Firebase 상자에 ‘사용자 계정’을 하나 만든다고 해서, PostgreSQL 상자에 ‘매장 정보’가 저절로 생겨날까? Firebase가 ‘매장’이라는 개념을 알고 있을까?”

“어…?”

솔라의 펜이 허공에서 멈췄다. 저절로 생겨나지 않을까? 하지만 루나의 질문을 곱씹어보니 무언가 이상했다. Firebase는 범용 인증 서비스다. 그들에게 ‘사용자’는 있지만, ‘솔루션 R의 매장 점주’라는 특수한 개념은 존재하지 않는다. 그건 전적으로 ‘우리’ 시스템의 이야기였다.

“아니… Firebase는 그냥 이메일과 비밀번호가 유효한 사용자인지만 알려주겠지. 그 사용자가 점주인지, 어떤 매장을 가졌는지는 전혀 모를 거야. 그건 우리 쪽 PostgreSQL 데이터베이스에 ‘Firebase 사용자 ID 123번은 A매장의 주인이다’라고 기록해야만 알 수 있는 거고.”

그 말을 내뱉는 순간, 솔라는 자신이 처음 가졌던 생각이 틀렸음을 깨달았다. 두 시스템은 자동으로 일관성을 유지하는 하나의 덩어리가 아니었다. 오히려 각자의 역할에만 충실한, 독립된 두 개의 왕국에 가까웠다. 사용자 계정을 만드는 행위와 매장 정보를 생성하는 행위는 별개의 작업이었다. 우리 시스템의 애플리케이션 코드가 그 둘을 연결해주는 다리 역할을 할 뿐이었다.

솔라는 자신이 그린 두 개의 상자를 내려다보았다. 이제는 그저 기술 스택을 나열한 목록이 아니었다. ‘신원 인증의 원본’과 ‘도메인 데이터의 원본’이라는, 명확한 책임의 경계선이 보였다. ‘사용자 원본은 Firebase이고…’ 라는 문장이 왜 그렇게 쓰였는지 비로소 이해가 되었다. 그것은 두 시스템이 별개의 생명 주기를 가진다는 선언이었던 것이다.

“그렇구나. 두 시스템은 서로를 몰라. 우리는 그냥 Firebase가 발급해준 신분증을 가진 사람을 확인하고, 우리 시스템 안에서 그 사람에게 매장을 할당해주는 거였어. 태어나는 곳이 다르니, 죽는 곳도 다르겠네.”

솔라가 중얼거렸다. 처음의 혼란은 사라지고 새로운 의문이 고개를 들었다. 명확해진 경계선은 동시에 새로운 질문을 만들어냈다.

“그런데 언니, 그럼 더 복잡해지는 거 아니야? 만약 점주가 탈퇴해서 Firebase 계정을 삭제하면, 우리 PostgreSQL에 남겨진 그 사람의 매장 데이터는 어떻게 되는 거지? 누가 정리해주는 거야? 그냥 유령 데이터로 영원히 떠도는 건가?“

2장: 정체성의 고유한 춤: 계정 라이프사이클

솔라와 루나 사이의 테이블 위에는 어제 그렸던 그림이 그대로 놓여 있었다. ‘Firebase’와 ‘PostgreSQL’이라는 두 개의 커다란 사각형. 솔라는 이제 그 그림을 다른 눈으로 볼 수 있었다. 각자의 집에서 태어나고 관리되는, 두 개의 독립된 세계였다.

루나는 말없이 펜을 들어, Firebase 상자에서 시작해 PostgreSQL 상자로 향하는 화살표 하나를 허공에 그렸다. 그러다 이내 화살표 끝에 물음표를 그려 넣고는 멈췄다.

“이제 각자 어디에 사는지, 그 원본이 어디인지는 알았어. 그럼, 이 둘은 어떻게 대화할까? 특히 Firebase 왕국에서 중대한 사건, 예를 들어 왕의 신분증이 바뀌는 일이 생겼을 때 말이야. PostgreSQL 왕국은 그 사실을 어떻게 알아챌까?”

루나는 솔라가 보던 아키텍처 문서의 다른 섹션을 가리켰다. ‘관리 흐름’이라는 제목 아래, 건조한 문장 하나가 적혀 있었다.

“이 문장이 그 힌트가 될지도 몰라.”

솔라는 루나의 손끝을 따라 문장을 소리 내어 읽었다.

“작성자는 점주 계정 생성·수정, custom claims 부여와 비밀번호 변경 뒤 refresh token 폐기를 처리하는 관리 흐름을 만들었다…”

솔라는 고개를 갸웃했다.

“생성, 수정… 이건 당연한 관리자 기능이고. 권한을 부여하는 것도 그렇고. 그런데 비밀번호를 바꾼 다음에 ‘refresh token을 폐기한다’? 이건 그냥 세부적인 구현 내용 아닌가? 굳이 이렇게 중요한 관리 흐름에 명시할 정도의 일이야? 그냥 기술적인 뒷정리 작업처럼 보이는데.”

솔라의 눈에는 계정 정보 수정은 단순한 데이터 업데이트 작업으로, refresh token 폐기는 그저 따라오는 부수적인 절차로 보였다. 중요한 정보가 바뀐 뒤에 낡은 것을 치우는, 당연한 청소 같은 것.

루나는 솔라의 말을 듣고는 잠시 생각하더니, 새로운 질문을 던졌다. 마치 다른 놀이를 제안하듯이.

“좋아, 그럼 상황극을 한번 해보자. 솔라, 네가 지금 우리 가게 관리 앱에 로그인해서 쓰고 있다고 생각해 봐. 하나는 네 노트북에, 또 하나는 네 스마트폰에 로그인되어 있어.”

“응, 상상했어.”

“그런데 네가 노트북으로 비밀번호를 바꿨어. 해킹이 의심되는 상황이라 급하게 바꾼 거야. 자, 그럼 네 주머니 속에 있는 스마트폰은 어떻게 될까? 그 폰으로 여전히 매장 영업시간을 바꿀 수 있을까?”

솔라는 잠시 고민에 빠졌다. “음… 내 폰은 내가 방금 노트북에서 비밀번호를 바꾼 사실을 모르잖아. 그러니까… 앱을 완전히 껐다 켜거나 로그아웃하기 전까지는 계속 쓸 수 있지 않을까?”

“바로 그거야.”

루나는 말을 이었다.

“스마트폰 앱은 일종의 ‘장기 출입증’을 가지고 있어. 그게 바로 refresh token이지. 비밀번호를 직접 쓰는 대신, 이 출입증만 보여주면 시스템은 계속 새로운 ‘일일 출입증(access token)‘을 발급해 줘. 그런데 우리가 비밀번호를 바꿨을 때, 이 ‘장기 출입증’을 그냥 내버려 둔다면 어떻게 될까?”

그 순간, 솔라의 눈이 커졌다. 머릿속에서 퍼즐 조각들이 맞춰지는 소리가 들리는 듯했다.

“아! 그럼 비밀번호를 바꾼 게 아무 소용이 없잖아! 만약 누군가 내 스마트폰 세션을 탈취했다면, 내가 아무리 노트북에서 비밀번호를 바꿔도 그 사람은 계속 내 계정으로 활동할 수 있겠네. 출입증이 여전히 유효하니까!”

그제야 솔라는 아까 무심코 읽었던 문장의 무게를 깨달았다. ‘비밀번호 변경 뒤 refresh token 폐기’는 단순한 뒷정리가 아니었다. 그것은 이전에 발급했던 모든 ‘장기 출입증’을 강제로 무효화하는, 대단히 중요한 보안 조치였다. ‘나’라는 정체성의 핵심 정보가 바뀌었음을 시스템 전체에 알리고, 과거의 모든 인증 상태를 파기하는 선언적 의식이었던 것이다. Firebase 왕국의 왕이 바뀌었으니, 이전 왕이 발급한 모든 옥새를 폐기하는 것과 같았다.

“그랬구나… 계정 정보 ‘수정’은 단순히 데이터베이스의 한 줄을 바꾸는 게 아니었어. 그 변경이 인증 상태에 미치는 영향을 추적해서, 낡은 신뢰를 명시적으로 ‘무효화’하는 것까지가 한 세트였던 거야.”

솔라는 이제 계정의 생명 주기를 관리한다는 것이 무엇을 의미하는지 어렴풋이 알 것 같았다. 그것은 변화에 따른 파급 효과를 끝까지 책임지는 일이었다. 하지만 이 깨달음은 곧바로 그녀를 처음의 질문으로 되돌려 보냈다. 수정이 이토록 복잡하다면, 삭제는 오죽할까.

“알겠어, 언니. 계정 정보가 ‘바뀌었을’ 때, 이전 인증 상태를 어떻게 무효로 만드는지는 이제 이해했어. 이건 신분증 자체를 지키는 문제니까. 그런데… 계정, 그러니까 신분증 자체가 ‘사라졌을’ 때는? Firebase에서 사용자를 삭제하는 것만으로는 저쪽 PostgreSQL에 있는 매장은 그대로 남아있을 거잖아. 그건 또 다른 문제 같은데… 구체적으로 뭘 더 해야 하는 거지?“

3장: 도메인의 정리 춤: 매장 자원 라이프사이클

솔라의 질문이 채 가시기도 전에, 루나는 테이블 위에 놓인 그림으로 다시 손을 뻗었다. 어제 솔라가 직접 그린 ‘Firebase’와 ‘PostgreSQL’이라는 두 개의 왕국. 루나는 빨간 펜을 들어 PostgreSQL 상자 옆에 작은 위성 같은 상자 두 개를 더 그렸다. 그리고 각각 ‘미디어 저장소’와 ‘분석 Job 큐’라고 적었다. 곧이어 PostgreSQL 상자에서부터 새로 생긴 두 상자로 희미한 점선이 이어졌다.

이 작은 행동이 그림의 전체적인 균형을 바꾸어 놓았다. PostgreSQL 왕국은 더 이상 하나의 단일체가 아니었다. 마치 본성에 딸린 별채와 지하 창고가 드러난 것 같았다. 솔라는 미간을 찌푸렸다. 단순했던 그림이 갑자기 복잡하게 느껴졌다. 저 별채와 창고는 왕국이 사라질 때 함께 무너지는 걸까, 아니면 덩그러니 남겨지는 걸까.

“언니, 이건…?”

“매장 정보가 사는 PostgreSQL 왕국이 실제로는 다른 곳에도 영향을 미친다는 뜻이야. 메뉴판 이미지는 파일 저장소에, 매출 분석을 위한 계산 작업들은 별도의 작업 큐에 쌓이게 되지.”

루나의 말에 솔라는 이전의 질문이 얼마나 순진했는지 깨달았다. 그녀는 단지 Firebase 계정과 PostgreSQL의 매장 데이터, 두 가지만을 생각하고 있었다. 하지만 현실은 그보다 더 많은 것들이 얽혀 있었다.

“그럼 문제는 더 심각해지네. 점주가 탈퇴해서 Firebase 계정을 삭제하는 것만으로는 어림도 없겠어. PostgreSQL의 매장 데이터도 그대로고, 저기 저 미디어 파일이나 분석 작업들도 전부 고아가 되어버릴 테니까. 이걸 다 어떻게 정리해야 해?”

솔라의 목소리에는 막막함이 묻어났다. 마치 청소해야 할 방이 하나가 아니라, 비밀 통로로 이어진 여러 개의 방이었다는 걸 뒤늦게 알게 된 사람 같았다. 그녀는 ‘Firebase 계정을 삭제하면 모든 게 자동으로 정리될 것’이라는 처음의 생각이 얼마나 틀렸는지 다시 한번 실감했다.

루나는 문서의 다른 구절을 손가락으로 가리켰다.

“바로 그 지점에서 이 문장이 길잡이가 되어줘. 이건 단순한 설명이 아니라, 우리가 따라야 할 임무 목록 같은 거야.”

솔라는 루나가 가리킨 문장을 읽었다. 매장 삭제는 Firebase 사용자 한 건만 지우는 것으로 끝나지 않고 해당 매장의 미디어와 분석 job 같은 운영 자원을 정리한다.

이전에는 그저 스쳐 지나갔던 문장이었다. 하지만 새로 그려진 그림과 함께 보니, 그 의미가 완전히 다르게 다가왔다. ‘끝나지 않고’, ‘정리한다’. 이것은 누군가가 명시적으로 수행해야 할 책임의 목록이었다.

“자, 그럼 우리가 직접 이 정리 작업을 설계해 보자. ‘매장 삭제’라는 무도회의 안무를 짜보는 거야.”

루나가 새로운 종이를 내밀었다. 솔라는 잠시 망설이다 펜을 들었다.

“음, 일단… 순서가 중요할 것 같아. 제일 먼저 뭘 해야 하지? Firebase 계정을 지우는 게 먼저일까?”

“만약 Firebase 계정 삭제에 실패하면 어떻게 될까? 이미 매장 데이터는 다 지워졌는데, 계정만 덩그러니 남는 상황이 생길 수도 있겠네.”

루나의 지적에 솔라는 고개를 저었다. “안 돼. 그건 끔찍하지. 그럼 반대로, 우리 쪽 데이터부터 정리해야겠다.”

솔라는 종이 위에 첫 번째 단계를 적었다.

1. 'A 매장' 삭제 요청 접수

“좋아. 그럼 그 다음은?”

“‘A 매장’에 딸린 자원들을 찾아야 해. PostgreSQL 데이터베이스에서 A 매장 정보를 조회해서… 아, 거기에 메뉴판 이미지 파일의 주소나 분석 작업의 ID가 저장되어 있겠구나.”

솔라의 손이 바쁘게 움직였다.

2. PostgreSQL에서 'A 매장' 정보를 찾아, 관련된 미디어 파일 목록과 분석 Job 목록을 확보한다. 3. '미디어 저장소'로 가서 해당 파일들을 삭제한다. 4. '분석 Job 큐'로 가서 해당 Job들을 취소하거나 제거한다.

여기까지 쓰고 나자, 솔라는 비로소 핵심에 다가갔음을 느꼈다. 흩어져 있던 위성들을 먼저 정리해야 본체를 안전하게 제거할 수 있었다.

5. 모든 종속 자원 정리가 끝나면, PostgreSQL에서 'A 매장' 데이터를 삭제한다. 6. 마지막으로, Firebase에서 'A 매장' 점주의 사용자 계정을 삭제한다.

완성된 순서도를 본 솔라는 긴 한숨을 내쉬었다. ‘매장 삭제’라는 간단해 보이는 기능 뒤에 이렇게 정교한 단계들이 숨어 있었다니. 이것은 절대 저절로 일어나는 일이 아니었다. 시스템 어딘가에 이 모든 단계를 순서대로, 그리고 실패했을 때의 처리까지 책임지는 ‘정리 담당자’가 코드로 명확하게 구현되어 있어야만 했다.

“그렇구나. ‘매장 삭제는… 운영 자원을 정리한다’는 문장은, 이런 복잡한 정리 절차를 모두 책임져야 한다는 뜻이었어. 각 시스템의 경계를 넘나들면서 고아가 될 수 있는 모든 데이터를 찾아내 뒷정리하는, 아주 적극적인 행위였던 거야.”

솔라는 이제 시스템을 볼 때, 단순히 기능의 목록이 아니라 각 데이터가 태어나고, 살아가고, 죽는 전체 생명 주기를 상상하게 되었다. 그리고 그 죽음을 책임지는 명시적인 역할이 반드시 필요하다는 것을 깨달았다.

자신이 만든 정리 순서도에 만족한 솔라는 문득 새로운 궁금증이 생겼다. 그녀는 PostgreSQL 상자를 톡톡 두드리며 말했다.

“좋아, 당장 운영에 필요한 매장 정보, 메뉴판 이미지, 분석 작업 같은 것들은 이렇게 정리하면 되겠어. 그런데… 이 데이터들 말고, 과거의 기록들은 어떻게 되는 거지? 예를 들어 작년 이맘때의 매장 영업 상태 기록(StoreState)이나, 지금까지의 모든 주문 내역(OrderEvent) 같은 것들 말이야. 이것들도 매장을 삭제할 때 같이 지워야 하는 걸까? 왠지 그러면 안 될 것 같은데…”

4장: 기록의 독립성: 과거 데이터 보존 정책

테이블 위에는 솔라가 전날 밤늦게까지 그린 ‘매장 삭제의 안무’ 순서도가 펼쳐져 있었다. 여러 개의 상자와 화살표로 이루어진 그 그림은 이제 솔라에게 꽤나 익숙했다. 종속된 자원들을 먼저 정리하고, 본체를 삭제한 뒤, 마지막으로 외부 시스템의 계정을 정리하는 명쾌한 흐름. 솔라는 자신의 그림을 만족스럽게 바라보다, 문득 펜을 다시 집어 들었다. 다른 색의 펜이었다.

‘어제의 그 질문도 이 그림에 넣을 수 있을까?’

솔라는 그림의 빈 공간에 StoreState, OrderEvent라고 작게 적었다. 그리고 잠시 고민했다. 이 ‘과거 기록’들을 어디에 연결해야 할까. 메뉴판 이미지나 분석 Job처럼, 매장에 종속된 자원이니 함께 삭제하는 것이 맞을까? 그녀는 펜 끝을 ‘미디어 파일 삭제’ 단계 근처에 가져다 댔다. 하지만 무언가 강한 위화감이 들어 차마 선을 긋지 못했다. 작년의 주문 기록이, 6개월 전의 영업 상태 기록이, 단순히 매장이 사라졌다는 이유만으로 함께 지워져도 되는 걸까? 그것은 마치 한 사람의 출생증명서와 졸업앨범을, 그 사람이 이사 갔다는 이유로 모두 불태워버리는 것처럼 느껴졌다.

솔라가 긋다 만 화살표 앞에서 끙끙거리는 것을 보고 있던 루나가 조용히 입을 열었다.

“그 기록들은 그 안무에 어울리는 춤꾼이 아닐지도 몰라.”

루나는 솔라의 순서도를 가리키며 말했다.

“그건 ‘현재 운영 중인 것들’을 무대에서 퇴장시키는 안무잖아. 그런데 방금 솔라 네가 말한 StoreStateOrderEvent는 이미 공연이 끝나고 관객석에 앉아 있는 기록들이 아닐까?”

“관객석에 앉아있는 기록?”

“응. 그 데이터의 목적을 생각해 봐. ‘A 매장의 현재 메뉴판’은 매장이 운영되는 지금 이 순간에 의미가 있지. 하지만 ‘작년 5월 5일 A 매장의 주문 내역’은 어때? 그건 매장이 지금 있든 없든, ‘작년 5월 5일에 그런 일이 있었다’는 과거의 사실을 증명하는 기록이야. 목적이 달라.”

루나의 말에 솔라는 자신이 왜 선을 긋기를 주저했는지 깨달았다. 그녀는 본능적으로 데이터의 성격이 다르다는 것을 느끼고 있었던 것이다. 하나는 ‘현재 상태’를 기술하는 운영 데이터이고, 다른 하나는 ‘과거의 사건’을 기록하는 역사 데이터였다.

문서의 한 구절이 솔라의 머릿속을 스쳐 지나갔다. 반면 과거 StoreState와 OrderEvent를 어떻게 보존할지는 현재 운영 목록 삭제와 별도의 정책이다.

이전에는 그저 부연 설명처럼 느껴졌던 ‘별도의 정책’이라는 말이, 이제는 전혀 다른 무게로 다가왔다. 그것은 단순히 예외 조항이 아니라, 완전히 다른 원칙과 철학이 적용된다는 선언이었다.

“그럼… 퇴장시키는 안무 말고, 기록을 어떻게 다룰지 결정하는 새로운 의사결정 흐름이 필요한 거네.”

솔라는 새 종이를 꺼내 들었다. 이번에는 순서도가 아니라, 갈림길이 있는 의사결정 흐름도를 그리기 시작했다.

“‘매장 삭제’ 요청이 들어왔을 때, 우리는 모든 데이터에게 똑같은 질문을 던지면 안 돼. 질문 자체가 달라야 해.”

솔라는 첫 번째 갈림길을 그렸다.

질문 1: 이 데이터는 현재 시스템 운영을 위해 존재하는가? (예: 매장 마스터 정보, 미디어 파일, 분석 Job)

  • 예 (Yes): → ‘매장 삭제 안무’(이전 순서도)에 따라 관련된 모든 것을 정리하고 삭제한다.
  • 아니오 (No): → 다음 질문으로 넘어간다.

그리고 이어서 두 번째 갈림길을 만들었다.

질문 2: 이 데이터는 과거에 발생한 사실이나 사건에 대한 기록인가? (예: 과거 주문 내역, 매장 상태 변경 이력)

  • 예 (Yes): → 다음 질문으로 넘어간다.
  • 아니오 (No): → (이 경우는 거의 없겠지만) 성격이 불분명한 데이터, 별도 검토.

마지막으로 가장 중요한, 정책적 판단이 들어가는 세 번째 갈림길을 그렸다.

질문 3: 이 기록을 법적, 혹은 사업적 이유(분쟁 해결, 통계 분석 등)로 보존해야 할 의무가 있는가?

  • 예 (Yes):보존한다. (단, ‘데이터 보존 정책’에 따라 특정 기간 후 파기하거나 익명화할 수 있다.)
  • 아니오 (No): → 삭제할 수 있다.

완성된 흐름도를 본 솔라는 깊은 숨을 내쉬었다. 이것이었다. ‘매장 삭제’라는 하나의 이벤트가 두 개의 완전히 다른 파이프라인을 촉발시키는 구조. 어떤 데이터는 ‘정리 담당자’에게 보내져 깨끗하게 청소되고, 어떤 데이터는 ‘기록 보관 담당자’에게 보내져 안전하게 보관되는 것이었다.

‘별도의 정책’이라는 말의 의미는, 매장을 삭제할 때 StoreStateOrderEvent는 아예 첫 번째 질문에서 ‘아니오’로 빠져나와 전혀 다른 길을 간다는 뜻이었다. 두 데이터 그룹은 서로 다른 운명을 타고난 것이다.

“이제 알겠어. ‘매장 삭제’는 단순히 데이터를 지우는 행위가 아니라, 각 데이터의 성격을 판별해서 어떤 생명 주기 정책을 적용할지 결정하는 분류 작업이었구나. 운영 데이터와 기록 데이터는 보존 주기가 다르니까, 애초에 같은 줄에 세우면 안 되는 거였어.”

솔라는 자신이 그린 두 개의 그림, ‘삭제의 안무’ 순서도와 ‘보존의 의사결정’ 흐름도를 나란히 놓았다. 이제야 시스템의 전체 그림이 보이는 듯했다. 생성, 수정, 삭제, 그리고 보존까지. 각 단계는 서로 다른 원칙과 책임을 요구했다.

하지만 이내 새로운 불안감이 고개를 들었다. 완벽해 보이는 두 개의 설계도. 그러나 이 복잡한 흐름들이 과연 실제 코드에서 한 치의 오차도 없이 구현되었을까? 매장을 삭제하라는 명령을 내렸을 때, 시스템이 정말로 이 흐름도대로 움직여서 보존해야 할 것은 남기고 삭제해야 할 것만 정확히 골라 지운다고 어떻게 확신할 수 있을까?

“언니, 그런데 이 모든 규칙들이… 너무 복잡하고 섬세해서 불안해. 마치 유리로 만든 기계 같아. 이 모든 게 실제로 문제없이 돌아가고 있다는 건 어떻게 증명할 수 있는 거야?”

5장: 경계의 검증: 테스트와 보증

테이블 위에는 솔라가 어제 그린 두 개의 설계도가 나란히 놓여 있었다. 하나는 ‘매장 삭제의 안무’를 담은 복잡한 순서도, 다른 하나는 ‘기록 보존의 의사결정’을 나타낸 갈림길 흐름도였다. 완벽해 보이는 설계도였지만, 솔라의 마음은 오히려 무거웠다. 그녀는 펜으로 순서도 위 화살표들을 하나하나 따라가며, 각 연결 지점에서 손가락을 멈췄다. PostgreSQL에서 미디어 저장소로, 다시 분석 Job 큐로, 그리고 마지막에 Firebase로 이어지는 섬세한 연결선들. 마치 아슬아슬하게 균형을 잡고 선 유리 조각들을 보는 듯했다. 이 정교한 규칙들이 과연 현실에서 한 치의 오차도 없이 작동할까.

솔라의 불안한 손끝을 지켜보던 루나가 시스템 아키텍처 문서의 마지막 장, ‘테스트’ 섹션을 펼쳐 솔라 앞에 놓아주었다. 그곳에는 간결한 몇 개의 항목이 나열되어 있었다.

“그 유리 조각들이 깨지지 않는다는 걸 보증해주는 서명들이야.”

솔라는 루나가 가리키는 문장을 소리 내어 읽었다.

테스트는 점주 계정 삭제가 admin 계정을 지우지 않는지, 비밀번호 변경 뒤 token 폐기가 호출되는지와 자원 삭제 경계를 확인한다.

문장을 다 읽은 솔라의 표정은 개운치 않았다.

“음… 테스트 코드가 있다는 건 알겠는데… 이게 정말로 그 복잡한 흐름 전체를 보증해주는 거야? ‘admin 계정을 지우지 않는지 확인한다’거나, ‘token 폐기가 호출되는지 확인한다’ 같은 건 너무 지엽적인 확인 사항처럼 들려. 우리가 그린 이 거대한 설계도에 비하면 그냥 작은 점 몇 개를 확인하는 것뿐이잖아.”

솔라에게 테스트 항목들은 그저 개별 기능이 잘 작동하는지 확인하는 체크리스트에 불과했다. 독립된 왕국들의 외교 관계와 국경 수비라는 거대한 그림을 증명하기에는 너무 작고 사소해 보였다.

루나는 고개를 끄덕이는 대신, 솔라가 그린 두 개의 설계도를 손가락으로 톡톡 두드렸다.

“좋아. 그럼 반대로 해보자. 우리가 직접 보증서에 서명하는 거야. 솔라 네가 그린 이 설계도에서, 여기가 무너지면 모든 게 끝장이라고 생각하는 가장 약한 연결 고리는 어디야? 그 지점 하나를 골라서, 우리가 직접 테스트 시나리오를 써보는 거지.”

루나의 말에 솔라는 잠시 생각에 잠겼다. 그리고는 펜을 들었다. 그녀는 가장 먼저 계정의 정체성이 흔들렸던 순간을 떠올렸다.

“첫 번째는 이거야. 비밀번호가 바뀌었을 때, 낡은 신분증이 계속 사용되면 안 돼.”

솔라는 새 종이에 첫 번째 시나리오를 적기 시작했다. 마치 자신이 시스템의 품질을 책임지는 검증자가 된 것처럼.

시나리오 1: 인증 무효화 경계 검증

  • 상황: 한 점주가 노트북과 스마트폰, 두 기기에서 로그인한 상태.
  • 사건: 점주가 노트북에서 비밀번호를 변경한다.
  • 검증: 스마트폰에 있던 기존 로그인 세션은 즉시 무효화되어, 더 이상 매장 정보를 수정할 수 없어야 한다.

시나리오를 완성한 솔라는 문서의 테스트 항목을 다시 보았다. ‘비밀번호 변경 뒤 token 폐기가 호출되는지 확인한다’. 아! 그 건조한 한 줄이 바로 이 시나리오를 보증하는 핵심적인 서명이었던 것이다.

다음으로 솔라는 고아가 될 뻔했던 데이터들을 떠올리며 ‘매장 삭제의 안무’ 순서도를 바라보았다.

시나리오 2: 자원 정리 경계 검증

  • 상황: ‘솔라네 빵집’ 매장은 여러 개의 메뉴판 이미지 파일과 진행 중인 매출 분석 Job을 가지고 있다.
  • 사건: 관리자가 ‘솔라네 빵집’ 매장 삭제를 요청한다.
  • 검증:
    1. 연관된 모든 메뉴판 이미지 파일이 미디어 저장소에서 완전히 삭제되어야 한다.
    2. 진행 중이던 매출 분석 Job이 큐에서 제거되어야 한다.
    3. 이 모든 정리가 끝난 후에야 PostgreSQL의 매장 정보와 Firebase의 계정이 삭제되어야 한다.

“이게 바로… ‘자원 삭제 경계를 확인한다’는 말의 진짜 의미였구나.”

마지막으로 솔라는 문서에만 있던 테스트 항목 하나를 곰곰이 생각했다. ‘점주 계정 삭제가 admin 계정을 지우지 않는지’. 처음에는 엉뚱한 테스트라고 생각했지만, 이제는 그 의미를 알 것 같았다. 그것은 삭제라는 행위의 영향 범위를 명확히 제한하는, 가장 중요한 경계선 중 하나였다.

시나리오 3: 책임 범위 경계 검증

  • 상황: 시스템에는 여러 점주 계정과 하나의 슈퍼 관리자(admin) 계정이 있다.
  • 사건: 점주 A의 계정을 삭제한다.
  • 검증: 점주 A와 관련된 매장 자원만 삭제될 뿐, 슈퍼 관리자 계정에는 아무런 영향이 없어야 한다.

자신이 직접 쓴 세 개의 시나리오를 내려다보는 솔라의 눈빛이 달라져 있었다. 이것은 단순한 기능 테스트 목록이 아니었다. 이중 원본, 계정의 생명 주기, 종속 자원의 정리, 데이터 보존 정책. 지금까지 논의했던 모든 복잡한 원칙들이 무너지지 않도록 지탱하는 강철 기둥들이었다. 그녀가 불안하게 바라보던 ‘유리 조각 사이의 연결부’를 정확하게 겨누고 있는 정밀한 보증 장치였다.

“알겠다… 이 테스트들은 시스템의 수많은 기능 중 몇 개를 무작위로 뽑아서 확인하는 게 아니었어. 우리가 그토록 고민했던 각 시스템 간의 ‘경계’가 실제로 코드 레벨에서 지켜지고 있다는 가장 확실한 증거였구나. 설계도가 청사진이라면, 이 테스트 시나리오는 그 청사진대로 지어졌음을 증명하는 준공 검사 보고서 같은 거였어.”

불안감은 사라지고 그 자리에 단단한 확신이 들어섰다. 이제 솔라는 이 시스템이 어떻게 살아 숨 쉬고, 어떻게 질서정연하게 사라지는지, 그리고 그것을 어떻게 증명하는지 모두 이해했다.

루나가 미소 지으며 마지막 질문처럼 물었다. “만약 우리가 여기에 ‘고객 멤버십 포인트’라는 전혀 다른 시스템을 새로 연결해야 한다면, 제일 먼저 뭘 할 거야?”

솔라는 더 이상 망설이지 않았다. 그녀는 깨끗한 종이를 한 장 더 꺼내 들고, 익숙하게 펜을 쥐었다. 그리고는 거창한 아키텍처 다이어그램 대신, 명료한 목록을 적어 내려가기 시작했다.


신규 ‘멤버십 포인트’ 시스템 연동 명세 (초안)

  1. 원본 정의:
    • 포인트 잔액의 원본은 어디에 둘 것인가?
  2. 생명 주기 정책:
    • 생성/수정: 사용자 정보(이름, 등급) 변경 시 포인트 시스템에 어떻게 반영할 것인가?
    • 삭제: 사용자 계정 탈퇴 시, 현재 남은 포인트는 즉시 소멸시키는가?
    • 보존: 과거 포인트 적립/사용 이력은 분쟁 해결을 위해 별도로 보존해야 하는가? 보존 기간은?
  3. 경계 검증 테스트 케이스:
    • (삭제 경계) 계정 탈퇴 시, 포인트 잔액은 0이 되지만 포인트 사용 이력 데이터는 삭제되지 않음을 검증한다.
    • (독립성 경계) 포인트 시스템 장애가 매장 주문 기능에 영향을 주지 않음을 검증한다.

솔라가 작성한 것은 단순한 메모가 아니었다. 그것은 미래에 추가될 그 어떤 새로운 시스템이라도 혼란에 빠지지 않고, 기존의 질서 안으로 안전하게 편입시킬 수 있는 체계적인 계획서이자, ‘경계 일관성’을 보증하는 첫 번째 약속이었다.