실무를 시작한 지 2년쯤 됐다. 처음과 비교해 보면 요즘들어 주로 하는 고민이 꽤 달라졌다. 오늘은 그 고민들과 사고 방식에 생긴 변화들, 아직도 어려운 부분들을 중심으로 지난 세월들을 되돌아보려 한다.
지금 나는 사내 MSP 관리 시스템을 프론트, 백엔드를 넘나들며 개발하고 있다. 이 글에서는 이 시스템을 SYS_B라고 부르겠다.
프론트엔드 개발자로 일할 때는 API 응답을 화면에 매핑하고 상태를 관리하면서, 사용자가 기능을 자연스럽게 쓸 수 있도록 UI와 UX를 구현하는 데 집중했다. 공통 컴포넌트와 도메인 컴포넌트를 구분하고, 새 요구사항을 반영하면서 기존 화면에 영향을 주지 않도록 신경을 많이 썼다.
당시에는 주어진 API 계약 안에서 구현 방법을 찾는 경우가 많았다. 계약 자체가 업무에 맞는지, 응답에 담긴 데이터가 어떻게 만들어지는지까지 내가 판단해야 한다고는 생각하지 못했다.
백엔드와 DB를 함께 맡으면서는 화면에 필요한 필드 하나를 추가할 때도 결정할 것이 늘었다. 데이터를 어디에서 가져올지, 언제 저장할지, 기존 데이터에는 어떤 영향을 줄지까지 살펴야 했다. 그러다 화면과 API만 확인해서는 원인을 알기 어려운 운영 이슈를 만났다.
8월 초, 수백 명 규모의 고객사 사용자를 한꺼번에 초대하는 기능에서 사용자가 잘못된 계정에 연결되는 이슈가 발생했다. 처음에는 조회 로직 한 군데를 고치면 되는 문제처럼 보였다.
관련 서비스와 Gateway의 로그부터 인증 서버 이벤트, 운영 데이터, 코드 변경 이력까지 확인했다. 그 과정에서 계정 식별 기준 외에도 초대 흐름을 불안정하게 만드는 문제들이 드러났다. 대량 조회 방식을 바꾸면서 nginx ingress의 요청 크기 제한에 걸렸고, 외부 메일 서버와의 통신에는 타임아웃이 없었다. 다른 서비스에서는 원본 오류가 로그에 남지 않아 실패 원인을 추적하기 어려웠다.
문제마다 발생하는 위치도, 필요한 수정도 달랐다. 처음 보인 증상만 고쳤다면 나머지는 그대로 남았을 것이다. 이후로는 필요한 코드만 바꾸더라도 그 코드가 사용되는 흐름은 함께 살펴보려고 한다.
정상 동작을 확인한 뒤에는 다음 항목도 점검한다.
모든 변경에서 이 항목들을 같은 깊이로 확인하지는 않는다. 이번 변경이 어디까지 영향을 줄 수 있는지 따라가 보고, 그 범위에 맞춰 검증한다.
구현 전 리뷰에서도 실행 환경을 함께 살펴본 적이 있다. 인증 미들웨어에 로컬 캐시를 넣는 변경이었는데, API 인스턴스가 요청량에 따라 최대 열 개까지 늘어나는 환경이었다. 각 인스턴스가 권한 정보를 따로 캐시하면 갱신 시점에 따라 서로 다른 권한으로 판단할 수 있었다.
그래서 변경 빈도가 낮은 계정 유형만 로컬 캐시에 두고, 최신성이 중요한 권한 목록은 요청마다 DB에서 조회하자고 제안했다. 이 방향이 반영됐다.
SYS_B에는 청구와 할인처럼 회계적인 성격의 업무가 있다. 같은 ‘금액’, ‘담당자’, ‘할인’이라는 단어도 적용 기준과 시점에 따라 의미가 달라진다.
고객사의 청구 데이터에 영업대표를 연결하는 작업이 그런 경우였다. 코드로 보면 필드 하나를 채우는 일이지만, 구현 전에 확인할 것이 있었다.
현재 담당자를 보여주는 기능과 발행 당시 담당자를 보존하는 기능은 겉으로 비슷해도 저장하고 조회하는 방식이 달라진다. 이 기준을 정하지 않으면 코드는 정상적으로 동작해도 업무상 잘못된 정보를 보여줄 수 있다.
그래서 요즘은 구현에 들어가기 전에 이 기능이 어떤 업무 문제를 해결하려는 것인지, 데이터는 언제 만들어지고 바뀌는지부터 확인한다. 도메인을 이해했다고 구조가 바로 정해지지는 않지만, 어떤 값을 보존하고 어디에서 책임질지 판단할 근거는 생긴다. 불분명한 부분이 있으면 먼저 질문해서 확인하는 편이 낫다는 것도 배웠다.
구조를 정할 때는 비슷한 역할의 코드가 현재 프로젝트의 어디에 있고, 어떻게 쓰이는지 먼저 찾아본다. 새로운 원칙이나 패턴을 적용하기 전에 기존 방식을 이해하려고 한다.
이론적으로 타당한 구조라도 팀원이 익숙하게 찾아가던 코드의 위치가 달라진다면 그 비용을 함께 생각해야 한다. 기존 관례에 문제가 있으면 바꾸되, 왜 그런 관례가 생겼는지는 먼저 살펴본다.
컴포넌트를 어디에 둘지 정할 때도 사용 횟수와 함께 어떤 업무를 담당하는지 본다. 여러 화면에서 쓰인다는 이유만으로 도메인 컴포넌트를 공용 영역으로 옮기지는 않는다. 모양이 비슷해도 담당하는 업무와 변경 이유가 다를 수 있기 때문이다.
파일 위치만 보고 역할을 예상할 수 있는지도 중요하게 생각한다. 프로젝트의 관례에 따라 순수 계산은 utils, React와의 연결은 hooks, 화면 표현은 ui에 둔다. 새로운 디렉터리가 필요해 보이면 기존 위치에 두기 어려운 이유가 실제로 있는지부터 확인한다.
업무 범위가 넓어지니 관련 코드를 모두 기억하기가 어려워졌다. 변경할 때마다 같은 항목을 빠짐없이 확인하는 것도 쉽지 않았다. 그래서 반복되는 조사와 확인 절차를 AI가 수행할 수 있는 스킬로 정리해 쓰고 있다.
불필요한 추상화를 줄이기 위해 ponytail도 적극(아니 항상) 활용한다. 기존 구현과 표준 기능으로 해결할 수 있는지 먼저 살펴보는 방식이 내가 지향하는 개발 방식과 잘 맞는다.
AI가 제안한 내용은 현재 코드와 업무 조건에 맞는지 다시 확인한다. Git 이력과 실제 호출부를 살펴보고, 필요한 테스트와 lint, build 결과도 함께 본다. 업무 히스토리를 찾는 시간도 줄이고 싶어서 로컬 문서와 메모리를 검색하는 RAG 도구를 개발 중이다.
이런 도구를 쓰면서 더 넓은 범위를 살펴보는 데 도움을 받고 있다. 그래도 무엇을 확인할지 정하고 결과를 해석해서 최종 선택을 설명하는 일은 내가 해야 한다. 특히 처음부터 설계하는 일에서는 아직 그 판단이 어렵다.
데이터 모델을 처음부터 설계하는 일은 아직 익숙하지 않다. 요구사항을 테이블과 관계로 옮기는 것도 느리지만, 여러 설계안 중 하나를 고르고 그 이유를 설명하는 일이 더 어렵다.
최근 사용자 초대 현황과 일괄 초대 이력을 위한 DB 스키마를 설계하면서 이 부분을 많이 느꼈다. 화면에 필요한 정보는 비교적 명확했다. 어떤 파일을 올렸고 누구에게 초대를 요청했는지, 몇 명이 성공하거나 실패했는지, 초대한 사용자가 현재 활성 상태인지를 보여주면 됐다.
막상 설계에 들어가니 결정할 것이 늘어났다. 업로드한 파일과 실제 발송을 하나의 작업으로 어떻게 연결할지부터 고민했다. 파일 전체의 결과를 JSON으로 보관할지, 대상자별 기록을 별도 테이블로 둘지도 정해야 했다. 화면에서 잠깐 쓰는 선택 상태와 나중에도 확인해야 하는 발송 이력도 구분해야 했다.
처음에는 원본과 결과를 JSON으로 보관하는 단일 테이블 안을 검토했다. 그런데 이 방식으로는 한 사람의 발송 결과가 바뀔 때마다 전체 JSON을 다시 저장해야 했다. 발송 도중 서버가 중단되면 어디까지 처리했는지 어떻게 확인할지도 문제였다.
선임분께 피드백을 받으면서, 발송 중에는 대상자별 기록을 갱신하고 처리가 끝나면 그 기록으로 최종 결과 JSON을 만드는 방향으로 정리했다. 테이블은 늘어나지만 한 번에 수정하는 데이터의 범위는 분명해진다. 대신 대상자별 기록과 완료 결과를 언제, 어떤 기준으로 맞출지까지 정해야 한다.
같은 ‘초대 현황’ 안에서도 시간의 기준은 달랐다. 누구에게 어떤 고객사와 역할로 초대했는지는 발송 당시의 기록을 보존해야 했다. 반면 사용자의 활성 상태는 현재 값을 확인해야 했다. 화면에 함께 표시되는 정보여도 필요한 시점이 달랐고, 모두 DB에 저장해야 하는 것도 아니었다.
각 방식의 설명을 들으면 이해는 된다. 하지만 요구사항을 보고 직접 선택지를 정리한 뒤, “이번에는 이 방식을 택하고, 이 비용은 감수하겠다”고 판단하기까지는 시간이 걸린다. 선택지를 처음부터 일관된 기준으로 비교하는 연습이 더 필요하다고 느꼈다.
도메인을 먼저 이해하고 필요한 만큼만 구현하려고 해도, 막상 무엇이 필요한 최소 구조인지 정하는 일은 어렵다. 테이블 수가 적으면 단순할 줄 알았는데 꼭 그렇지도 않았다. 그렇다고 나중을 대비해 필드를 많이 두는 게 좋은 설계인 것도 아니었다.
다음 설계에서는 ERD를 그리기 전에 데이터 한 건의 흐름부터 그려보려고 한다. 언제 생성되고 바뀌는지, 어떤 조회에 쓰이는지, 중간에 실패하면 무엇이 남아야 하는지를 정리하고 구조를 비교해볼 생각이다. 최종 구조를 선택한 이유와 포기한 대안, 다시 검토해야 할 조건도 짧게 남기고 싶다. 직접 내린 결정이 구현과 운영에서 어떤 결과로 이어지는지 보면서 판단 기준을 쌓아가려 한다.
처음에는 화면을 안정적으로 구현하고 성능을 개선하는 일이 가장 중요했다. 지금은 화면에 표시되는 데이터가 어디에서 만들어지고 어떤 규칙으로 저장되는지, 변경 이후에는 무엇이 달라지는지까지 생각하게 됐다.
생각할 범위가 넓어진 만큼 아직 서툰 부분도 보인다. 기존 흐름을 따라 문제를 찾는 일보다 새로운 데이터 구조를 정하고 선택의 근거를 설명하는 일이 여전히 어렵다. 다음 회고를 쓸 때쯤에는 내가 만든 기능이 왜 이렇게 동작해야 하는지, 왜 이 구조를 골랐는지 지금보다 분명하게 설명할 수 있었으면 좋겠다. 기술적인 문제를 만났을 때 해결 방법과 그 이유를 명쾌하게 이야기할 수 있는 개발자가 되고 싶다.