Forward Deployed Engineer challenge: I built a company ERP portal in 2 days with AI
한 회사가 Forward Deployed Engineer (FDE) 채용 공고와 함께 과제(take-home brief)를 올렸다. 내부 운영 포털을 3일 동안 만들어 오라는 내용이었다. 나는 이 도전을 받아들여, AI가 타이핑의 상당 부분을 담당하게 하면서 나 혼자서 2일 만에 완성했다. 회사는 해당 공고를 내리고 내가 만든 포털을 채택했으며, 오늘날 약 700명의 직원이 이를 사용하고 있다.
저는 Nazmul Ahasan입니다. 저는 Senior Engineering Manager로서 Platform Engineering 팀을 운영하며, 이런 종류의 작업에 대해 제 사이트 Nazmul Ahasan에서 글을 씁니다. 분명히 밝히자면: 저는 FDE 직함을 가진 것은 아닙니다. 제가 FDE 도전을 수행한 것입니다. 이 게시물에서는 제가 무엇을 만들었는지, 이런 구축 작업에 AI를 어떻게 사용할지, 수동으로 반드시 확인해야 할 부분들, 그리고 제가 가장 중요하다고 생각하는 부분인 승인 엔진(approval engine)에 대해 다룹니다.
Forward Deployed Engineer 챌린지가 요구한 것
과제는 내부 운영/ERP 포털을 설명했다. 저는 이것을 OpsFlow라고 이름 붙였습니다. 제가 구현한 범위는 다음과 같습니다:
- 하드코딩된 플로우가 아닌, 설정(config) 기반의 다단계 승인 시스템
- 휴가 신청: 직원 → 직속 상사 → HR → 자동 이메일 발송 → 휴가 잔여 일수 차감
- 급여 증명서
- 급여 관리(Payroll)
- 선지급 급여
스택은 의도적으로 지루하게 구성했습니다: Next.js, Prisma 및 PostgreSQL을 AWS EC2에서 실행하는 방식이었습니다. 2일이라는 시간 동안에는 '지루한' 것이 오히려 좋습니다. 이 목록에 있는 모든 도구는 검색 엔진 없이 디버깅할 수 있는 것들이라 중요합니다. 왜냐하면 AI가 자신 있게 틀린 정보를 제공할 때가 있기 때문입니다.
그것이 제가 생각하는 FDE 스타일 과제의 진정한 핵심입니다. 코드를 작성할 수 있는지 여부를 묻는 것이 아닙니다. 복잡한 비즈니스 문제에 직면했을 때, 그것의 형태를 빠르게 파악하고 실제 결과물을 사용자 앞에 내놓을 수 있는지를 묻는 것입니다. AI는 코드를 얼마나 빨리 쓸 수 있는지 바꿀 뿐입니다. 결과물에 대한 책임자가 누구인지는 바꾸지 않습니다.
저는 전 과정에서 AI 코딩 어시스턴트를 활용했습니다. 제가 생각하는 AI의 방식은 이렇습니다. 비즈니스에 대한 맥락 지식이 전혀 없는, 빠르고 지치지 않는 주니어 엔지니어 같으면서도 확신에 찬 말투를 습관적으로 사용하는 느낌입니다. 이러한 관점이 모든 것을 결정합니다.
AI가 제 역할을 하는 영역:
- 뼈대 구축 (Scaffolding). Next.js 페이지, 폼(form), 테이블 뷰, Prisma 모델의 기본 구조(boilerplate), API 라우트 셸(shell) 등입니다. 양은 많고 위험도는 낮으며, 원래라면 이틀 중 대부분을 차지했을 부분입니다.
- 반복적인 변형 (Repetitive variations). 한 번 승인 흐름(approval flow)이 작동하면, 다음 흐름들은 대부분 '같은 패턴에 다른 필드와 단계'로 이루어져 있습니다. AI는 이미 설정된 패턴을 따르는 데 매우 능숙합니다.
- 설계 검토 (Rubber-ducking the design). 실제로 코드를 작성하기 전에 데이터 모델을 논의하는 과정에서, 승인자가 요청자이기도 한 경우처럼 어색한 케이스를 초기에 발견할 수 있습니다.
제가 고집하고 싶은 프롬프팅 워크플로우:
- 먼저 손으로 설계합니다. 데이터 모델과 워크플로우 구성 형식을 직접 작성하세요. 다른 모든 것은 이 두 가지에 의존하며, 충분히 깊이 생각할 수 있을 만큼 작습니다.
- 모델에게 계약(Contract)을 제시합니다.
인증 및 RBAC(역할 기반 접근 제어). 상태를 변경하는 모든 서버 액션과 API 경로는 사용자가 누구인지, 그리고 어떤 역할을 가지고 있는지 서버 측에서 확인해야 합니다. AI가 생성한 코드는 보통 UI에 검사 로직을 넣고 버튼을 숨기는 데 그치며, 엔드포인트 자체는 놓치는 경우가 많습니다. 해당되는 모든 변경(mutation) 경로를 읽어보세요.
급여 및 급여 계산. 돈과 관련된 논리는 한 줄씩 읽어야 합니다: 반올림 처리, 공제액, 선지급액 상환, 부분 월 급여 등입니다. 모델이 옳다고 말하는 것과 비교하기보다는, HR 부서가 이미 계산한 결과와 작업 예시를 대조해 보세요.
마이그레이션(Migrations). Prisma는 스키마 변경을 쉽게 할 수 있게 해주는데, 바로 이 점 때문에 읽어봐야 합니다. 생성된 모든 마이그레이션은 실제 데이터베이스에 적용되기 전에 사람이 검토해야 하며, 특히 컬럼을 삭제하거나 이름을 바꾸는 내용은 더욱 그렇습니다.
승인 예외 케이스(Approval edge cases). 더블 클릭. 두 명의 승인이 동시에 이루어지는 경우. 이미 다음 단계로 넘어간 단계에 대해 승인자가 행동하는 경우. 요청자가 자신의 요청을 승인하는 경우. 거부된 요청이 나중에 승인되는 경우 등입니다. 이러한 버그들이 사람들이 시스템에 대한 신뢰를 잃게 만들고, AI가 생성한 '행복 경로(happy-path)' 코드는 이를 놓칩니다.
테스트. 가장 가치가 높은 테스트는 상태 전이 함수(state-transition function)에 대한 것입니다. 왜냐하면 모든 워크플로우가 이 과정을 거치기 때문입니다. 그 다음으로는 각 역할별로 전체 흐름을 끝까지 따라가며 검토해야 합니다.
승인 엔진 설계
이것은 제가 디자인 리뷰에서 가장 강력하게 주장할 부분입니다. 휴가, 급여 증명서, 선지급액 모두 같은 구조를 가지고 있습니다: 누군가가 요청하고, 일련의 사람들이 승인하거나 거부하며, 완료되면 무언가 발생합니다. 그래서 저는 하나의 엔진을 만들고 각 흐름(flow)을 설정 파일 조각으로 만들었습니다.
모델은 네 부분으로 구성됩니다:
enum RequestStatus {
PENDING
APPROVED
...
}
두 가지 제약 조건(constraint)이 여기서 많은 조용한 작업을 수행합니다. idempotencyKey는 고유하기 때문에, 재시도된 요청은 두 번째 액션을 기록할 수 없습니다. 그리고 @@unique([requestId, stepOrder])는 어떤 애플리케이션 코드를 작성하든 단계가 한 번만 결정될 수 있음을 의미합니다.
설정 기반 워크플로우 정의 (config-driven workflow definition)
단계들은 데이터베이스에 존재하여 검사할 수 있지만, 동작(behavior)은 타입이 지정된 설정(typed config)에 있습니다. 워크플로우를 추가한다는 것은 새로운 상태 기계(state machine)를 작성하는 것이 아니라, 정의(definition)를 추가하는 것을 의미합니다.
type Role = "EMPLOYEE" | "LINE_MANAGER" | "HR" | "FINANCE";
interface StepDef {
...
직원(employee)이 암묵적인 첫 번째 단계입니다. 요청을 생성하는 것이 그들의 액션입니다. 그 이후에는 직속 상사(line manager), 그다음 HR, 그리고 완료 후크(completion hook)가 이메일을 보내고 잔액을 차감합니다.
제가 설계할 방식은 다음과 같습니다. 잔액 차감은 최종 승인과 같은 트랜잭션 내에서 실행되어야 합니다. 따라서 차감이 실패하면 승인은 발생하지 않습니다. 잔액이 건드려지지 않은 상태로 '승인됨(approved)'으로 표시된 것이 명확한 오류보다 더 나쁩니다. 또한, 이메일은 인라인(inline)으로 전송되기보다는 큐에 넣어져야 합니다. 왜냐하면 이메일은 롤백(roll back)할 수 없기 때문입니다. 이것이 유지되는 설계입니다.
상태 전환 함수 (The state-transition function)
모든 승인 및 거부는 하나의 함수를 통과합니다. 역할 확인, 단계 확인, Idempotency, 그리고 동시성(concurrency) 모두 트랜잭션 내에 존재합니다.
import { Prisma, PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
...
언급할 만한 몇 가지 선택 사항이 있습니다:
- 안전장치와 보강책(Belt and braces). Idempotency 조회는 정중한 재시도(polite retry)를 처리합니다. Unique constraints는 무례한 경쟁 상황(impolite race)을 처리합니다. 버전 확인은 두 명의 승인자가 동시에 같은 단계에 작용하는 것을 포착합니다.
- 직무 분리(Separation of duties)가 명시적입니다. "자신의 요청을 승인할 수 없음"이라는 문구 하나로 구현됩니다. 이는 AI가 생성한 코드에서 정확히 빠지기 쉬운 부분이기도 합니다.
- 설계 단계부터의 감사 추적(Audit by construction).
ApprovalAction은 추가 전용(append-only)으로 설계되었기 때문에, "누가 언제 승인했는지"는 조사가 아니라 단순한 쿼리입니다.
- 테스트를 위한 단일 지점. 휴가, 급여 증명서, 선지급금 모두
decide를 거칩니다. 이 흐름을 한 번만 제대로 만들면 모든 워크플로우에 이점이 돌아갑니다.
향후 시스템을 더욱 강화할 아이디어: 승인자가 휴가 중일 때의 위임(delegation), 정체된 요청에 대한 에스컬레이션(escalation), 그리고 진행 중인 요청이 시작했던 규칙을 유지하도록 하는 워크플로우 정의 버전 관리 등이 있습니다.
배포하기 (Deploying it)
이 포털은 AWS EC2에서 운영되며 그 뒤에는 PostgreSQL 데이터베이스가 있습니다. 짧은 빌드 기간 동안, 제가 선호하는 것은 반복 가능한 가장 단순한 배포 방식이며, 제가 반드시 고집할 사항들은 다음과 같습니다: 단일 빌드 명령어, 앱 부팅 시가 아닌 명시적인 단계로 적용되는 마이그레이션(migrations), 모든 마이그레이션 전 데이터베이스 백업, TLS를 통한 보호, 그리고 앱이 다운될 경우 재시작하는 프로세스 관리자입니다. 플랫폼 엔지니어링 습관은 데드라인이 짧다고 해서 꺼지는 것이 아닙니다. 오히려 더 중요합니다.
제가 얻은 교훈 (What I took from it)
AI가 2일이라는 일정을 가능하게 했습니다. 하지만 AI가 이 제품을 신뢰할 수 있게 만든 것은 아니었습니다. 올바른 사람이 승인하고, 정확한 잔액과 정확한 돈이 오가는 상황에서 사람들이 기꺼이 사용하도록 만든 요소들은 어떤 부분을 세심한 인간의 주의를 기울여 결정했는지에 달려 있었습니다.
만약 당신이 Forward Deployed Engineer 역할을 찾고 있거나, 비즈니스를 위해 직접 구축하는 모든 역할을 고려하고 있다면, 제가 연습할 기술은 이것입니다. 프롬프팅(prompting) 자체가 아닙니다. AI 결과물이 어느 정도 수준이면 충분한지, 그리고 어디는 반드시 증명되어야 하는지에 대한 판단력입니다.
회사는 채용 공고를 내리고 이 포털을 도입했습니다. 약 700명이 사용합니다. 이것이 제가 2일보다 더 중요하게 생각하는 성과입니다.
플랫폼 엔지니어링과 AI를 활용한 구축에 대해서는 제 사이트(Nazmul Ahasan)에서 더 많이 작성하고 있습니다.