이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Functional Requirement를 기능 목록이 아니라 API 계약과 상태 전이로 어떻게 정리하는가?
- 기능 범위, 제외 범위, 실패 응답, 중복 요청을 어떻게 문서에 고정하는가?
- 기능 요구사항 정리는 성능, 정합성, 장애 격리, 운영 복잡도 판단을 어떻게 바꾸는가?
개요
Functional Requirement는 시스템이 사용자에게 제공해야 하는 행동이다.
하지만 “알림을 보낸다”, “주문을 생성한다”, “피드를 보여준다” 같은 문장은 설계 입력으로 부족하다.
백엔드 설계에서는 기능을 caller, endpoint, command/query, 상태 전이, 실패 응답, 제외 범위로 바꿔야 한다.
이 과정을 거쳐야 동기 처리와 비동기 처리, DB 트랜잭션, queue, idempotency, 권한 검사의 위치가 보인다.
기능 요구사항의 단위
기능 요구사항은 화면 단위보다 사용자 결과 단위로 나누는 편이 좋다.
알림 시스템을 예로 들면 다음은 서로 다른 기능이다.
- 관리자가 알림 캠페인을 생성한다.
- 관리자가 발송 대상을 업로드한다.
- 관리자가 예약 발송 시간을 변경한다.
- 시스템이 대상자별 발송 작업을 생성한다.
- 사용자가 알림 수신 거부를 설정한다.
- 운영자가 실패한 발송 건을 재처리한다.
이 기능들은 같은 “알림”이라는 단어로 묶이지만 API 계약과 상태 전이가 다르다.
캠페인 생성은 관리자 권한과 중복 이름 검사가 중요하다.
대상자 업로드는 파일 크기, validation, partial failure가 중요하다.
발송 작업 생성은 fanout, queue, 중복 발송 방지가 중요하다.
기능을 API 계약으로 바꾸기
기능은 다음 표로 정리한다.
| 기능 | caller | command/query | 성공 기준 | 실패 응답 | 제외 범위 |
|---|---|---|---|---|---|
| 캠페인 생성 | admin | command | campaign id 생성 | 중복 이름, 권한 없음 | 실제 발송 |
| 대상자 업로드 | admin | command | upload job 접수 | 파일 형식 오류 | 즉시 전체 검증 |
| 캠페인 조회 | admin | query | 상태와 집계 반환 | 권한 없음 | 실시간 발송 통계 |
| 예약 취소 | admin | command | 발송 전 상태 취소 | 이미 발송 시작 | 발송된 메시지 회수 |
| 수신 거부 | user | command | user preference 저장 | 인증 실패 | 이미 발송된 알림 삭제 |
이 표의 목적은 endpoint를 빨리 정하는 것이 아니다.
각 기능의 성공 기준과 제외 범위를 명확히 해서 설계가 과도하게 커지는 것을 막는 것이다.
상태 전이
기능 요구사항에는 상태 전이가 포함되어야 한다.
알림 캠페인 상태는 다음처럼 정의할 수 있다.
DRAFT -> SCHEDULED -> DISPATCHING -> COMPLETED
DRAFT -> CANCELLED
SCHEDULED -> CANCELLED
DISPATCHING -> PARTIAL_FAILED
PARTIAL_FAILED -> COMPLETED상태 전이가 없으면 “취소 가능” 같은 요구사항이 모호해진다.
예를 들어 DISPATCHING 상태에서 취소를 허용하면 이미 queue에 들어간 메시지를 어떻게 처리할지 정해야 한다.
취소를 SCHEDULED까지만 허용하면 API는 단순해지지만 운영자가 긴급 중단할 수 없다는 비용이 생긴다.
API 예시
캠페인 생성 API는 실제 발송을 약속하지 않는다.
POST /admin/notification-campaigns
Idempotency-Key: campaign-create-20260701-001{
"campaignId": "cmp-1001",
"status": "DRAFT",
"createdAt": "2026-07-01T10:15:00+09:00"
}예약 발송 API는 접수와 발송 완료를 분리한다.
{
"campaignId": "cmp-1001",
"status": "SCHEDULED",
"scheduledAt": "2026-07-01T18:00:00+09:00",
"dispatchVisibility": "PENDING"
}이 계약은 성능과 장애 격리를 바꾼다.
사용자 응답은 빠르게 끝낼 수 있지만, 실제 발송 상태는 별도 job과 queue 지표로 운영해야 한다.
Spring 경계 예시
기능 요구사항이 명확하면 command와 query를 분리하기 쉽다.
public record CreateCampaignCommand(
Long adminId,
String title,
LocalDateTime scheduledAt,
String idempotencyKey
) {
}@Transactional
public CampaignResult create(CreateCampaignCommand command) {
adminPermission.check(command.adminId(), Permission.CAMPAIGN_WRITE);
idempotencyService.ensureFirstRequest(command.idempotencyKey());
Campaign campaign = Campaign.draft(command.title(), command.scheduledAt());
campaignRepository.save(campaign);
return CampaignResult.created(campaign.id(), campaign.status());
}이 코드는 기능 요구사항이 “캠페인 생성”임을 보여준다.
발송 대상 검증, queue fanout, 외부 provider 호출은 이 기능의 성공 기준에 넣지 않는다.
범위를 나누면 트랜잭션도 작아지고 실패 원인도 분리된다.
기능과 비기능의 경계
기능 요구사항은 “무엇을 한다”이고, 비기능 요구사항은 “어떤 품질로 한다”다.
예를 들어 다음처럼 분리한다.
| 문장 | 분류 | 이유 |
|---|---|---|
| 관리자는 캠페인을 예약할 수 있다 | Functional | 사용자가 수행할 행동 |
| 예약 발송은 1분 이내 시작되어야 한다 | Non-functional | 시간 품질 기준 |
| 캠페인은 발송 전 취소할 수 있다 | Functional | 상태 전이와 API |
| 발송 성공률은 99% 이상이어야 한다 | Non-functional | 운영 지표 |
| 수신 거부 사용자는 발송 대상에서 제외한다 | Functional | 비즈니스 규칙 |
| 수신 거부 이력은 3년 보존한다 | Non-functional/Constraint | 보존 조건 |
분류가 틀리면 설계도 흔들린다.
시간, 처리량, 가용성을 기능처럼 구현 목록에 넣으면 측정과 알림이 빠진다.
위험 신호
- “알림 기능”처럼 큰 단어만 있고 구체 API가 없다.
- 성공 응답은 있지만 실패 응답과 제외 범위가 없다.
- 상태 전이 없이 취소, 재시도, 재처리를 말한다.
- command와 query가 섞여 조회 API가 상태를 바꾼다.
- 기능 요구사항에 latency와 availability가 섞여 있지만 SLO가 없다.
- 중복 요청과 권한 실패를 기능 계약에서 빼 둔다.
개인 프로젝트 기준
개인 프로젝트에서는 기능 요구사항 표를 작게라도 만든다.
- caller
- endpoint 후보
- 성공 응답
- 실패 응답
- 상태 전이
- 제외 범위
이 표가 있으면 구현 코드가 단순해도 설계 설명이 선명해진다.
특히 포트폴리오에서는 “CRUD를 만들었다”보다 “예약 취소는 발송 전까지만 허용했다” 같은 판단이 더 중요하다.
기업 운영 기준
기업에서는 기능 요구사항이 운영 권한과 감사까지 이어진다.
- 누가 기능을 호출할 수 있는가?
- 상태 변경 감사 로그가 필요한가?
- 운영자가 수동 재처리할 기능이 있는가?
- API 응답 계약이 old/new client와 호환되는가?
- 기능 제외 범위를 고객지원과 운영팀이 알고 있는가?
기능 범위가 문서화되지 않으면 장애 중 운영자가 기대하지 않은 수동 조작을 하게 된다.
확인 질문
확인 질문
- 확인 질문: Functional Requirement를 기능 목록으로만 남기면 왜 부족한가?
- 답변: caller, 성공 기준, 실패 응답, 상태 전이, 제외 범위가 없으면 API 계약과 트랜잭션 경계를 정할 수 없기 때문이다.
- 확인 질문: 예약 발송 기능에서 취소 가능 상태를 정해야 하는 이유는 무엇인가?
- 답변: 발송 시작 후 취소를 허용할지에 따라 queue 중단, 중복 방지, 운영 재처리 구조가 달라지기 때문이다.
- 확인 질문: 기능 요구사항 정리가 바꾸는 설계 축은 무엇인가?
- 답변: API와 상태 전이를 선명하게 해 정합성과 장애 격리는 좋아지지만, 기능 범위와 예외 계약을 관리하는 운영 복잡도는 생긴다.