[AI 협업 회고] LimitBuy 프로젝트에 하네스 엔지니어링을 적용한 과정

들어가며

LimitBuy Commerce 백엔드를 진행하면서 AI에게 단순히 코드를 생성하게 하는 것보다 먼저 해결해야 할 문제가 있었다.

프로젝트의 목표와 모듈 경계가 충분히 설명되어 있지 않으면 AI는 그럴듯한 코드를 만들 수는 있어도, 내가 의도한 설계를 지키기는 어렵다. 특히 재고, 결제, 인증처럼 직접 고민하고 구현해야 할 영역까지 AI가 대신 만들어버리면 프로젝트의 학습 목적도 흐려질 수 있다.

그래서 이번에는 프로덕션 코드부터 작성하지 않았다. 먼저 프로젝트 문서를 읽고 충돌을 찾은 뒤, AI가 따라야 할 작업 절차와 보호 영역을 정비했다. 그다음 Product와 Category의 설계 결정을 문서와 ADR로 남겼다.

하네스 엔지니어링을 적용한 이유

이번에 생각한 하네스는 AI가 작업할 때 참고하는 문서, 규칙, 검증 절차의 묶음이다.

프롬프트에 매번 모든 조건을 길게 적는 대신 저장소 안에 기준을 두고, 작업 전에 반드시 읽게 만드는 방식이다.

1
2
3
4
5
6
프로젝트 목표
  -> 아키텍처와 모듈 경계
  -> 코딩 규칙과 보호 영역
  -> 도메인 문서와 ADR
  -> 승인된 범위만 구현
  -> 테스트와 변경 범위 보고

이 구조가 있으면 AI가 무엇을 할 수 있는지뿐 아니라, 무엇을 하면 안 되는지도 명확해진다.

먼저 문서와 현재 구조를 비교했다

처음에는 코드 수정을 금지하고 다음 내용을 먼저 분석했다.

  • 프로젝트의 목표
  • Modular Monolith의 전체 모듈 구조
  • AI가 구현해도 되는 영역
  • 개발자가 직접 구현할 보호 영역
  • 코딩 스타일
  • 문서 사이의 충돌과 모호한 내용
  • 구현 전에 추가로 결정해야 할 사항

분석해보니 문서의 목표 구조와 실제 코드 사이에 차이가 있었다. 문서에는 여러 도메인 모듈이 있었지만 실제 구현은 아직 globaluser 중심이었다. 별도 auth 모듈을 둘 것인지도 문서와 현재 패키지 구조가 일치하지 않았다.

들여쓰기 규칙도 문서에는 공백 4칸, 기존 코드에는 공백 2칸으로 달랐다. project-plan.md에는 예시 패키지명과 아직 승인되지 않은 재고·결제 흐름이 확정된 것처럼 남아 있었다.

이 상태에서 Product 구현을 바로 시작했다면 새 코드는 만들 수 있어도 프로젝트 전체 기준은 더 혼란스러워졌을 것이다.

문서 우선순위를 정했다

문서가 여러 개라면 내용이 충돌할 때 무엇을 따라야 하는지가 필요하다. 이번에는 구현 규칙의 우선순위를 다음과 같이 정리했다.

  1. AGENTS.md
  2. docs/architecture.md
  3. 승인된 ADR
  4. 각 도메인 문서
  5. project-plan.md

project-plan.md는 전체 방향을 보여주는 계획서이지만, 초기 예시가 포함될 수 있다. 반면 승인된 ADR은 실제 구현 전에 결정한 정책이므로 더 높은 우선순위를 갖게 했다.

또한 현재 인증 책임도 실제 구조에 맞게 나누었다.

영역 책임
user 회원가입, 로그인, OAuth 회원 매핑
global.security JWT, Security Filter, SecurityConfig, 쿠키 보안

별도 auth 모듈은 지금 만들지 않기로 했다. 나중에 책임이 커져 분리가 필요해지면 그때 다시 결정할 수 있지만, 현재 단계에서 문서의 이상적인 구조만 보고 패키지를 먼저 늘리지 않기 위해서다.

AI가 수정하면 안 되는 보호 영역

