윤정현
백엔드 · 풀스택 개발자
- 경력
- 4년 1개월 · 2022.04 – 2026.07
- 주력
- Java · Spring Boot / PHP · Laravel
Go · Node.js · Next.js · Flutter - 교육
- DBMS기반 빅데이터 응용 SW개발자 전문과정
중앙정보처리학원 · 2018.08 – 2019.03 - 이메일
- yjhzzzzdev@gmail.com
- GitHub
- github.com/yjh-dev
- 년 개월총 경력 · 3개 회사
- 년 개월Java · Spring Boot 백엔드 · 저장소 6개 · 52,146줄
- %Laravel 본 서비스 커밋 기여 · 1,585 / 2,349
- 0건레거시 이관 기간 중 서비스 중단
- %신규 스택 커밋 · 1,755 / 1,772
Career
레거시에서 시작해 레거시를 옮기기까지
-
2024.09 — 2026.07모바일파트너스책임연구원 · 1년 10개월 · 자체 솔루션
모바일 앱 운영 B2B SaaS AppBox의 본 서비스를 Laravel 11로 1년 9개월간 개발·운영했습니다. 팀 전체 2,349커밋 중 1,585건이 제 커밋입니다. 이후 같은 시스템을 Next.js + Go 구조로 서비스 중단 없이 옮겼고, 새 구조는 제가 맡아 만들었습니다. GPS 실시간 채팅 돌콩의 채팅 서버와 백오피스도 혼자 만들었습니다.
-
2025.03 — 2026.01프리랜스인슈플러스 · 플라잉닥터스 (재직 중 병행)
전 소속사에서 담당하던 여행보험 B2C 서비스(PHP 레거시)를 위탁받아 혼자 유지보수했습니다. 요청 60건을 처리하며 ISMS-P 인증 요건을 운영 서버에 반영했고 — 개인정보 마스킹, 컬럼 암호화, 조회 통제, 동시접속 제한 — 결제·알림톡 장애와 AWS EC2 운영을 맡았습니다.
-
2023.05 — 2024.05코드아이디어주임 · 1년 1개월 · SI
Java · Spring Boot로 B2B 플랫폼 마켓해머의 백엔드를 담당했습니다. 1세대(Spring Boot 2.7 · Java 8)에서 기능을 만들다, 퇴사 전 2개월간 PC 백엔드·프론트를 Java 17 · Spring Boot 3로 다시 세웠습니다(231파일 · 첫 커밋부터 제가 작성). 여행보험 PG 결제 모듈 교체와 Laravel 10 고객사 웹 2건도 맡았습니다.
-
2022.04 — 2023.05비젠소프트사원 · 1년 2개월 · 웹에이전시
JSP · Spring과 PHP 레거시로 사내 ERP, 장비 예약, 전자계약 등 고객사 웹 서비스 6건을 개발했습니다. 남이 쓴 레거시 코드를 읽고 고치는 훈련을 이때 했습니다.
01 — AppBox
웹사이트를 앱으로
만들고 운영합니다
앱 빌드부터 푸시·인앱 메시지, 구독 결제까지 처리하는 B2B SaaS입니다. 기존 시스템은 팀으로 운영하고, 새 구조는 제가 맡아 만들었습니다.
모바일파트너스 · 책임연구원 · 2024.09 – 2026.07
01 / 04
앱 빌드
고객사 웹사이트를 클라우드에서 Android · iOS 앱으로 컴파일·패키징합니다. 빌드는 수 분이 걸려 웹 요청 안에서 처리할 수 없었습니다. 접수만 응답하고 실제 빌드는 큐로 넘겨 웹 응답 시간과 분리했습니다.
02 / 04
푸시 메시지
토큰 선택 · CSV 업로드 · 테스트 발송을 지원합니다. API는 요청을 대기열에 쌓아 두기만 하고, 실제 발송은 별도 서버의 프로그램이 하나씩 꺼내 처리합니다. 덕분에 외부 Push API를 붙일 때 발송 파이프라인은 한 줄도 고치지 않았습니다.
03 / 04
인앱 메시지
위치 · 크기 · 딤 · 애니메이션을 설정하고 실시간으로 미리 봅니다. 캠페인 메타는 PostgreSQL, 콘텐츠는 MongoDB로 나눠 저장합니다 — 집계·정합성은 관계형이 유리하고, 자유로운 콘텐츠 구조는 스키마리스가 유리하기 때문입니다.
04 / 04
사용자 여정 · 토큰 적재
SDK가 수집한 디바이스 토큰을 적재·갱신하고, 발송 대상 산출의 기준으로 씁니다. 여정 인스턴스는 유저가 아니라 디바이스 단위로 잡았습니다 — 한 사람이 기기를 여러 대 쓰면 발송 대상이 달라지기 때문입니다.
운영 백오피스
고객사·발송·빌드를 한자리에서 관제합니다
고객사명 · 담당자 정보 · 접속 IP는 마스킹 처리했습니다.
본 서비스 — Laravel 11 · 1년 9개월
“레거시”라고 불렀지만
매일 돌아가던 제품이었습니다
이관 이야기를 하기 전에 — 저는 이 Laravel 시스템을 1년 9개월 동안 계속 만져 온 사람입니다. 고객사가 매일 쓰는 기능이 전부 여기 있었고, 신규 구조는 이 코드를 충분히 안 뒤에야 그릴 수 있었습니다.
- 1,585본인 커밋 · 팀 전체 2,349건 중 67%
- 24,656제가 맡아 운영한 애플리케이션 코드 줄 수
- 1년 9개월2024.10 – 2026.06 연속 커밋
- Laravel 11 · PHP 8.2
- 컨트롤러 49 · 서비스 13 · 미들웨어 13 · 마이그레이션 31 · 라우트 229 · Blade 뷰 126
- 아티즌 커맨드 34
- 빌드·발송·정산 배치를 스케줄러가 부르는 커맨드로 떼어냈습니다 — 웹 요청과 배치가 같은 실행 경로를 쓰지 않게 한 자리입니다
- 이관과의 관계
- Next.js·Go 구조는 이 코드를 버리고 다시 쓴 게 아니라 여기서 알아낸 도메인을 옮긴 것입니다. 구독 게이팅·인증 회귀 2건을 운영 반영 전에 잡을 수 있던 것도 원본을 알고 있었기 때문입니다
레거시 무중단 점진 이관
한 번에 바꾸면
실패가 곧 장애가 됩니다
저장소를 성격으로 나눴다
관계·정합성은 PostgreSQL, 디바이스·이벤트처럼 쓰기가 많고 스키마가 흔들리는 것은 MongoDB로 옮겼습니다. Redis는 캐시와 큐를 별도 인스턴스로 갈랐습니다 — 큐가 캐시 eviction에 쓸려가면 발송이 그대로 증발하기 때문입니다.
접수와 발송을 떼어냈다
API는 요청을 대기열에 쌓아 두기만 하고, 별도 서버의 프로그램이 하나씩 꺼내 발송합니다. 대기열 전용 제품(Kafka·RabbitMQ) 대신 Redis를 쓴 건, 이미 캐시로 쓰고 있어 운영할 서버를 늘리지 않기 위해서였습니다.
포기한 것
전면 재작성을 포기하고, 대신 두 시스템을 한동안 동시에 유지하는 비용을 받아들였습니다. 정기결제 스케줄러도 Go로 옮기지 않고 Console에 남겼습니다 — 결제 로직이 전부 TS에 있어 이중 유지보수가 생길 자리였습니다.
이관 결과
- 0건이관 기간 중 서비스 중단
- 건운영 반영 전 발견·복원한 기능 회귀
- 수정 없이 확장기존 파이프라인 그대로 Push API 대응
회귀 2건은 이런 것이었습니다 — 구독을 정지시킨 앱에도 인앱 메시지가 계속 노출됐고(구버전에 있던 게이팅이 신버전에서 누락), 브라우저 대응으로 인증을 뺀 인앱 엔드포인트로는 타사 캠페인 열람과 통계 조작이 가능했습니다. 게이팅 복원 · 경로별 CORS 분기 · IP Rate limit(20rps)으로 막았습니다.
데이터 설계
이미 결제한 달은 흔들리면 안 됩니다
plan_name까지 복사해, 이력에서 “그때 무슨 플랜이었나”를 볼 수 있게 했습니다.차감은 원자적으로
Console 스케줄러(TS)와 Go API가 같은 테이블에 동시 접근합니다. 읽고·계산하고·쓰면 경합에서 잔량이 마이너스로 갑니다.
UPDATE billing.charge_balances
SET remaining = remaining - :qty
WHERE remaining >= :qty;두 지갑을 쓰는 순서
월 제공량(subscription_quotas)을 먼저 쓰고 부족분만 크레딧(charge_balances)에서 뺍니다. 기간이 지나면 사라지는 것부터 쓰는 순서 — 고객에게 유리한 쪽입니다.
UNIQUE(app_id, balance_type)로 행 자체를 하나로 묶어 경합 지점을 좁혔습니다.
정리 못 한 부채
journey_instances는 정의가 PostgreSQL에 남아 있는데 실제 읽기·쓰기는 MongoDB에서 일어납니다. 디바이스 단위 고쓰기라 Mongo가 맞고, 초기에 PG로 설계했다가 옮기면서 PG 정의를 지우지 않았습니다. 발견했고 정리 대상으로 보고 있습니다 — 숨기면 저장소에서 바로 걸리는 종류입니다.
- 규모
- PostgreSQL 9스키마 81테이블 — core · billing · messaging · build · cms · mailing · support · backoffice · audit / MongoDB 22컬렉션 / Redis 4인스턴스
스키마 지도 — 도메인 경계를 스키마로 갈랐습니다
core가 허브고 — apps 테이블을 중심으로 나머지 스키마가 이 앱을 참조합니다.서비스 구성 — 저장소 6개를 서버 3대에
- web · 107
- console 고객 콘솔 (Next.js 16) · backoffice 운영 (Next.js) · api SDK·콜백·스케줄러 (Go/Echo) · landing 레거시 본 서비스 (Laravel 11) · demo 가상 고객사
- push · 103
- push 발송 워커 (Go) — Redis Stream 을 소비해 FCM·SMTP 로 보냅니다. 접수와 물리적으로 분리한 자리입니다
- db · 106
- PostgreSQL · MongoDB · Redis 4인스턴스 (dev·prod × cache·queue)
- 환경 분리
- 같은 서버 안에서 dev·prod 를 포트와 systemd 유닛으로 가릅니다. 설정은
_infra/에 저장소로 관리합니다
02 — 마켓해머
웹과 앱이 같은 API를
부르게 만들었습니다
기업 간 B2B 플랫폼의 Java · Spring Boot 백엔드입니다. 운영 중인 1세대 시스템에서 기능을 만들다, 마지막에는 같은 서비스를 Java 17 · Spring Boot 3 구조로 다시 세웠습니다.
코드아이디어 · 주임 · 2023.05 – 2024.05
재구축 — 한 덩어리를 넷으로 갈랐습니다
Spring Security + JWT로 바꿨습니다.규모와 기여
- 6Spring Boot 저장소 · Java 569파일 52,146줄
- 787매핑된 엔드포인트 · 컨트롤러 68 · MyBatis 매퍼 55
- 5주PC v2 백엔드·프론트 — 첫 커밋부터 제가 작성
기여를 나눠서 말하면 이렇습니다 — v1(2023.05–2024.04)에서는 147커밋으로 팀의 한 명이었고, 모바일 v2는 절반(백 58/120 · 프론트 70/149)이었으며, PC v2는 2024.04 첫 커밋부터 2024.05까지 5주간 제가 작성했습니다(백 231파일 10,835줄 · 프론트 18파일). 어디까지가 팀이고 어디부터가 저인지 저장소로 확인할 수 있습니다.
- 맡은 기능
- 웹·앱 공용 RESTful API 설계 · 기업 간 채팅과 푸시 알림 · 기업별 구독 플랜과 구독 상태 관리 · Quartz 배치 · Flutter WebView 앱 패킹
- 다음으로 이어진 것
- 여기서 만든 구독·결제 상태 관리가 AppBox 구독 게이팅 설계로 이어졌습니다 — 이관 중 발견한 회귀 2건 중 하나가 정확히 그 게이팅이었습니다
03 — 돌콩
반경 5km,
지금 여기의 대화
GPS 기반 실시간 위치 채팅 서비스입니다. 핵심 문제는 프로세스를 여러 개로 늘려도 같은 이벤트를 모두에게 전달하는 것이었습니다.
모바일파트너스 · 2026.03 – 2026.07 · 채팅 서버와 운영 백오피스 구축
01 / 03
클러스터 실시간 동기화
Socket.IO 서버를 PM2 클러스터로 확장하고 Redis Adapter로 프로세스 간 이벤트를 동기화했습니다. JWT 소켓 미들웨어로 연결 단계에서 인증합니다.
02 / 03
위치가 곧 채팅방
MongoDB의 위치 검색 인덱스로 반경 5km 안의 채팅방을 찾고, 메시지는 마지막으로 읽은 지점부터 거슬러 올라가며 나눠 불러옵니다.
03 / 03
안전한 운영 체계
같은 방에서 3회 신고되면 자동으로 내보내고 영구 차단합니다. 백오피스에서 내린 조치는 Redis를 거쳐 채팅 서버에 곧바로 전달됩니다.
- 설계 기준
- 반경 5km · 방 24h / 메시지 7d TTL · 동일 방 신고 3회 자동 차단 · FCM 3초 스로틀
돌콩 · 앱 화면
- 기술
- Node.js · Socket.IO · Redis · MongoDB · Next.js
데이터 모델 — 인덱스가 곧 기능입니다
2dsphere, 방 24시간·메시지 7일 정리는 expiresAt TTL(expireAfterSeconds:0)이 대신합니다 — 삭제 배치가 없습니다. 대용량 메시지 조회는 {room, createdAt:-1} 복합 인덱스 위에서 커서로 거슬러 올라갑니다.- 왜 TTL 인가
- 지워야 할 것을 애플리케이션이 기억하지 않아도 되게 만들었습니다. 배치가 죽어도 데이터는 제때 사라집니다
- 왜 sparse 인가
- 푸시를 끈 사용자는
fcmToken이 없습니다. 없는 값을 인덱스에서 빼 발송 대상 조회만 빠르게 했습니다
프리랜스 · 여행보험 B2C
개인정보를 다루는 화면은
기능보다 통제가 먼저입니다
전 소속사에서 담당하던 서비스를 퇴사 후 위탁받아 11개월간 혼자 유지보수하며, ISMS-P 인증 요건을 운영 서버에 반영했습니다.
노출 통제
이름 · 주민번호 뒷자리 · 휴대전화를 출력 단계에서 마스킹하고, 복사 · 드래그를 차단했습니다. 검색 조건 없는 전체 조회를 막고, 쓰지 않는 관리자 게시판은 접근 자체를 닫았습니다.
저장 · 반출 통제
단체 가입 저장 시 개인정보 컬럼이 암호화되지 않던 결함을 찾아 고치고, 암호화 키를 환경별 설정으로 분리했습니다. 개인정보가 든 엑셀 · CSV 반출에는 암호화를 걸었습니다.
접근 통제
관리자 동시접속을 제한해 다른 브라우저로 로그인하면 기존 세션이 끊기게 했고, 접속 IP를 등록해 관리하도록 했습니다.
- 그 외
- 이니시스 · 토스페이먼츠 결제 전환, 가상계좌 자동전환 · 카드 취소 오류, 카카오 알림톡 발송 장애, 제휴사 전용 가입 페이지 3종, AWS EC2 운영 · SSL 갱신
- 규모
- 2025.03 – 2026.01 · 11개월 · 유지보수 요청 60건 처리 · PHP 레거시
04 — 투인원
기획부터 운영까지,
두 스토어에 올렸습니다
커플 미디어 앱입니다. App Store와 Google Play에 출시해 운영 중이며, 앱(Flutter)과 서버(NestJS), 인프라까지 전 과정을 혼자 담당했습니다.
개인 프로젝트 · 1인 개발 · 5개 배포 단위 · Flutter + NestJS · AWS EC2 · Cloudflare R2
투인원 · 앱 화면
직접 부딪힌 문제
혼자 만들면 어느 층위든 내가 고쳐야 합니다
층위를 고르는 문제 해결
"한 사람은 한 커플에만 속한다"를 앱 코드에서 검사했더니, 두 요청이 동시에 들어올 때 규칙이 깨졌습니다. 코드를 더 조이는 대신 데이터베이스가 두 번째 저장을 직접 거절하도록 조건을 걸었습니다. 문제를 어느 층위에서 풀지 고르는 것이 절반입니다.
릴리스에서만 터지는 크래시
디버그는 멀쩡한데 릴리스만 부팅 즉시 죽었습니다. 원인은 코드가 아니라 빌드 설정이었습니다 — 빌드 도구가 "쓰이지 않는다"고 판단해 지운 클래스를, 앱이 실행 중에 이름으로 찾고 있었습니다. 개발 환경의 초록불은 프로덕션을 보장하지 않습니다.
중복 요청 제거
로그인 만료 응답이 한꺼번에 오면 토큰 갱신 요청도 여러 번 나갑니다. 갱신할 때마다 이전 토큰이 무효가 되는 방식이라 먼저 받은 토큰이 곧바로 못 쓰게 되고, 이유 없는 로그아웃이 생겼습니다. 동시에 들어온 갱신 요청을 하나로 합쳐 처리하도록 고쳤습니다.
- 기술
- Flutter · NestJS · PostgreSQL · Docker · AWS
스키마 설계 — 제약은 나중에 못 붙입니다, 이미 깨진 데이터가 막습니다
제약에 담은 의도
UNIQUE(user_id) WHERE is_active- 동시 수락으로 한 사람이 두 커플에 속하는 것 — 앱 코드로만 막으면 검사와 저장 사이의 틈으로 뚫립니다
(ownerScope, ownerId, sourceHash)- 같은 사진 중복 업로드 — 재시도·네트워크 끊김에도 멱등하게
(ownerScope, ownerId, authorId, date)- 작성자당 하루 1엔트리 — upsert 키로 그대로 씁니다
(mediaId, userId, emoji)- 같은 이모지 중복 — 토글 구현이 자연히 따라옵니다
데이터 모델 — 개인과 커플을 한 테이블에서
ownerScope(user | couple)와 ownerId 두 컬럼이 소유자를 표현하고, 유니크 제약도 이 두 컬럼을 앞에 둡니다. 커플이 되는 순간 데이터를 옮기는 대신 소유자를 바꿉니다 — 테이블을 나눴다면 여기서 마이그레이션이 필요했습니다.- 22 모델 · 5 배포 단위
- 사용자·인증(
UserSocialIdentityAuthSessionPushToken) / 관계(CoupleCoupleMemberInvite) / 콘텐츠(MediaReactionCommentDiaryEntryEventWishItem) / 기록(MoodLogHeartNoteQuestionAnswerLoveLanguageResult) / 운영(AdminUserAuditLogCsInquiry) - append-only 감사
AuditLog는 수정·삭제하지 않습니다.media.view같은 행위별 조회 인덱스를 둬 관리자가 무엇을 봤는지까지 남깁니다
05 — 윤사무실
지어내지 않는 구조로
에이전트를 관찰합니다
AI 코딩 에이전트의 활동을 실시간으로 관제하는 데스크톱 앱입니다. 에이전트가 하는 일을 픽셀 사무실 화면에 공간으로 배치했습니다.
개인 프로젝트 · Electron 33 · v0.21.0 · 런타임 의존성 0
01 / 03
지어내지 않는 구조
CLI 출력을 파싱하는 대신 훅 이벤트를 구독합니다. 이벤트가 없으면 캐릭터가 움직일 근거가 없어 가짜 활동이 구조적으로 불가능합니다.
02 / 03
테스트를 의심하는 절차
검사를 만들 때마다 결함을 일부러 주입해 검출 여부를 확인했습니다. ‘초록불이지만 동작하지 않는’ 상태 4건을 발견해 수정했습니다.
03 / 03
되돌릴 수 있는 실행
git plumbing으로 임시 저장소에 스냅샷을 만듭니다. 사용자의 git은 건드리지 않고, 스냅샷은 30개까지 보관합니다.
검증 결과
- 36.8%검사 코드 비중 — 6,776 / 18,433줄
- 건로직 단언 — 43초 내 실행
- 건결함 주입으로 찾아낸 거짓 검사
- 0런타임 의존성
스스로 지운 것
정확하지 않은 지표는 없느니만 못합니다
사용량 게이지를 만들어 붙였는데, 재는 기간도 분모도 값도 실제 도구의 화면과 전부 달랐습니다. 한도가 아닌 것을 한도처럼 보이게 만든 셈이라 기능을 통째로 걷어냈고, 되돌아오지 못하도록 검사를 심었습니다. 만든 것을 지우는 판단까지가 제 몫이라고 봅니다.
- 기술
- Electron · Node.js · Python
데이터 모델 — 파일이 곧 프로토콜입니다
jsonl 에 한 줄씩 붙이고 앱은 그것만 읽습니다. 이벤트가 없으면 화면이 움직일 근거 자체가 없습니다. 파일이 프로젝트 폴더 안에 있어서, 회사를 3개 열어도 서로 모르는 채 각자 돕니다 — 규약을 전역에 뒀다면 셋이 같은 큐를 집었을 겁니다.- 이벤트 9종
session·prompt·agent_start·tool·agent_stop·error·reply·command_taken·cancel— 공통 필드ts는 발생 시각이라 재생할 때 그대로 씁니다- 귀속은 추론하지 않는다
- 어느 에이전트가 한 일인지 훅이 남긴 값만 씁니다. 로그에서 유추하면 그럴듯한 거짓말이 화면에 뜹니다
Stack
기술 스택
- 주력
- Java · Spring Boot (2년 3개월 · 저장소 6개) / PHP · Laravel (4년 · 본 서비스 커밋 67%)
실무 4년 대부분을 이 두 스택 위에서 일했습니다 - 그 외 언어
- Go · Node.js · TypeScript · Dart
- 백엔드
- Spring Boot (MyBatis · JPA · Spring Security · Quartz) · Laravel (Eloquent · Artisan · Blade) · Echo(Go) · Express · NestJS
- 프론트엔드 · 앱
- Thymeleaf · Blade · jQuery / Next.js · React / Flutter · Electron
- 데이터
- MySQL · MariaDB · PostgreSQL · MongoDB · Redis
- 인프라 · 보안
- Nginx · Docker · PM2 · systemd · AWS / Spring Security · JWT · AES-256 · 개인정보 마스킹·암호화
- 대외 활동

「조코딩의 AI 비트코인 자동 매매 시스템 만들기」(한빛미디어)
「코딩셰프의 플러터 맛집」(루비페이퍼) — 기술 감수
Contact
문제를 찾아 정의하고, 구조로 해결하며, 변경의 결과까지 책임지겠습니다.
투인원 내려받기
- 교육
- DBMS기반 빅데이터 응용 SW개발자 전문과정
중앙정보처리학원 · 2018.08 – 2019.03 - 경력
- 4년 1개월 · 2022.04 – 2026.07
© 2026 윤정현