서비스를 설계할 때, '정책'과 '사양'을 구분하지 못하면 생기는 일
시스템, 정책, 비즈니스 로직, 서비스, 인터페이스, UI 로직, 사양. 이 개념들이 왜 따로 존재하는지, 구분하지 않으면 실무에서 어떤 일이 생기는지를 정리합니다.
같은 단어를 쓰는데 대화가 안 통하는 이유
수정 요청이 하나 왔어요. “이거 고쳐주세요.”
근데 이게 정책을 바꾸는 건지, API 스펙만 고치면 되는 건지 모르겠어서 회의가 한 시간 넘게 간 적 있잖아요. 결국 “일단 해보고 나서 판단하자”로 끝났는데, 나중에 보니까 처음부터 층을 잘못 짚은 거였어요.
이런 일이 반복될 때는 보통 개념 정의가 흔들리고 있을 때예요. 개념이 정확하면 대화가 짧아지고, 흔들리면 회의가 길어져요.
이 글은 서비스 설계에서 반복적으로 등장하는 개념들을 정리하는 시도예요.
먼저, ‘계(界)‘라는 단어부터
서비스 설계에서 경계를 가진 하나의 독립 단위를 계(界)라고 부를게요. 계 안에는 규칙이 있고, 계 바깥과는 소통 방식이 있어요.
이 계를 중심으로 개념들이 두 축으로 나뉘어요. 하나는 **“무엇이 있느냐”**이고, 다른 하나는 **“어떻게 잇느냐”**예요.
무엇이 있느냐 — 계 안의 이야기
시스템 = 계 그 자체
“이것과 저것은 다르다”는 경계 그 자체예요. 하나의 개념이 독립적으로 존재할 수 있는 경계를 가진 단위예요.
“회원”, “결제”, “테이스팅 기록” — 이런 것들이 각각 하나의 계예요. 무엇을 하나의 계로 볼 것인가를 정하지 않으면, 그 다음 단계는 전부 흔들려요. 이 경계를 나누는 행위 자체가 설계의 시작이에요.
정책 = 계에 대한 정의
“이 계는 이런 계다”를 선언하는 것이에요. 계가 무엇인지, 계의 규칙이 무엇인지, 계와 계 사이의 소통이 어떤 것인지를 정의해요.
여기서 핵심이 하나 있어요. 정책이 정책인 이유는 계의 정의 그 자체이기 때문이에요. 누가 결정했느냐는 별개의 문제예요.
- “CoffeeDuck과 WineDuck은 분리된 서비스다” → 정책이에요. 두 개의 계가 존재한다는 선언.
- “테이스팅 기록은 본인만 수정 가능하다” → 정책이에요. 이 계의 규칙을 정의하는 것.
- “CoffeeDuck 사용자는 WineDuck 데이터에 접근할 수 없다” → 정책이에요. 계와 계 사이 소통의 정의.
정책 안에도 위계가 있어요. “계의 존재 자체를 선언하는 정책”이 바뀌면 그 계의 모든 하위 규칙이 영향받아요. “계 안의 개별 규칙”이 바뀌면 해당 규칙의 하위만 영향받고요.
비즈니스 로직 = 계 안의 판단 규칙
정책을 계 안에서 실제로 집행하는 판단 규칙이에요. 정책이 “이 계는 이런 계다”라고 선언했다면, 비즈니스 로직은 “그럼 이 요청은 허용인가 거부인가”를 판단하는 조건, 분기, 계산이에요. 정책에서 파생되지만, 정책 그 자체는 아니에요.
- 정책: “충분한 데이터가 쌓여야 의미 있는 분석을 보여준다.”
- 비즈니스 로직: 그 정의를 실행하는 구체적인 조건과 판단.
여기서 기준값이 계의 정의에 해당하는지, 실행 규칙에 해당하는지는 — 서비스마다, 맥락마다 판단이 필요한 지점이에요.
정책과 비즈니스 로직, 경계가 흐릿할 때
둘을 구분하는 가장 빠른 방법은 **“선언인가, 집행인가”**를 묻는 거예요.
- “테이스팅 기록은 본인만 수정 가능하다” → 정책. 계의 규칙을 선언한 것.
- “수정 요청이 오면 JWT의 user_id와 tasting의 owner_id를 비교해서 다르면 거부한다” → 비즈니스 로직. 그 선언을 집행하는 방법.
정책이 바뀌면 비즈니스 로직도 바뀌어야 해요. 반대는 성립하지 않아요. “소유자 확인 방식을 JWT에서 세션으로 바꾸자”는 비즈니스 로직 변경이지만, “본인만 수정 가능하다”는 정책은 그대로예요.
어떻게 잇느냐 — 계와 계 사이의 이야기
서비스 = 연결
“누구와 누구를 잇느냐”의 문제예요. 사용자와 시스템, 또는 시스템과 시스템 사이를 잇는 것이에요.
“테이스팅 기록하기” 기능을 예로 들면, 사용자(기록하고 싶은 사람)와 테이스팅 계(기록을 보관하는 시스템)를 잇는 것이 서비스예요. 연결이 존재한다는 것 자체가 서비스의 정의예요.
인터페이스 = 연결의 규약
연결이 생기면 자연스럽게 따라오는 질문이 있어요. “그 연결은 어떤 형식으로 소통하느냐?” 이게 인터페이스예요.
연결에서 상대와 ‘어떤 형식, 어떤 규칙으로 소통할 것인지’를 정하는 규약이에요. 소통의 내용이 아니라 소통의 방식 자체를 정하는 것이에요.
그리고 이 규약은 상대가 누구냐에 따라 형태가 달라져요.
- 상대가 사람이면 → 유저 인터페이스(UI). 화면, 버튼, 텍스트, 흐름으로 소통해요.
- 상대가 시스템이면 → API. 엔드포인트, 요청/응답 형식, 인증 방식으로 소통해요.
UI와 API는 별개의 개념이 아니에요. 둘 다 인터페이스이고, 상대가 다를 뿐이에요.
커피 테이스팅 기록 서비스로 보면:
- 사용자가 테이스팅을 기록할 때 → UI: 맛 프로파일 슬라이더, 향 노트 태그, 저장 버튼이 있는 화면
- 프론트엔드가 백엔드에 기록을 저장할 때 → API: POST /api/tastings, JSON body로 점수/노트/날짜를 보냄
같은 “테이스팅 기록”이라는 연결인데, 상대가 사람이냐 시스템이냐에 따라 인터페이스가 달라지는 거예요.
사양 = 소통의 구체 명세
인터페이스가 정한 형식 안에서, ‘구체적으로 뭘 주고받느냐’를 적은 것이에요. 인터페이스는 소통 방식을 정하고, 사양은 그 방식 안의 내용을 채워요.
-
인터페이스(API): “이 연결은 REST API로 소통한다”
-
사양: “PUT /api/tastings/:id 호출 시 JWT의 user_id와 owner_id 일치 시 200, 불일치 시 403”
-
인터페이스(UI): “사용자는 웹 화면에서 와인을 등록한다”
-
사양: “와인 종류 선택은 Red/White/Rosé/Sparkling 4개 버튼이고, 하나를 선택해야 다음 단계로 넘어간다. 미선택 시 저장 버튼은 비활성.”
사양은 뒤에 나와요. 뭘 판단하고, 어떤 흐름으로 처리하고, 어떤 규약으로 소통하는지가 먼저 정해져야, “구체적으로 뭘 주고받을 건지”를 적을 수 있으니까요.
인터페이스와 사양, 순서가 있어요
인터페이스가 먼저예요. “어떤 형식으로 소통하느냐”가 정해져야, “그 형식 안에서 뭘 주고받느냐”를 적을 수 있으니까요.
변경으로 보면 명확해져요:
- “REST에서 GraphQL로 바꾸자” → 인터페이스 변경. 소통 형식 자체가 달라지는 것. 사양을 전부 다시 써야 해요.
- “응답에
created_at필드를 추가하자” → 사양 변경. 기존 형식은 그대로이고 내용만 바뀌는 것.
인터페이스가 바뀌면 사양 전체가 영향받아요. 사양이 바뀌면 인터페이스는 그대로예요.
UI 로직 = 사용자와의 소통 흐름
사용자가 화면에서 어떤 순서로 행동하고, 그에 따라 어떤 피드백을 받는가의 흐름이에요. 비즈니스 로직이 “계 안의 판단 규칙과 계 간 처리 절차”라면, UI 로직은 “사용자와 인터페이스 사이에서 일어나는 실행 순서”예요.
사용자가 와인 테이스팅을 기록하는 상황으로 보면:
- 사용자가 와인 상세 화면에서 “간단히 기록하기”를 누름
- Quick Tasting 폼이 뜸 — 날짜, 와인 이름, 타입, 상황 태그, 점수, 한 줄 메모를 입력
- 저장을 누르면 로딩 표시가 뜨고
- 성공하면 와인 상세 화면으로 돌아가고, 실패하면 에러 메시지가 표시됨
이 전체 흐름이 UI 로직이에요. “어떤 화면에서 시작해서, 어떤 입력을 하고, 어떤 결과를 보게 되는가”의 순서.
한편 3번에서 실제로 뒤에서 일어나는 일 — 인증 확인(회원 계) → 와인 존재 확인(와인 계) → 테이스팅 저장(테이스팅 계) → 통계 갱신(프로필 계) — 이건 비즈니스 로직이에요. 계 간 처리 순서도, 각 계 안의 판단도 모두 비즈니스 로직의 범위예요.
비즈니스 로직과 UI 로직, 어떻게 구분해요?
두 개념은 관점이 달라요.
- 비즈니스 로직: 계 안에서, 그리고 계와 계 사이에서 작동해요. “이 요청이 허용되는가?” “이 요청이 어떤 순서로 어떤 계를 거쳐 처리되는가?” 판단 규칙과 처리 절차예요.
- UI 로직: 사용자와 화면 사이에서 작동해요. “이 버튼을 누르면 어디로 가는가?” “성공하면 뭘 보여주는가?” 사용자 경험의 흐름이에요.
둘이 섞이면 어떻게 되냐면 — “이 쿠폰이 적용 가능한가”라는 판단 규칙(비즈니스 로직)이 화면 표시 로직(UI 로직) 안에 박혀 있으면, 쿠폰 정책이 바뀔 때 화면 코드를 뜯어야 해요. 비즈니스 로직은 계 안에 있어야 하고, UI 로직은 그 결과를 사용자에게 어떻게 보여줄지만 알아야 해요.
한 사례로 전부 관통하면
“테이스팅 기록은 본인만 수정 가능하다”를 전체 흐름으로 분해해볼게요.
정책: 테이스팅 기록은 본인만 수정 가능하다.
비즈니스 로직: 수정 요청이 오면 소유자 여부를 판단한다. 인증 확인(회원 계) → 소유자 대조(테이스팅 계) → 성공 시 저장, 실패 시 거부.
서비스: 사용자(기록을 고치고 싶은 사람)와 테이스팅 계(기록을 보관하는 시스템)를 잇는 연결.
인터페이스: 이 연결에서 상대가 사람인 쪽과 시스템인 쪽이 각각 있어요.
- UI 인터페이스: 사용자는 웹 화면에서 테이스팅 기록을 수정한다.
- API 인터페이스: 프론트엔드는 REST API로 백엔드와 소통한다.
UI 로직: 사용자가 내 기록 화면에서 수정 버튼을 누름 → 수정 폼이 뜸 → 저장 누르면 로딩 → 성공 시 상세 화면으로 복귀, 실패 시 에러 메시지 표시.
사양: 인터페이스와 UI 로직의 구체 명세예요.
- UI 사양: 본인 기록에만 수정 버튼이 보이고, 타인 기록에는 수정 버튼이 없어요. 수정 폼에는 저장/취소 버튼이 있고요.
- API 사양: PUT /api/tastings/:id, JWT user_id와 owner_id 일치 시 200, 불일치 시 403.
하나의 정책에서 시작해서, 판단 → 연결 → 규약 → 흐름 → 명세까지 전부 이어져요.
순서
이 개념들은 이 순서로 흐르는 거예요.
정책 → 비즈니스 로직 → 인터페이스 → UI 로직 → 사양 → 구현
변경이 어디서 시작됐는지를 알면 영향 범위가 바로 보여요.
- 정책이 바뀌면 → 그 아래 전부 영향
- 비즈니스 로직이 바뀌면 → 인터페이스 + UI 로직 + 사양 + 구현 영향
- 인터페이스가 바뀌면 → UI 로직 + 사양 + 구현 영향. “REST에서 GraphQL로 바꾸자”는 이 층이에요.
- UI 로직이 바뀌면 → 사양 + 구현 영향. “수정 완료 후 목록으로 갈지 상세로 갈지”는 이 층이에요.
- 사양이 바뀌면 → 구현만 영향. “응답에 필드 하나 추가하자”는 이 층이에요.
왜 이걸 구분해야 하는가
구분하지 않으면 이런 일이 생겨요.
1. 정책 변경을 사양 수정으로 처리하려다 반복 수정이 생겨요. “이 기능을 없애자”는 정책 변경인데, UI만 숨기면 끝이라고 생각하면 나중에 데이터/API/로직에서 계속 문제가 터져요.
2. 비즈니스 로직과 UI 로직을 섞으면 코드가 얽혀요. “이 조건이면 할인 적용”이라는 판단 규칙이 화면 표시 로직 안에 섞여 들어가면, 할인 정책이 바뀔 때 화면 코드 전체를 뜯어야 해요.
3. 사양 없이 구현하면 “이게 맞는 건지” 판단이 안 돼요. QA할 때 “이건 버그야, 원래 이래?”라는 질문이 나오면, 대부분 사양이 없거나 흐릿한 거예요.
결국 이걸 구분하는 건 “깔끔하게 만들자”가 아니라, **“변경이 생겼을 때 어디부터 어디까지 손대야 하는지를 빠르게 판단하자”**가 목적이에요.
정리
| 축 | 개념 | 한 줄 정의 |
|---|---|---|
| 계(界) | 시스템 | 계 그 자체. 경계를 가진 독립 단위 |
| 계(界) | 정책 | 계에 대한 정의. 규칙과 소통의 선언 |
| 계(界) | 비즈니스 로직 | 계 안의 판단 규칙 + 계 간 처리 절차. 정책의 실행 |
| 연결 | 서비스 | 연결. 누구와 누구를 잇느냐 |
| 연결 | 인터페이스 | 연결의 규약. 상대가 사람이면 UI, 시스템이면 API |
| 연결 | UI 로직 | 사용자와의 소통 흐름. 화면 → 액션 → 피드백의 실행 순서 |
| 연결 | 사양 | 소통의 구체 명세. 인터페이스의 구체적 내용 |
순서: 정책 → 비즈니스 로직 → 인터페이스 → UI 로직 → 사양 → 구현
서비스를 설계하거나, 이슈를 분류하거나, 변경 요청을 받았을 때 — “이건 어느 층이야?”를 먼저 찍어보세요. 그 한 마디에서 다음 액션이 나와요.