LimitBuy 프로젝트의 핵심 학습 주제는 동시성, 멱등성, 트랜잭션 경계, 인증 보안이다. 이 부분을 AI가 완성된 코드로 대신 구현하면 내가 설계를 설명하기 어렵다.

그래서 다음 영역은 개발자가 직접 구현하도록 보호했다.

  • 재고 차감 시점과 동시성 제어 방식
  • 결제 승인, 금액 검증, 중복 Confirm 방지
  • 주문·결제·재고의 상태 전이와 처리 순서
  • OAuth 로그인 성공 처리와 Refresh Token Rotation
  • Kafka 이벤트, Outbox 저장 시점, Consumer 멱등성
  • 핵심 동시성 테스트 시나리오

AI는 이 코드를 읽고 분석할 수는 있지만, 명시적인 승인 없이 수정하지 않는다. 필요한 경우에는 변경 이유, 영향 파일, 위험, 권장 방향까지만 보고하도록 했다.

반대로 일반적인 DTO, 조회 기능, 문서, 테스트 Fixture, 비핵심 기능의 골격은 승인 범위 안에서 맡길 수 있게 했다.

핵심은 AI 사용을 막는 것이 아니라 학습해야 할 판단을 내가 계속 소유하는 것이다.

Product와 Category 설계를 먼저 확정했다

문서 충돌을 정리한 뒤에도 곧바로 코드를 작성하지 않았다. Product와 Category가 어떤 규칙을 가져야 하는지 먼저 논의하고 문서로 남겼다.

이번에 확정한 주요 내용은 다음과 같다.

Category 구조

Category는 product 모듈 안에 두고 첫 버전에서는 단층 구조만 사용한다. Product는 하나의 Category에 반드시 소속된다.

처음부터 다단계 카테고리나 복잡한 트리 구조를 넣지 않고, 현재 필요한 범위만 구현하기로 했다.

Product 생명주기

Product는 DRAFT 상태로 생성한다.

1
DRAFT -> ON_SALE -> STOPPED

사용할 상태는 DRAFT, ON_SALE, STOPPED 세 가지로 제한했다. 같은 상태로 판매 시작이나 중지를 다시 요청하면 실패시키지 않고 멱등 성공으로 처리한다.

삭제 API는 두지 않는다. Hard Delete와 Soft Delete 대신 판매 중지로 제거 요구를 표현하기로 했다. 상품은 이후 주문 이력과 연결될 수 있으므로 물리적으로 지우는 것보다 생명주기로 관리하는 편이 현재 프로젝트에 더 잘 맞는다고 판단했다.

가격 표현

가격은 KRW 정수 금액으로 다루고, 도메인에서는 long 기반 Money 값 객체를 사용하기로 했다.

단순히 필드 하나를 감싸기 위한 목적보다는 음수 가격 방지와 금액 관련 규칙을 한곳에 모으기 위한 선택이다.

조회 범위

일반 사용자 상품 목록은 Slice, 관리자 목록은 Page를 사용한다.

사용자 목록에서는 다음 페이지 존재 여부가 중요하고 전체 개수는 꼭 필요하지 않다. 반면 관리자 화면에서는 전체 건수와 페이지 정보가 필요할 수 있어 목적에 따라 조회 방식을 나누었다.

Product와 Inventory의 경계를 분리했다

가장 중요하게 본 부분은 Product와 Inventory의 책임이었다.

Product에는 재고 수량을 두지 않는다. 재고는 Inventory가 별도로 소유한다. 두 모듈은 서로의 Repository나 내부 Service에 직접 접근하지 않는다.

1
2
Product: 상품 정보, 가격, 판매 상태
Inventory: 판매 가능 수량, 재고 변경 이력

첫 버전에서는 Product 등록과 Inventory 등록도 분리한다. Product 판매 시작 시 Inventory가 등록되었는지를 강제로 검사하지 않기로 했다.

이 결정은 완벽한 운영 흐름을 한 번에 만들기 위한 것이 아니다. 아직 재고 차감과 결제 순서가 승인되지 않은 상태에서 Product가 Inventory 내부 구현에 의존하지 않게 하려는 선택이다.

