자기소개서

좋은 개발 환경은 좋은 결과물을 만든다고 믿습니다

저는 좋은 개발 환경이 단순히 편한 분위기를 의미한다고 생각하지 않습니다. 서로의 의견을 존중하고, 문제를 숨기지 않고 드러내며, 각자의 역할을 책임 있게 수행하는 환경이 결국 안정적인 서비스와 좋은 결과물을 만든다고 믿습니다.

개발자로 일하면서 가장 크게 체감한 것은 혼자 빠르게 만드는 능력만큼이나, 문제를 정확히 공유하고 팀이 같은 방향으로 움직이게 만드는 역량이 중요하다는 점입니다. 천재교육 AIDT LMS 프로젝트에서는 백엔드, 프론트엔드, 인프라 흐름이 맞물리는 실시간 퀴즈/판서 서비스를 개발하며 서버 분리, Kafka, Socket IO, Nginx, ALB 설정까지 함께 보았습니다. Guidance에서는 오래 운영된 레거시 시스템을 옮기는 일에서 시작해, 옮긴 기반 위에서 팀의 요청이 코드에 닿기까지의 과정을 자동화하는 데까지 범위를 넓혀 왔습니다.

저는 Java/Kotlin Spring 기반 백엔드 개발을 중심으로 하되, 화면, 데이터, 배포, 운영 흐름까지 연결해서 문제를 해결하는 개발자입니다. 단순히 주어진 기능을 구현하는 데서 멈추지 않고, 서비스가 안정적으로 동작하기 위해 필요한 맥락을 끝까지 파악하며 팀의 결과물에 기여하고 싶습니다.


직무역량

레거시를 옮기고, 반복을 자동화하고, AI를 설계에 넣습니다

제 일은 세 단계로 이어져 왔습니다. 오래된 시스템을 옮겨 다시 돌아가게 만들고, 그 위에서 사람이 반복하던 과정을 자동화하고, 그 자동화에 AI를 어떻게 쓸지 설계하는 일입니다. 세 가지가 따로가 아니라 한 줄기라고 생각합니다. 레거시를 모르면 무엇을 자동화해야 할지 알 수 없고, 자동화의 경계를 정해 보지 않으면 AI에게 무엇을 맡길지도 정할 수 없기 때문입니다.

Guidance에서는 Ruby on Rails로 운영되던 사내 ERP를 Java/Kotlin Spring Boot 구조로 전환하는 일을 맡아 인증·인가 체계와 접근제어, OMR 매퍼 관리, 주문 파이프라인을 도메인 단위로 이식하고 Rails 원본 앱을 제거했습니다. 여기에 더해 Rails를 거치지 않고 ASP 원본에만 남아 있는 검사 주문·정산 흐름까지 직접 이관했습니다. 오래 운영된 화면에는 문서화되지 않은 기준이 묻혀 있기 때문에, 원본 로직을 읽어 정가·할인 산정 같은 규칙을 먼저 드러낸 뒤 단일 기준으로 정리하면서 옮기는 방식을 택했습니다. 영속성 기술도 관성대로 고르지 않았습니다. 이 시스템이 도메인 엔티티를 영속화하는 앱이라기보다 연도별로 분리된 여러 레거시 DB를 정확히 조회·가공하는 관리자 앱에 가깝다고 보고, JPA 대신 MyBatis와 Spring JDBC를 주 축으로 삼았습니다.

이관을 마치고 나니 다음 병목이 보였습니다. 요구사항은 개발자가 아닌 팀에서 올라오는데, 그것이 코드에 닿으려면 매번 같은 사람을 거쳐야 한다는 것이었습니다. 그래서 요구사항 관리 도구에 접수된 항목을 읽어 구현하고, 빌드와 코드 스타일 검증을 통과한 경우에만 PR을 여는 과정을 에이전트 스킬로 정의해 레포에 두었습니다. 실행에 코드 수정이 필요 없으니 제가 자리에 없어도, 이 스킬을 만든 사람이 아니어도 같은 절차가 돌아갑니다. 결과적으로 비개발자가 올린 요구사항이 설계가 필요한 지점만 아니라면 개발자 병목 없이 반영되는 경로가 생겼습니다.

여기서 제가 가장 신경 쓴 것은 자동화의 범위를 넓히는 일이 아니라 어디서 멈춰야 하는지를 정하는 일이었습니다. PR까지만 열고 머지는 반드시 사람이 하도록 못 박았고, 요구가 모호하거나 결제·개인정보·권한 체계·스키마 변경처럼 위험이 큰 건은 구현하지 않고 사유와 함께 보류로 돌려 사람에게 넘기게 했습니다. 또 요구사항 본문은 PR을 연 뒤에도 요청자가 고치거나 덧붙일 수 있기 때문에, 머지 이후 현재 본문을 다시 읽어 실제 반영 여부를 대조하고 늘어난 요구가 있으면 재작업으로 되돌리는 단계를 넣었습니다. "PR을 열었다"가 아니라 "지금의 요구가 반영됐다"를 완료 기준으로 삼은 것입니다. 기능이 바뀌면 비개발자용 사용 매뉴얼과 업데이트 이력까지 갱신되도록 별도 스킬을 두어, 코드와 사용자가 보는 문서가 어긋나지 않게 했습니다.

