사내 이슈 관리 시스템을 요구사항부터 풀스택으로 구현한 과정
Work Experience · 0→1 Product
요약
- 분류
- Work Experience · 0→1 Product
- 회사
- 주식회사 팀마파
- 기간
- 2026.07 - 현재
- 역할
- Frontend Engineer · 애플리케이션 개발 1인 담당
기존 협업 도구의 업무 적합성·민감정보·인당 과금 문제를 바탕으로 요구사항과 정책을 구체화하고, 이슈 관리 제품의 설계·풀스택 구현·테스트·Asana 데이터 이전 도구 구현을 담당했습니다.
하이라이트
- 요구사항·제품 정책부터 풀스택 구현까지 1인 담당
- 테스트 라인 커버리지 90% 이상과 주요 시나리오 E2E 검증
- Asana 이전 도구 구현 — 이슈 573건·댓글 737건·첨부 701건 운영 이전 완료
기술React · TypeScript · Vite · TanStack Query · NestJS · PostgreSQL · Prisma · SSE · Playwright
문제·판단·구현 과정 읽기목차
한눈에 보기
사내 개발자와 클라이언트 담당자가 함께 사용하는 이슈 관리 시스템을 만들었습니다. 기존 협업 도구의 업무 적합성, 민감한 정보 취급과 인당 과금 구조를 검토해 자체 구축안을 제안했고, 승인 이후 요구사항과 제품 정책부터 프론트엔드·백엔드, 데이터 이전 도구까지 애플리케이션 개발을 1인으로 담당했습니다.
2026년 9월 현재 운영 배포가 완료되어 클라이언트와 함께 실사용 중입니다.
문제와 역할
기존에는 클라이언트와의 개발 이슈를 Asana로 관리했습니다. 그러나 일부 업무 기능이 맞지 않았고, 비개발자에게 범용 도구의 많은 개념은 학습 부담이 될 수 있었습니다. 개인정보와 내부 업무정보를 외부 SaaS에서 주고받는 부담, 사용자가 늘수록 반복되는 비용도 함께 고려해야 했습니다.
Asana와 Notion, 국내 협업 도구를 비교하고 동료 인터뷰와 클라이언트 피드백을 수집했습니다. 그 결과 모든 기능을 제공하는 범용 도구보다 이슈 등록·처리·공유에 집중하고 필요에 따라 직접 수정할 수 있는 도구가 현재 조직에 적합하다고 판단했습니다. 자체 개발 비용도 있기 때문에 라이선스 비용만으로 구축을 정당화하지는 않았습니다.
제품의 중심 흐름은 클라이언트가 이슈를 등록하면 개발자가 담당자·상태·우선순위를 지정하고, 댓글과 첨부파일로 진행 상황을 공유한 뒤 완료하는 과정으로 정의했습니다. 프로젝트별 데이터 격리, 실시간 갱신과 알림, 기존 데이터의 안전한 이전을 첫 출시의 완료 조건으로 삼았습니다.
애플리케이션 개발은 1인이 담당하되 방향과 운영 인프라는 팀장과 역할을 나눴습니다.
- 본인 — 대안 제안, 요구사항 구체화, 업무 정책과 출시 범위 초안, 기술 구조·데이터 모델·API·화면 흐름 결정, 프론트엔드·백엔드 구현, 로컬 Docker 환경, 테스트, 이전 도구
- 팀장 — 추진 승인, 요구사항과 정책 검토, 운영 배포·DB 등 외부 인프라 설정
- 동료·클라이언트 — 기존 도구와 업무 흐름에 대한 인터뷰·피드백 제공
요구사항과 변경안은 Markdown 명세와 제안서로 먼저 작성하고, 대화와 회의에서 승인받은 뒤 수정일과 함께 반영했습니다.
핵심 제품 판단
초기에는 Organization과 Project를 각각 권한 경계로 두고 1차 구현을 진행했습니다. 그러나 조직 단위 권한이 필요하다는 실제 요구가 없고 두 계층이 권한 모델과 화면을 불필요하게 복잡하게 만든다고 판단했습니다.
Organization을 제거하고 Project 하나를 클라이언트별 멤버십·역할·데이터 격리의 유일한 경계로 단순화했습니다. Group은 권한과 무관하게 프로젝트 안에서 이슈를 묶는 선택적 분류로 재정의했습니다. 과거 담당자와 댓글 작성자, 감사 이력의 책임 관계는 사용자 물리 삭제 대신 계정과 멤버십 비활성화로 보존했습니다.
기술적 결정
기술 선택의 기준은 속도와 익숙함이었습니다. 약 두 달 안에 애플리케이션 전반을 혼자 구현해야 했기 때문에 React/Vite와 NestJS로 TypeScript 생태계를 통일했고, 관계형 권한 모델과 트랜잭션을 위해 PostgreSQL과 Prisma를 선택했습니다.
현재 사용자 규모에는 단일 API 인스턴스로 충분하다고 보고, 마이크로서비스 대신 하나의 NestJS 애플리케이션 안에서 도메인 경계를 나눈 모듈러 모놀리스를 구성했습니다. 다중 인스턴스가 실제로 필요해지면 인메모리 SSE 대신 외부 메시지 브로커를 도입하도록 전환 조건을 문서화했습니다.
업무 데이터의 기준은 HTTP API와 DB로 유지하고, SSE는 변경 사실만 알리는 invalidation signal로 사용했습니다. DB 트랜잭션이 커밋된 뒤 이벤트를 발행하면 프론트엔드가 TanStack Query 캐시를 무효화하고 HTTP로 최신 상태를 다시 조회합니다. 이 방식은 중복·역순 이벤트를 재조회로 흡수하고 연결 복구 뒤에도 서버 상태로 수렴하게 합니다.
모든 보호 API에서 활성 프로젝트 멤버십과 역할을 검사하고, 이슈·댓글·첨부파일·실시간 구독에 같은 프로젝트 경계를 적용했습니다. 다른 프로젝트 ID를 사용한 접근이 거부되는지는 실제 PostgreSQL 기반 API E2E 테스트로 검증했습니다.
리뷰로 트랜잭션 경계 수정
요구사항과 완료 조건을 직접 정의하고, 에이전트가 제안하거나 구현한 결과는 코드 리뷰와 테스트를 거쳐 반영했습니다. 아키텍처도 제안받은 대안을 일정·복잡도·운영 조건에 맞춰 검토해 최종 결정했습니다.
리뷰 과정에서는 SSE 발행이 DB 트랜잭션 안에 있어 전달 실패가 핵심 업무 저장까지 실패시킬 수 있는 문제를 발견했습니다. DB 변경을 먼저 커밋하고 SSE는 이후 best-effort로 발행하도록 경계를 수정해, 부가적인 실시간 전달 실패가 저장 결과를 되돌리지 않게 했습니다.
테스트와 데이터 이전
테스트 라인 커버리지 90% 이상을 품질 기준으로 두고, 실제 PostgreSQL 기반 API E2E와 Playwright로 권한 경계와 주요 사용자 시나리오를 검증했습니다.
기존 업무를 실제로 전환하기 위해 Asana의 사용자·이슈·댓글·첨부파일 이전 도구도 구현했습니다. 운영 이전에서 사용자 10명, 이슈 573건, 댓글 737건, 첨부 메타데이터 701건을 저장했고, 시스템 활동 이력 3,699건은 건수만 별도로 집계했습니다. 원본 ID와 신규 시스템 ID를 별도 매핑해 재실행 시 중복을 막고, 하나의 Task가 실패하면 해당 데이터만 롤백한 뒤 나머지는 계속 처리하도록 실패 경계를 나눴습니다.
첨부파일 다운로드와 저장소 쓰기는 DB 트랜잭션 밖에서 처리하고, 이후 DB 작업이 실패하면 저장한 파일을 보상 삭제했습니다. 외부 I/O로 트랜잭션이 길어지는 문제와 고아 파일을 함께 방지하기 위한 선택이었습니다.
현재 결과와 운영 현황
약 두 달 동안 요구사항 정의와 제품·기술 설계부터 풀스택 구현, 데이터 이전 도구까지 담당했습니다. 주요 업무 흐름과 권한 경계를 테스트했고, 수백 건 규모의 운영 데이터 이전이 완료되어 현재 클라이언트와 함께 실제 업무에 사용하고 있습니다.
회고
처음에는 클라이언트별 Organization 계층이 당연히 필요하다고 생각했습니다. 하지만 실제 권한 요구를 검토하자 Project 하나로 충분했고, 계층이 적은 편이 사용자에게도 더 이해하기 쉬웠습니다. 다시 시작한다면 익숙한 도메인 구조를 먼저 모델링하지 않고, 누가 어떤 데이터에 어떤 행동을 해야 하는지부터 검증하겠습니다.
실시간 통신과 백엔드 구조도 현재 규모에 필요한 단순성에서 시작했습니다. 단일 인스턴스 SSE와 모듈러 모놀리스를 선택하되 다중 인스턴스가 필요한 시점을 확장 조건으로 문서화했습니다. 확장 가능한 구조는 모든 복잡성을 미리 구현하는 것이 아니라, 복잡성을 추가할 조건을 분명히 아는 것이라는 점을 배웠습니다.
실시간 갱신의 트랜잭션 경계
DB 트랜잭션 안
- 01사용자 mutation
- 02DB transaction commit
커밋이 끝난 뒤에만
트랜잭션 밖 · best-effort
- 03SSE invalidation 신호 발행
- 04TanStack Query 캐시 무효화
- 05HTTP 재조회
데이터 이전의 실패 경계
트랜잭션 밖 · 외부 I/O
첨부 다운로드 → 스토리지 저장
Task 단위 트랜잭션
Task · 담당자 · 댓글 · 첨부 레코드 기록
성공
다음 Task 계속
실패
해당 Task만 롤백
저장한 파일 보상 삭제
첫 단계에서 이미 쓴 파일을 지워 고아 파일을 남기지 않습니다.