초기에는 Inventory 등록 누락을 운영 절차로 방지해야 한다는 위험이 남는다. 이후 실제 흐름을 구현할 때 공개 Application Service나 Facade, 이벤트 중 어떤 방식으로 연결할지 별도 ADR로 결정할 수 있다.

ADR로 결정의 이유를 남겼다

설계 결과는 도메인 문서와 API·데이터베이스 초안에만 적지 않고 ADR로도 남겼다.

  • ADR-004: Product와 Category 모델
  • ADR-005: Product 가격과 Money 모델
  • ADR-006: Product 생명주기
  • ADR-007: 상품 목록 페이지네이션
  • ADR-008: Product와 Inventory 경계

문서가 현재 규칙을 설명한다면 ADR은 왜 그 선택을 했는지 보여준다. 나중에 요구사항이 바뀌어 결정을 수정하더라도 당시의 제약과 판단을 확인할 수 있다.

특히 재고나 결제처럼 여러 선택지가 있는 문제에서는 특정 방식을 코드에 먼저 넣기보다, 후보안과 확정안을 구분해 기록하는 것이 중요하다고 느꼈다.

아직 남아 있는 위험

문서와 설계를 정리했지만 구현이 끝난 것은 아니다.

  • Product와 Category 프로덕션 코드와 테스트는 아직 없다.
  • Flyway Migration 버전과 문자열 길이는 기존 Migration을 확인한 뒤 정해야 한다.
  • ProductImage 순서 전체 변경 시 Unique 제약 충돌을 피할 갱신 전략이 필요하다.
  • 상품명 부분 검색과 깊은 페이지 조회는 실제 데이터와 실행 계획을 보고 개선해야 한다.
  • Inventory 등록을 강제하지 않는 초기 운영 절차가 필요하다.
  • 기존 JwtTokenProviderTest는 테스트 대상 미초기화로 실패하고 있다.

이번 작업에서는 문서와 설계 정합성을 맞추는 데 집중했고, 보호 영역의 프로덕션 코드는 수정하지 않았다. ./gradlew check 통과 여부도 아직 확인하지 않았기 때문에 구현 단계에서 다시 검증해야 한다.

이번 작업에서 배운 점

AI 협업 규칙을 정리하면서 가장 크게 느낀 것은 좋은 결과를 얻으려면 좋은 프롬프트 하나보다 저장소 안의 지속적인 기준이 더 중요하다는 점이다.

AI에게 모든 것을 맡기는 것도, 반대로 아무것도 맡기지 않는 것도 내가 원하는 방식은 아니었다. 반복적인 문서 정리와 일반 구현은 도움을 받되, 프로젝트에서 반드시 공부해야 할 설계 판단은 직접 소유하고 싶었다.

이를 위해서는 다음 내용이 명확해야 했다.

  • 어떤 문서가 기준인지
  • 어떤 범위까지 수정할 수 있는지
  • 어떤 코드는 보호되는지
  • 구현 전에 누구의 승인이 필요한지
  • 완료 후 무엇을 검증하고 보고해야 하는지

하네스를 정비하니 AI에게 작업을 요청할 때도 범위가 구체적으로 바뀌었다. “Product를 만들어줘”가 아니라 목표, 구현 범위, 금지 범위, 현재 알고 있는 실패, 완료 후 보고 형식을 함께 전달할 수 있게 되었다.

마무리

이번 작업에서는 코드를 많이 작성하지 않았지만, 이후 코드를 안전하게 작성하기 위한 기반을 만들었다.

문서 충돌을 먼저 해결하고, Product와 Category의 정책을 정한 뒤, Product와 Inventory의 경계를 ADR로 남겼다. 덕분에 다음 구현에서 무엇을 만들지뿐 아니라 무엇을 아직 만들면 안 되는지도 설명할 수 있게 되었다.

앞으로는 승인된 ADR을 기준으로 Money, Category, Product 상태 전이부터 작은 단위로 구현할 예정이다. 재고, 결제, 인증, Kafka·Outbox는 보호 영역으로 유지하면서 내가 직접 설계하고 테스트하려고 한다.

AI와 협업할수록 중요한 것은 더 많은 코드를 빠르게 만드는 것이 아니라, 프로젝트의 결정권과 학습 목적을 잃지 않는 것이라고 느꼈다.

Leave a comment