비즈니스 로직
고친 사람 github-actions[bot]
비즈니스 로직은 프로그램이 업무 규칙에 따라 내리는 판단과 계산을 맡습니다. 쇼핑몰에서는 배송비를 얼마로 매길지를 이 코드가 정합니다. 화면이나 데이터베이스를 바꿔도 이 규칙은 바뀌지 않습니다. 그래서 개발자는 이 코드를 나머지 기술 코드와 분리해 두려고 합니다.
쉽고 빠른 이해
비즈니스 로직은 업무가 정한 규칙을 코드로 옮긴 부분입니다. 「5만 원 넘게 사면 배송비를 받지 않는다」를 계산하는 if 문 하나가 그 예입니다.
이 코드가 요청을 받는 코드나 데이터베이스 코드와 섞이면 곤란해집니다. 같은 규칙이 여러 파일에 복사됩니다. 한 곳만 고치면 서로 어긋납니다. 규칙 하나를 시험하려고 서버와 데이터베이스를 함께 띄워야 합니다.
그래서 코드를 흔히 세 계층으로 나눕니다.
- 요청을 받는 코드는 입력을 읽고 결과를 돌려주기만 합니다
- 가운데 코드가 규칙을 따지고 일의 순서를 정합니다
- 저장하는 코드는 데이터베이스에 읽고 쓰기만 합니다
대가는 코드가 여러 계층으로 갈라진다는 것입니다. 규칙이 몇 개 없는 프로그램에서는 가운데 계층이 호출을 넘겨주기만 하는 빈 껍데기가 됩니다.
상세
은행 창구에는 직원이 따르는 규정이 있습니다. 잔액보다 많이 내주지 않는다는 것, 하루에 내줄 수 있는 한도가 있다는 것 같은 규정입니다. 창구가 현금인출기로 바뀌어도 이 규정은 같습니다. 장부가 종이에서 전산으로 옮겨 가도 마찬가지입니다.
프로그램에 옮기면 이 규정이 비즈니스 로직입니다. 창구와 장부는 요청을 받는 코드와 데이터베이스에 해당합니다.
업무 규칙과 기술 코드
비즈니스 로직은 소프트웨어가 맡은 업무의 규칙을 코드로 옮긴 부분입니다. 업무 로직이나 도메인 로직이라고도 부릅니다. 도메인은 소프트웨어가 다루는 업무 분야를 말합니다.
쇼핑몰이라면 아래 셋이 비즈니스 로직입니다.
- 주문 수량은 한 개 이상이어야 한다
- 이미 발송한 주문은 취소할 수 없다
- 5만 원 넘게 사면 배송비를 받지 않는다
프로그램에는 이런 규칙 말고도 코드가 많습니다. 요청을 읽는 코드, 응답을 만드는 코드, 데이터베이스에 읽고 쓰는 코드가 그렇습니다. 이것들은 규칙을 실어 나르는 기술 코드입니다.
둘을 가르는 질문
둘을 가르는 질문은 하나입니다. 그 결정을 누가 내리느냐입니다. 업무를 맡은 사람이 정하면 비즈니스 로직입니다. 개발자가 기술을 골라 정하면 기술 코드입니다.
| 결정 | 누가 정하나 | 어느 쪽 |
|---|---|---|
| 5만 원 넘게 사면 배송비 0원 | 운영 담당자 | 비즈니스 로직 |
| 발송한 주문은 취소 불가 | 운영 담당자 | 비즈니스 로직 |
| 주문을 어느 데이터베이스에 저장하나 | 개발자 | 기술 코드 |
| 응답을 어떤 모양의 데이터로 보내나 | 개발자 | 기술 코드 |
표에서 볼 것은 기준이 기술의 종류가 아니라 결정한 사람이라는 점입니다. 배송비 기준이 3만 원으로 내려가면 코드를 고칠 이유는 업무 쪽에서 옵니다.
입력 검증은 두 쪽에 걸칩니다. 입력 검증은 들어온 값을 받아들여도 되는지 확인하는 일입니다. 수량 칸에 숫자가 들어왔는지 보는 것은 기술 코드입니다. 요청을 읽어 들이는 일의 일부이기 때문입니다.
수량이 한 개 이상인지 보는 것은 비즈니스 로직입니다. 한 개 미만을 막자는 것은 업무가 정한 규칙입니다.
규칙 판단과 순서 조율
비즈니스 로직 안에도 두 가지 일이 섞여 있습니다. 하나는 규칙 자체를 판단하는 일입니다. 「이 주문은 취소할 수 있나」에 답하는 코드가 그렇습니다.
다른 하나는 일의 순서를 정하는 일입니다. 먼저 주문을 저장소에서 불러옵니다. 다음에 취소할 수 있는지 묻습니다. 끝으로 바뀐 주문을 저장합니다.
이 코드는 스스로 판단하지 않고 차례만 세웁니다. 이렇게 순서를 세우는 코드를 서비스라고 부릅니다.
sequenceDiagram
participant S as 서비스
participant O as 주문
participant R as 저장소
S->>R: 주문을 불러온다
R-->>S: 주문
S->>O: 취소할 수 있나
O-->>S: 된다
S->>R: 바뀐 주문을 저장한다
그림에서 판단을 내리는 쪽은 주문입니다. 취소해도 되는지는 주문 객체의 취소 메서드가 답합니다. 서비스는 그 답에 따라 다음 차례로 넘어갑니다.
앞쪽 판단만 좁게 도메인 로직이라 부르기도 합니다. 그때 뒤쪽 순서 조율은 애플리케이션 로직이라 부릅니다. 둘을 합쳐 비즈니스 로직이라고 부르는 경우도 많습니다.
계층으로 분리하기
비즈니스 로직을 기술 코드와 분리하는 흔한 방법은 코드를 계층으로 나누는 것입니다. 계층은 코드를 맡은 일에 따라 위아래로 가른 묶음입니다. 위 계층은 아래 계층을 부릅니다. 아래 계층은 위를 모릅니다.
레이어드 아키텍처가 대표입니다. 레이어드는 코드를 세 계층으로 나눕니다. 요청을 받는 표현 계층, 규칙을 따지는 업무 계층, 데이터를 저장하는 영속 계층입니다. 영속은 프로그램이 꺼져도 데이터가 남아 있다는 뜻입니다.
표현 계층의 코드 이름은 컨트롤러입니다. 업무 계층의 코드는 앞에서 본 서비스입니다. 영속 계층의 코드 이름은 리포지토리입니다. 앞 절 그림의 저장소가 바로 이것입니다.
flowchart TD
A["요청"] --> C["표현 계층 · 컨트롤러"]
C --> S["업무 계층 · 서비스 · 비즈니스 로직"]
S --> R["영속 계층 · 리포지토리"]
R --> D[("데이터베이스")]
비즈니스 로직은 가운데 업무 계층에 놓입니다. 요청 형식이 바뀌면 표현 계층만 고칩니다. 저장 방식이 바뀌면 영속 계층만 고칩니다.
헥사고날과 클린 아키텍처는 한 걸음 더 갑니다. 비즈니스 로직을 한가운데 둡니다. 데이터베이스와 웹 같은 기술 코드는 전부 바깥에 둡니다.
레이어드와 다른 점은 누가 누구의 이름을 아느냐입니다. 레이어드의 업무 계층은 영속 계층을 직접 부르므로 리포지토리의 이름을 압니다. 헥사고날의 안쪽은 필요한 기능을 인터페이스로만 적어 둡니다. 바깥의 데이터베이스 코드가 그 인터페이스를 구현합니다.
이렇게 하면 안쪽 코드는 바깥 기술의 이름을 하나도 모릅니다. 데이터베이스 코드 없이도 안쪽만 따로 시험할 수 있습니다. 이처럼 의존 방향을 안쪽으로 뒤집는 원칙을 의존성 역전 원칙이라고 합니다.
코드로 본 비즈니스 로직
분리해 둔 비즈니스 로직이 어떻게 생겼는지 배송비 규칙 하나로 봅니다. 주문 금액을 받아 배송비를 돌려주는 메서드입니다.
int shippingFee(int total) {
if (total > 50_000) return 0;
return 3_000;
}
이 메서드에는 요청도 데이터베이스도 안 나옵니다. 금액을 받아 배송비를 돌려줄 뿐입니다. 그래서 서버를 띄우지 않고 바로 불러서 결과를 확인할 수 있습니다.
shippingFee(62_000); // 0
shippingFee(18_000); // 3000
이처럼 코드 조각 하나만 따로 불러 확인하는 시험을 단위 테스트라고 합니다. 정해 둔 입력을 넣고 맞는 결과가 나오는지 봅니다. 비즈니스 로직이 기술 코드와 분리돼 있으면 규칙마다 이런 시험을 붙일 수 있습니다.
섞였을 때 생기는 일
반대로 이 if 문이 컨트롤러 안에 들어가 있다고 해 봅시다. 쇼핑몰 앱과 관리자 화면에 주문을 받는 컨트롤러가 하나씩 있다고 합시다. 그러면 같은 배송비 계산이 두 곳에 들어갑니다. 아래 표가 이렇게 섞였을 때 생기는 일입니다.
| 섞인 모양 | 생기는 일 |
|---|---|
| 같은 규칙이 컨트롤러 여러 개에 복사된다 | 한 곳만 고치면 앱과 관리자 화면의 배송비가 달라진다 |
| 규칙이 요청 처리 코드 안에 있다 | 규칙 하나를 시험하려고 서버를 띄우고 요청을 보내야 한다 |
| 규칙이 데이터베이스 질의문 안에 있다 | 데이터베이스를 바꾸면 규칙까지 다시 옮겨 적어야 한다 |
셋 다 규칙이 바뀌는 이유와 기술이 바뀌는 이유가 한 코드에 겹쳐서 생깁니다. 바뀌는 이유가 다른 코드끼리 나눠 두는 원칙을 관심사 분리라고 합니다.
서버 밖에 놓이는 규칙
비즈니스 로직이 늘 서버의 업무 계층에만 있는 것은 아닙니다. 두 군데로 나가곤 합니다.
첫째는 프론트엔드입니다. 프론트엔드는 사용자가 보는 브라우저나 앱 화면 쪽 코드입니다. 수량이 0이면 주문 버튼을 막아 두는 식으로 규칙을 미리 검사합니다. 사용자는 요청을 보내기 전에 잘못을 알 수 있습니다.
그러나 화면을 거치지 않고 서버에 요청을 직접 보내면 이 검사를 건너뜁니다. 그래서 같은 규칙을 서버에서 한 번 더 검사합니다.
둘째는 데이터베이스입니다. 데이터베이스 안에 저장해 두고 이름으로 불러 실행하는 절차를 저장 프로시저라고 합니다. 규칙을 여기에 적으면 데이터 가까이에서 돕니다. 그만큼 애플리케이션과 주고받는 횟수가 줍니다.
대신 규칙이 애플리케이션 코드와 데이터베이스 두 곳으로 나뉩니다. 규칙을 찾으려면 양쪽을 다 봐야 합니다.
데이터베이스의 제약 조건도 규칙 일부를 맡습니다. 제약 조건은 테이블에 붙여 두는 규칙입니다. 「수량은 0보다 크다」를 붙여 두면 어떤 코드가 쓰든 어기는 값이 안 들어갑니다.
규칙을 담는 두 가지 패턴
업무 계층 안에서 규칙을 어디에 적을지도 두 갈래입니다. 차이는 규칙이 절차에 붙느냐 데이터에 붙느냐입니다.
트랜잭션 스크립트는 요청 하나를 처리하는 절차 하나에 규칙을 차례로 적는 방식입니다. 주문 취소 메서드 안에 발송 여부 확인, 상태 변경, 환불이 위에서 아래로 이어집니다. 규칙이 적을 때는 메서드 하나만 읽으면 흐름이 다 보입니다. 규칙이 늘면 비슷한 확인이 여러 절차에 되풀이됩니다.
도메인 모델은 규칙을 그 데이터를 가진 객체 안에 두는 방식입니다. 발송한 주문은 취소할 수 없다는 규칙을 주문 객체의 취소 메서드가 지킵니다. 서비스는 그 메서드를 부르기만 합니다. 앞의 그림이 이 방식입니다.
객체에 데이터만 있고 규칙은 전부 서비스에 있는 꼴을 빈약한 도메인 모델이라고 부릅니다. 객체 모양은 도메인 모델인데 구조는 트랜잭션 스크립트와 같습니다.
분리의 이점과 대가
규칙이 많고 자주 바뀌는 프로그램일수록 분리해서 얻는 것이 커집니다. 보험료 계산이나 할인 정책처럼 운영 쪽에서 자주 손대는 규칙이 그렇습니다. 규칙이 한 곳에 모여 있으면 바뀐 규칙만 고치고 그 부분만 시험합니다.
규칙이 거의 없는 프로그램도 있습니다. 받은 값을 저장하고 저장된 값을 보여 주기만 하는 게시판이 그렇습니다. 만들기·읽기·고치기·지우기 네 동작만 하는 이런 프로그램을 CRUD(Create, Read, Update, Delete) 프로그램이라고 부릅니다.
CRUD 프로그램에 업무 계층을 따로 두면 서비스는 리포지토리 호출을 그대로 넘겨주는 코드가 됩니다. 분리할수록 파일과 계층이 늡니다. 요청 하나를 따라가려면 여러 파일을 건너가야 합니다. 그래서 규칙의 양에 맞춰 얼마나 분리할지를 고릅니다.
관련 항목
비즈니스 로직을 담는 코드 단위
서비스 계층 · 애플리케이션 로직 · 도메인 모델 · 엔티티 · 값 객체 · 애그리거트 · 유스케이스 · 도메인 서비스
비즈니스 로직을 기술 코드와 분리하는 아키텍처
레이어드 · 헥사고날 · 클린 아키텍처 · 어니언 아키텍처 · MVC · 관심사 분리 · 의존성 역전 원칙 · 계층
비즈니스 로직을 짜는 방식
트랜잭션 스크립트 · 액티브 레코드 · 테이블 모듈 · 빈약한 도메인 모델 · 도메인 주도 설계
비즈니스 로직 바깥에 서는 기술 계층
컨트롤러 · 리포지토리 패턴 · 데이터 접근 계층 · 영속성 · ORM · 직렬화
비즈니스 로직 일부를 나눠 맡는 서버 밖 수단
프론트엔드 · 입력 검증 · 저장 프로시저 · 제약 조건 · 데이터베이스 트리거
비즈니스 로직을 시험하는 테스트
단위 테스트 · 통합 테스트 · 테스트 더블 · 목 객체 · 테스트 주도 개발
비즈니스 로직이 속하는 상위 분류
소프트웨어 아키텍처 · 아키텍처 양식 · 백엔드 · 애플리케이션 서버
다른 이름: business logic · 도메인 로직 · domain logic · 업무 로직 · 업무 규칙 · 비즈니스 규칙