HN.
Portfolio로 돌아가기
Featured2025.07 – 08팀 프로젝트

ResumeLink

AI 기반 이력서 생성 및 포트폴리오 관리 & 개발자 네트워킹 플랫폼

  • 프로그래머스 데브코스 6기 팀 프로젝트로 개발한 AI 기반 이력서 생성 및 개발자 네트워킹 플랫폼
  • 커피챗 신청-승인 프로세스를 통해 네트워킹 기회를 제공하고, 승인된 관계에서만 실시간 채팅이 가능하도록 접근 제어를 설계
  • 프로젝트 최우수상 수상
TypeScriptNode.jsExpressPrismaPostgreSQLWebSocket

문제 정의

Why this project?

개발자 커뮤니티에서 멘토링이나 피어 리뷰를 위한 자연스러운 연결 수단이 부족했습니다. 기존 플랫폼은 이력서 관리와 네트워킹이 분리되어 있어, 프로필을 확인한 뒤 바로 대화를 시작하기 어려웠습니다. 프로필 기반 매칭부터 대화까지 하나의 플랫폼에서 자연스럽게 이어지는 구조가 필요했습니다.

핵심 기능

Key Features

1

커피챗 도메인 분리

커피챗 신청-승인 프로세스와 실시간 채팅을 논리적으로 분리하여, 커피챗 승인이 채팅방 생성의 선행 조건이 되도록 접근 제어 로직을 설계했습니다. 승인되지 않은 관계에서는 채팅 채널 자체가 생성되지 않으므로, 비인가 접근을 구조적으로 차단합니다. 도메인 분리를 통해 커피챗 로직 변경이 채팅 로직에 영향을 주지 않도록 결합도를 낮췄습니다.

2

실시간 메시지 시스템

WebSocket으로 실시간 메시지 브로드캐스팅을 처리하고, Prisma REST API로 메시지를 DB에 영속화하는 이중 구조를 설계했습니다. WebSocket은 즉시성을, REST API는 데이터 안정성을 각각 담당하여, 메시지가 실시간으로 전달되면서도 DB에 안전하게 저장되는 구조를 구현했습니다.

3

읽음 상태 관리

DB 내 LastReadMessageId를 사용자별로 관리하고, 서버 사이드에서 UnreadCount를 계산하여 단일 진실 소스(Single Source of Truth)를 유지했습니다. 클라이언트에서 카운트를 관리하면 다중 디바이스/탭 환경에서 불일치가 발생하므로, 서버 연산 기반으로 일관된 상태를 보장하는 방식을 선택했습니다.

기술 선택 이유

Tech Decisions

WebSocket + REST API 결합

실시간성이 필요한 메시지 전송은 WebSocket, 영속화와 조회는 REST API로 분리하여 각 프로토콜의 강점을 활용했습니다.

Prisma ORM

TypeScript와의 자연스러운 타입 통합으로 개발 생산성을 높이고, 팀 전체가 일관된 DB 접근 패턴을 사용할 수 있었습니다.

Git Flow

팀 프로젝트에서 feature/develop/release/hotfix 브랜치 전략으로 병렬 개발 시 충돌을 최소화하고 릴리스 품질을 관리했습니다.

문제 해결 경험

Problem Solving

Problem

초기 설계에서 커피챗 승인 여부와 관계없이 채팅 채널이 미리 생성되어 있었고, 승인 전 사용자가 URL을 직접 입력하면 채팅이 가능해지는 접근 제어 취약점이 발견되었습니다. 인증(로그인)은 되어 있지만 인가(권한)가 제대로 작동하지 않는 상태였습니다.

Approach

이 문제를 "도메인 경계가 명확하지 않아서 발생한 구조적 결함"으로 판단했습니다. 단순히 채팅 진입 시 승인 여부를 체크하는 것이 아니라, 커피챗 도메인과 채팅 도메인을 논리적으로 분리하고, 채팅방 생성 자체를 커피챗 승인의 후행 이벤트로 설계했습니다. 승인 이벤트가 발생해야만 채팅 채널이 생성되므로, 비인가 접근을 체크 로직이 아닌 구조로 원천 차단할 수 있었습니다.

Result

승인된 관계에서만 채팅이 가능한 안전한 접근 제어를 구현했고, 도메인 분리를 통해 향후 커피챗 로직 변경이 채팅에 영향을 주지 않는 유연한 구조를 확보했습니다.

Problem

클라이언트 측에서 미읽음 메시지 수를 각각 관리하고 있었는데, 같은 사용자가 여러 탭이나 디바이스에서 접속하면 UnreadCount가 서로 다르게 표시되는 데이터 불일치 문제가 발생했습니다. 한쪽에서 읽어도 다른 쪽에 반영되지 않았습니다.

Approach

이 문제의 본질은 "상태의 진실 소스가 분산되어 있다"는 것이었습니다. 클라이언트가 각자 카운트를 관리하는 방식을 폐기하고, 서버에서 LastReadMessageId를 기준으로 UnreadCount를 매번 계산하여 응답하는 구조로 전환했습니다. 상태를 저장하는 것이 아니라 조회 시점에 계산하는 방식이므로, 어떤 디바이스에서 접근하든 항상 동일한 결과를 보장합니다.

Result

다중 디바이스/탭 환경에서도 일관된 미읽음 수를 보장하는 구조를 확립했습니다.

성과 및 배운 점

Outcomes

  • 프로그래머스 데브코스 6기 프로젝트 최우수상을 수상했습니다
  • 팀 리드로서 TypeScript 기반 API 명세·DTO 표준을 수립하고, Git Flow 전략을 도입했습니다. "어떻게 만들 것인가"보다 "팀이 어떻게 함께 일할 것인가"를 먼저 설계하는 경험이 개발 리더십에 대한 시각을 넓혀줬습니다
  • WebSocket과 REST API를 결합하는 과정에서, 실시간성과 안정성이라는 두 가지 상충되는 요구사항을 프로토콜 분리로 해결하는 패턴을 체득했습니다
  • 접근 제어 취약점을 구조적 설계로 해결하면서, 보안 문제를 "체크 로직 추가"가 아니라 "구조적으로 불가능하게 만드는 것"이 더 안전하다는 교훈을 얻었습니다

링크

Links