AI 시대 백엔드 개발을 효율화하는 아키텍처 구상
요약
AI 시대 백엔드 개발의 핵심은 코드 작성보다 일관된 아키텍처 관리입니다. 본 글은 코드를 SSOT로 삼지 않고, 요구사항, 유스케이스, 데이터 모델 등 인간이 정의하는 영역을 중심으로 아키텍처를 구상했습니다. ZenStack과 OpenAPI를 활용하여 기계적인 CRUD 및 권한 부여는 도구에 맡기고, 복잡한 업무 로직은 별도로 분리하는 것이 핵심입니다.
핵심 포인트
- 코드를 SSOT로 삼지 않고, 요구사항/유스케이스/데이터 모델을 인간이 관리해야 합니다.
- ZenStack의 ZModel을 활용하여 DB 스키마와 데이터 관련 정보를 통합적으로 정의할 수 있습니다.
- 단순 CRUD는 도구에 맡기고, 복잡한 업무 로직(예: 승인)은 OpenAPI로 분리하여 설계하는 것이 효율적입니다.
최근 AI를 활용한 코드 생성 능력이 상당히 실용적인 수준에 이르렀습니다.
CRUD API 정도의 기능이라면, AI에게 사양(specification)만 전달해도 상당 부분 구현해 줍니다.
반면, 실제 업무 시스템을 개발할 때는 코드를 작성하는 것보다 더 어려운 것이 바로,
- DB 설계
- 권한 부여 (認可)
- API 설계
- 업무 규칙
- 유스케이스
- 문서화
- 테스트
등의 요소들을 어떻게 일관성 있게 관리할 것인가입니다.
특히 문제가 되는 것은, 같은 사양이 여러 곳에 존재하게 되는 경우입니다.
예를 들어,
사양서
↓
OpenAPI
...
이 각각 비슷한 정보를 담고 있다면, 점차적으로 괴리가 생기게 됩니다.
따라서 이번에는,
인간은 '무엇을 만들지'를 정의하고, AI와 도구에 '어떻게 만들지'를 맡긴다는 관점으로 백엔드 개발 아키텍처를 구상해 보았습니다.
이번에 생각한 구성은 다음과 같습니다.
인간이 관리하는 SSOT
┌──────────────────────────────────────────────────┐
│ │
...
핵심 포인트는, 코드를 SSOT(Single Source of Truth)로 삼지 않는다는 것입니다.
인간이 관리해야 하는 것은,
- 요구사항 (要件)
- 유스케이스
- 데이터 모델
- API 계약
입니다.
그 정보를 바탕으로 AI나 코드 생성 도구에 구현을 맡기는 방식입니다.
여기서 중요한 점은,
'SSOT를 하나로 만들 필요는 없다'는 것입니다.
오히려, 서로 다른 종류의 정보를 하나의 파일에 담으면 무너집니다(파탄합니다).
그래서 각 책임 영역별로 SSOT를 갖게 합니다.
| SSOT | 관리하는 것 |
|---|---|
| Requirements | 시스템으로서 무엇이 필요한가 |
| ... |
이러한 사고방식이 이번 아키텍처의 중심입니다.
DB 설계에는 ZenStack의 ZModel을 사용합니다.
예를 들어,
model Project {
id String @id @default(cuid())
name String
...
여기에,
- 테이블
- 컬럼
- 타입
- 리레이션
- 제약
- 권한 부여 (認可)
등, 데이터에 관한 정보를 집약할 수 있습니다.
DB 설계서를 별도로 손으로 만들 필요가 없습니다.
schema.zmodel
│
├── DB schema
...
이런 형태로 구성할 수 있습니다.
업무 시스템에서는 CRUD API가 대량으로 발생합니다.
예를 들어,
GET /projects
POST /projects
GET /projects/:id
...
이것을 매번 손으로 구현하는 것은 상당히 아깝습니다.
그래서 ZenStack에 맡깁니다.
schema.zmodel
↓
ZenStack
...
이를 통해 CRUD 구현 비용을 크게 줄일 수 있습니다.
게다가, 권한 부여 규칙도 ZModel 측에서 정의할 수 있습니다.
즉,
CRUD
권한 부여 (認可)
ORM
...
이처럼 비교적 기계적인 부분은 도구에 맡기는 것입니다.
여기서가 상당히 중요합니다.
예를 들어,
POST /projects/:id/approve
은 단순한 CRUD가 아닙니다.
'프로젝트의 status를 변경한다'는 DB 작업뿐만 아니라,
- 현재 상태를 확인하는 과정
- 승인 권한을 확인하는 과정
- 승인자를 기록하는 과정
- 승인 일시를 기록하는 과정
- 필요하다면 이벤트를 발생시키는 과정
등의 업무 처리가 포함됩니다.
이것을 CRUD API에 억지로 넣으면, API 설계가 점점 복잡해집니다.
그래서,
CRUD API
↓
ZenStack
...
와 분리합니다.
업무 API는 OpenAPI로 정의합니다.
paths:
/projects/{projectId}/approve:
post:
...
여기서는 HTTP API에 대해,
- URL
- HTTP method
- Request
- Response
- Status Code
- operationId
를 정의합니다.
중요한 것은, OpenAPI에 업무 로직을 쓰지 않는 것입니다.
OpenAPI는 어디까지나,
'이 시스템에는 이런 API가 있다'라는 계약일 뿐입니다.
HTTP 서버에는 Fastify를 사용합니다.
Fastify
│
┌────────────┴────────────┐
...
Fastify를 시스템 전체의 HTTP 레이어로 사용함으로써,
- 인증
- Middleware / Hook
- HTTP 로깅
- 유효성 검사(Validation)
- 라우팅(Routing)
- 에러 처리(Error handling)
등을 한 곳에 모을 수 있습니다.
OpenAPI를 손으로 Fastify의 route에 옮겨 적는 것도 피하고 싶습니다.
그래서 OpenAPI에서 Fastify의 route를 구성합니다.
예를 들어,
operationId: approveProject
라는 정의로부터,
POST /projects/:projectId/approve
↓
approveProject
...
와 같은 연결을 만듭니다.
Fastify에는 OpenAPI와 연동하는 에코시스템이 있어, OpenAPI를 기점으로 route를 구성할 수 있습니다.
이를 통해,
OpenAPI
↓
Fastify route
...
라는 흐름을 만들 수 있습니다.
여기서 조금 의외의 설계를 합니다.
UseCase 자체는 SSOT(Single Source of Truth)로 삼지 않습니다.
예를 들어,
export class ApproveProjectUseCase {
async execute(command: {
projectId: string;
...
와 같은 구현이 됩니다.
언뜻 보기에는,
UseCase도 문서에서 자동 생성해야 하는 것 아닌가?
라고 생각합니다.
하지만, 여기서는 억지로 자동화하지 않습니다.
여기서 AI가 등장합니다.
사람이,
UC-PROJECT-APPROVE.md
를 정비합니다.
예를 들어,
# 프로젝트 승인
## 액터(Actor)
프로젝트 관리자
...
그리고 AI에게,
이 UseCase 사양을 구현해 주세요.
DB 모델은 schema.zmodel을 참조해 주세요.
API 계약은 business-api.yml을 참조해 주세요.
기존의 구현 패턴을 따라 주세요.
테스트도 만들어 주세요.
라고 요청합니다.
그러면,
UseCase document
│
▼
...
를 생성할 수 있습니다.
업무 사양과 구현은 별개이기 때문입니다.
예를 들어,
「신청된 프로젝트만 승인할 수 있다」
라는 규칙이 업무 사양입니다.
이를,
if (project.status !== 'SUBMITTED') {
throw new Error(...);
}
라고 쓰는 것은 구현 방법입니다.
미래에,
- TypeScript에서 다른 언어로 변경하는 경우
- ORM을 변경하는 경우
- 아키텍처를 변경하는 경우
- 에러 처리를 변경하는 경우
등이 생겨도,
「신청된 프로젝트만 승인할 수 있다」
라는 업무 규칙은 변하지 않습니다.
따라서,
업무 사양을 SSOT로 삼고, 구현은 교체 가능하게 만드는
라는 생각입니다.
여기 또한 중요합니다.
AI에게,
프로젝트 승인 API를 만들어 주세요
라고 부탁하면, AI가 업무 규칙을 추측해 버립니다.
이것은 위험합니다.
대신에,
Requirements
+
UseCase document
...
로 합니다.
AI는,
'무엇을 만들지'
가 아니라,
'정해진 사양을 어떻게 구현할지'
를 담당합니다.
UseCase document에,
## 업무 규칙
- SUBMITTED의 프로젝트만 승인할 수 있다
- DRAFT는 승인할 수 없다
...
라고 적혀 있다면,
AI에게,
이 업무 규칙을 포괄하는 테스트를 만들어 주세요.
라고 요청할 수 있습니다.
그러면,
UseCase document
│
├──────────→ UseCase
...
가 됩니다.
이 구조로 하면, 업무 사양과 테스트의 대응 관계도 명확해집니다.
이 구성에서의 개발 플로우는,
① 요구사항 정리
↓
② UseCase 정의
...
가 됩니다.
사람이 코드를 한 줄씩 작성하는 것이 목적이 아니라,
사람이 사양을 올바르게 정의하고, AI에게 구현을 위임하는
것을 목적으로 하고 있습니다.
이 아키텍처에서는 각 도구의 책임(責務)을 상당히 명확히 합니다.
Requirements
│
│ 「무엇이 필요한가」
...
이 경계가 중요합니다.
예를 들어 AI에게 DB 설계까지 생각하게 하는 것이 아니라,
schema.zmodel
을 제공합니다.
API 사양까지 AI에게 생각하게 하는 것이 아니라,}<tool_call|>{
비즈니스 로직(business logic)을 AI가 생각하게 하는 것이 아니라,
usecase.md
을 제공합니다.
CRUD는 ZenStack을 사용하고,
HTTP 라우팅은 Fastify/OpenAPI를 사용합니다.
타입 정의나 클라이언트 코드도 OpenAPI로부터 생성할 수 있습니다.
AI에게는 UseCase 구현과 테스트를 맡길 수 있습니다.
인간이 관리하는 명세(specification)를 명확히 합니다.
UseCase
schema.zmodel
business-api.yml
이 명세의 중심입니다.
코드는 이 명세로부터 파생되는 것으로 생각합니다.
AI에게 방대한 기존 코드를 읽게 하면서,
"어렴풋이 이 시스템처럼 구현해 줘"라고 하는 것이 아니라,
UseCase
+
Data Model
...
을 입력으로 제공할 수 있습니다.
이는 AI에게도 다루기 쉬운 형식입니다.
결국, 이 아키텍처로 하고 싶은 것은,
소프트웨어 개발에서 '인간이 생각하는 부분'과 '기계가 작업하는 부분'을 분리하는 것입니다.
인간이 할 일:
요건(Requirement)을 결정한다
비즈니스 규칙(Business Rule)을 결정한다
데이터 모델(Data Model)을 결정한다
...
기계가 맡길 일:
CRUD 구현
HTTP 라우팅
타입 생성
...
이러한 분담입니다.
궁극적으로는 이런 개발 환경을 목표로 합니다.
HUMAN
│
┌───────────┼───────────┐
...
AI에 의해 '코드 작성 비용'이 낮아지는 한편, 앞으로 중요해질 것은 무엇을 만들 것인지 기계에게 정확하게 전달하는 것이라고 생각합니다.
이를 위해서는 AI가 코드를 작성하기 전에,
비즈니스 명세(Business Specification)
- 데이터 모델(Data Model)
- API 계약(API Contract)
을 명확한 형식으로 관리해야 합니다.
이번에 구상한 아키텍처에서는,
인간이 명세를 관리하고, 툴이 기계적인 부분을 생성하며, AI가 구현을 담당하는
역할 분담을 목표로 했습니다.
특정 업무 시스템에 국한되지 않고, CRUD가 많고 비즈니스 API도 존재하는 일반적인 웹 백엔드라면 응용할 수 있을 것이라고 생각합니다.
아직 실제 개발을 통해 검증하는 과정이므로, 특히 다음 부분들에 대해서는 앞으로 더 고민해보고 싶습니다.
UseCase 문서를 어디까지 형식화할 것인지
- AI에게 어느 정도까지 구현을 맡길 것인지
- OpenAPI와 UseCase의 경계를 어떻게 할 것인지
- 명세 변경 시 어떤 SSOT(Single Source of Truth)를 변경할 것인지
이 부분들은 앞으로 추가로 검토하고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기