근태관리 설계 문서를 분리해 정산 흐름과 용어를 정돈한 과정
목차
근태관리 SaaS 설계를 시작할 때 처음 선택지는 단순했다. PLAN.md 하나에 전체 흐름을 다 때려넣느냐, 아니면 도메인 경계에서 잘라내느냐. 처음엔 당연히 전자로 갔다. 어차피 팀 내부에서 보는 문서고, 빠르게 전체 맥락을 공유하려면 한 파일이 편하다고 생각했음.
근데 출퇴근 → 시프트 배정 → 초과근무 판정 → 정산 집계 순서로 흐름을 이어붙이다 보니 어느 순간부터 스크롤을 내려도 내려도 끝이 안 보였다. 800줄 즈음 됐을 때 검색 없이는 읽을 수 없는 문서가 됐고, 그 시점에 분리를 결정했다. PLAN_ATTENDANCE_DETAIL 파일로 뽑아내면서 상위 문서는 "무엇을 만드는가"만 남기고, 디테일 문서는 "각 도메인이 어떻게 동작하는가"만 담는 구조로 잘랐다.
문서 분리가 별것 아닌 것처럼 보이지만, 이게 후속 작업자한테 주는 영향이 생각보다 크다. 6개월 뒤에 정산 로직 고치러 들어온 사람이 출퇴근 API 명세까지 같은 파일에서 봐야 한다면, 그건 그냥 피로도 문제가 아니라 실수를 유발하는 환경이다. 인지 부하를 낮추는 게 곧 버그 발생률을 낮추는 것과 연결된다는 걸, 코드베이스보다 문서에서 더 자주 실감함.
용어 정의를 먼저 못박은 이유
분리 작업을 하면서 가장 먼저 눈에 띈 건 도메인 언어가 파일마다 달랐다는 점이다. "근태"라고 써야 할 자리에 어떤 페이지는 "출퇴근", 어떤 곳은 "타임시트", 어떤 곳은 "스케줄"이라고 적혀 있었다. 같은 이벤트를 세 가지 이름으로 부르고 있었던 것. 처음 코드를 작성하던 시점엔 큰 문제가 아니었겠지만, 나중에 팀원이 늘어나거나 기획서와 코드를 맞춰봐야 할 때 그 불일치가 오해를 낳는다.
그래서 디테일 문서 맨 앞에 용어 정의 표를 강제로 박았다.
| 용어 | 정의 | 단위 |
|---|---|---|
| 근무 일정 | 사전에 배정된 시프트 | 일 |
| 근태 기록 | 실제 체크인/체크아웃 이벤트 | 분 |
| 근무 시간 | 기록을 일정에 맞춰 환산한 결과 | 분 |
| 정산 | 월/주 단위 합산 결과 | 시간 |
이 표 하나로 후속 챕터들이 자연스럽게 정렬됐다. "근태 기록"과 "근무 시간"이 다른 개념이라는 게 명확해지니까, 두 테이블의 스키마를 구분할 때 컬럼 네이밍도 덩달아 정리됐다. 문서 작업이지만 코드 네이밍 리뷰와 동시에 진행하게 된 셈.
이런 방식을 "유비쿼터스 언어(Ubiquitous Language)"라고 부르는데, DDD에서 강조하는 개념이다. 도메인 전문가와 개발자가 같은 단어를 쓰지 않으면 설계가 아무리 정교해도 어딘가에서 번역 오류가 생긴다. 지금은 팀이 작아서 괜찮아 보여도, 문서화 시점에 용어를 통일해두면 이후 규모가 커질 때 재작업이 훨씬 줄어든다.
상태 전이를 의사코드로 정리한 이유
글로만 상태 전이를 적으면 읽는 사람마다 다르게 해석한다. "체크인 시 시프트가 없으면 미정 근태로 처리한다"라고 써도, "처리한다"가 DB에 row를 생성하는 건지, 아니면 그냥 플래그를 다는 건지 모호하다. 그래서 디테일 문서에는 의사코드 블록을 넣었다.
on check_in(user, ts):
shift = find_shift(user, ts.date)
if not shift: create_unscheduled_attendance(user, ts)
else: open_attendance(user, shift, ts)
on check_out(user, ts):
att = current_open_attendance(user)
close_attendance(att, ts)
enqueue_settlement(att) # 비동기, 정산은 별도 워커
여기서 핵심은 check_out 시점에 정산을 동기로 돌리지 않는다는 것이다. 출퇴근 트래픽은 오전 9시 전후 5분 안에 집중된다. 동기 정산을 묶어두면 정산 로직이 느려지거나 DB 락이 걸리는 순간 그 5분이 그대로 장애로 이어진다. 비동기 큐로 넘기면 체크아웃 응답은 즉각 반환하고, 정산 워커는 별도 리소스에서 천천히 처리하면 된다. 이커머스 결제 플랫폼 작업할 때 배웠던 패턴 그대로 가져온 것인데, 적용 도메인이 달라도 "피크 트래픽에서 무거운 연산을 분리한다"는 원칙은 그대로 통한다.
비동기로 뺐을 때 트레이드오프도 있다. 정산 결과가 실시간으로 보이지 않아서, 관리자 대시보드에서 "오늘 근무 시간"이 체크아웃 직후 바로 반영되지 않을 수 있다. 이 부분은 UI에서 "집계 중" 상태를 명시적으로 표시하는 식으로 커버하기로 했다. 사용자가 모르게 지연되는 것보다, 처리 중이라는 걸 명확히 알리는 게 낫다.
문서 작업에서 반복되는 패턴
이번에 작업하면서 다시 확인된 것들.
- 상위 문서는 의사결정만, 하위 문서는 동작만. 두 가지를 섞는 순간 어느 쪽도 제대로 읽히지 않는다. "왜 이 구조인가"를 찾으러 들어왔는데 체크인 API 파라미터 목록이 나오면 그냥 닫게 됨.
- 용어 정의 표를 무조건 맨 앞에. 챕터가 세 개만 넘어가도 용어 충돌이 생긴다. 나중에 고치려면 전체 문서를 다 뒤져야 하니까, 초반에 시간 투자하는 게 압도적으로 효율적.
- 상태 전이는 글 대신 의사코드. 표현이 줄어드니까 리뷰도 빨라지고, 코드 작성할 때 의사코드를 참조하면서 함수 시그니처도 자연스럽게 정렬됨.
- 외부 의존은 별도 박스로 분리. 파트너 API나 외부 정산 윈도우 같은 것들을 본문에 섞으면 내부 로직인지 외부 제약인지 구분이 안 된다. 별도 섹션으로 빼서 "이건 우리가 바꿀 수 없는 것"임을 명시하는 게 중요.
문서를 나누는 작업이 실제 코딩보다 가성비가 낮아 보일 수 있다. 당장 기능이 나오는 게 아니니까. 근데 총괄로 있다 보면 세 달 뒤에 온보딩하는 팀원이 어느 파일 먼저 열어야 하는지, 설계 결정이 왜 이렇게 됐는지를 설명하는 데 드는 시간이 훨씬 크다는 걸 실감한다. 그 비용을 문서 구조에서 미리 줄이는 것도 엔지니어링의 일부다.
댓글 0
첫 댓글 달아줘.