검사 도메인 자체도 같은 방식으로 다뤘습니다. SPS, LDAT 등 신규 검사 개발에서는 설계도와 검수용 엑셀 데이터를 기준으로 채점, 규준, 소견, 결과 속성 생성 로직을 구현하고, 문항·척도·변환점수·규준표·소견 조건을 DB 구조와 도메인 객체에 매핑한 뒤 검수 결과값과 실제 산출 결과를 비교하며 정확성을 맞춰 갔습니다. 그다음에 맡은 일은 반대 방향이었습니다. 같은 성격의 검사가 각기 다른 스택에서 자기 방식으로 채점되고 결과를 내고 있었고, 검사가 하나 늘 때마다 실시·채점·결과 경로가 통으로 하나씩 늘어나는 구조였습니다. 그래서 네 종을 공통 검사 스택으로 옮기고 과거 결과까지 공통 저장소로 이전 적재해, 조회하는 쪽은 그 결과가 어느 시절에 만들어졌는지 알 필요가 없게 만들었습니다. 천재교육에서는 Spring Boot 기반 AIDT LMS 실시간 퀴즈 서비스와 LMS/LCMS 저작도구 서비스를 개발하며 Kafka, Socket IO 기반으로 최대 동시 접속자 600명 규모의 실시간 통신을 안정화했고, API·Kafka·Socket 서버를 분리해 메시징 구조를 구성했습니다.

문제해결 경험

원인을 끝까지 좁혀 실제 장애 요인을 해결합니다

실시간 퀴즈 서비스 개발 과정에서 서버 다중화 시 세션과 메시지 전달이 안정적으로 맞물리지 않는 문제가 있었습니다. API 서버와 Socket 서버를 분리하고 sticky-session을 적용했지만, 한 서버에서 생성된 정보가 다른 서버로 정상 전달되지 않는 상황이 발생했습니다. 원인을 추적한 결과 Kafka consumer group을 room-id 단위로 구성해 같은 group 내에서 메시지를 의도한 방식으로 pull하지 못하는 구조적 문제가 있었습니다. 이를 서버 자원의 고유한 이름을 기준으로 consumer group을 재정의하는 방식으로 수정해 서버 간 데이터 공유와 실시간 통신 흐름을 안정화했습니다.

Guidance에서는 문제해결의 성격이 조금 달랐습니다. 레거시를 옮기는 일은 장애를 고치는 작업이라기보다, 오래 운영되며 아무도 적어 두지 않은 기준을 찾아내 다시 세우는 작업에 가까웠습니다.

레거시 검사들을 공통 스택으로 옮기면서 분명해진 것이 있습니다. 이관은 원본을 그대로 옮기는 일이 아니라 원본이 맞는지 확인하는 일이었습니다. 산출값을 연구소 수기채점표와 한 항목씩 맞춰 보니, 오래 돌아온 코드인데도 반응일관성처럼 판정 자체가 동작하지 않던 지표가 있었고, 강점과 약점이 서로 뒤바뀌어 저장되는 곳도 있었습니다. 오류로 드러나지 않고 결과지가 그럴듯하게 채워져 나오기 때문에 그동안 드러나지 않았던 것입니다. 이후로는 이관 대상을 받으면 동작부터 보지 않고 정본 기준을 먼저 놓고 대조하고 있습니다. 검사 결과가 기대값과 다를 때는 문항 매핑, 척도 계산, 규준표 기준, 소견 조건, 결과 속성 생성 순서를 하나씩 비교하며 차이를 좁혔고, OMR 업로드처럼 신규 Spring API와 Ruby 기반 ERP, ASP 레거시 화면이 함께 맞물리는 지점에서는 각 시스템의 기준 데이터를 맞추는 방식으로 문제를 좁혔습니다. 배포가 끝나지 않고 멈춰 있는 상황도 단순 재시도로 넘기지 않고, 신·구 태스크가 서로를 기다리며 겹쳐 있는 구조적 원인까지 좁혀 겹침을 제거하고 실패한 배포가 무한히 머무르지 않도록 자동 중단 장치를 같이 넣었습니다.

이관이 끝난 뒤에도 같은 방식이 이어졌습니다. 중복과 죽은 코드를 걷어내는 정리 작업을 따로 잡아 돌렸는데, 정리 자체보다 정리하는 동안 드러난 것들이 더 중요했습니다. 매퍼가 값을 옮겨 담기만 하는 중간 타입을 걷어내다 보니 검사 정보를 수정할 때 OMR 검사지 값이 초기화되던 경로가 보였고, 화면과 엑셀의 금액 칸이 한 칸씩 밀려 들어가던 것, 특정 항목을 비워 저장하면 실패하던 것도 같은 과정에서 나왔습니다. 셋 다 오류를 내지 않고 조용히 어긋나던 유형입니다. 위치로 맞추는 생성자 매핑은 컬럼 순서가 바뀌면 오류 없이 어긋나므로, 그 순서를 미리 대조하는 가드를 남겨 같은 유형이 다시 지나가지 않게 했습니다.

자동화에서는 안전장치를 어디에 둘지가 문제입니다