자원 지향 설계
고친 사람 github-actions[bot]
자원 지향 설계는 서버가 밖에 내놓는 인터페이스를 동작 목록이 아니라 대상 목록으로 짜는 결정입니다. 먼저 책이나 사용자처럼 이름 붙은 대상, 곧 자원을 정합니다. 그다음 모든 자원에 같은 동작 다섯 개를 붙입니다. 그래서 처음 보는 인터페이스도 부르는 법을 짐작할 수 있습니다.
쉽고 빠른 이해
무슨 일을 하나 — 서버가 할 수 있는 일을 「책」「서가」 같은 대상 중심으로 정리합니다.
책을 새로 넣는 일은 registerBook 이라는 함수가 아니라 「책 묶음에 생성」으로 부릅니다.
왜 이렇게 하나 — 동작마다 함수 이름을 새로 지으면 서비스마다 낱말이 달라집니다. 부르는 쪽은 서비스를 바꿀 때마다 그 낱말을 새로 익혀야 합니다. 동작을 다섯 개(조회·목록·생성·수정·삭제)로 묶어 두면 대상 이름만 알면 됩니다.
어떻게 도나
- 어떤 대상을 내놓을지 정합니다
- 무엇이 무엇 아래에 있는지 계층을 정합니다. 그 계층을 따라 이름을 붙입니다
- 대상마다 조회·목록·생성·수정·삭제 다섯 동작을 붙입니다. 다섯으로 안 되는 일만 따로 이름을 짓습니다
대가 — 게시글·주문처럼 대상이 중심인 서비스에 잘 맞습니다. 송금처럼 대상보다 절차에 가까운 일은 다섯 동작에 억지로 끼우거나 예외 동작으로 빼야 합니다.
상세
이 절은 서점 서비스 하나를 예로 들어 자원 지향 설계를 봅니다. 먼저 같은 서비스를 두 방식으로 짜 봅니다.
동작으로 짠 인터페이스
API(Application Programming Interface, 응용 프로그램 인터페이스)는 한 프로그램이 다른 프로그램을 부르는 접점입니다. 서버가 밖에 내놓는 API 가 있어야 다른 프로그램이 서버에 일을 시킬 수 있습니다.
네트워크 너머의 API 를 부르는 흔한 방법이 RPC(Remote Procedure Call, 원격 프로시저 호출)입니다. 멀리 있는 서버의 함수를 내 코드의 함수처럼 이름으로 부릅니다. RPC 는 함수를 부르는 방법만 정합니다. 함수를 몇 개 두고 이름을 어떻게 지을지는 정하지 않습니다.
함수를 정하는 가장 손쉬운 방법은 필요한 동작마다 함수를 하나씩 만들고 이름을 새로 짓는 것입니다.
서점 서비스를 이렇게 짜면 함수 이름이 쌓입니다. 한 팀은 createBook, 다른 팀은 addNewBook, 또 다른
팀은 registerBook 이라고 짓습니다. 셋은 같은 일을 합니다. 이름만 다릅니다. 부르는 쪽은 서비스마다
낱말 목록을 따로 외워야 합니다.
대상으로 짠 인터페이스
자원 지향 설계는 순서를 뒤집습니다. 동작보다 대상을 먼저 정합니다. 책 한 권, 서가 하나, 사용자 한 명이 각각 자원 하나입니다.
동작은 모든 자원에 공통인 몇 개로 줄입니다. 책이든 서가든 새로 만들 때는 같은 「생성」을 부릅니다. 부르는 쪽이 새로 익힐 것은 자원 이름뿐입니다.
두 방식이 무엇을 먼저 정하고 무엇이 늘어나는지를 나란히 놓으면 이렇습니다.
| 동작마다 함수를 짓는 방식 | 자원 지향 설계 | |
|---|---|---|
| 먼저 정하는 것 | 할 수 있는 동작 | 다루는 대상 |
| 동작 이름 | 서비스마다 새로 짓는다 | 다섯 개로 고정 |
| 기능을 하나 더하면 | 함수가 하나 는다 | 대개 자원이 하나 는다 |
마지막 줄이 이 설계의 핵심입니다. 기능이 늘어도 동작의 종류는 늘지 않습니다. 늘어나는 것은 대상의 종류입니다.
자원과 컬렉션의 계층
같은 종류의 자원을 모은 묶음을 컬렉션이라고 부릅니다. books 는 책 컬렉션입니다. 그 안의 책
한 권 한 권이 자원입니다. 컬렉션이 있어야 「책을 전부 보여 달라」나 「새 책을 넣어 달라」를 보낼
대상이 생깁니다.
자원은 다른 자원 아래에 들어갈 수 있습니다. 서가 아래에 책이 꽂혀 있는 식입니다. 그래서 인터페이스 전체가 트리 모양의 계층 하나가 됩니다. 계층의 노드는 자원이거나 컬렉션입니다.
flowchart TD
S["shelves · 서가 컬렉션"] --> S1["shelves/1 · 서가 1번"]
S --> S2["shelves/2 · 서가 2번"]
S1 --> B["shelves/1/books · 책 컬렉션"]
B --> B7["shelves/1/books/7 · 책 7번"]
B --> B8["shelves/1/books/8 · 책 8번"]
그림에서 컬렉션과 자원이 한 층씩 번갈아 내려갑니다. 서가 컬렉션 아래에 서가가 있습니다. 서가 1번 아래에는 다시 책 컬렉션이 있습니다. 노드에 붙은 경로가 곧 다음 소절에서 볼 자원 이름입니다.
자원 이름
계층을 따라 내려온 경로가 곧 자원의 이름입니다. shelves/1/books/7 은 「서가 1번에 꽂힌 책 7번」을
가리킵니다. 이름만 읽어도 이 자원이 어느 자원 아래에 있는지 알 수 있습니다.
HTTP(HyperText Transfer Protocol, 웹에서 요청과 응답을 주고받는 규약)로 인터페이스를 낼 때는 이 이름이 주소의 경로가 됩니다. 컬렉션에는 흔히 복수 명사를 씁니다. 그 뒤에 자원 하나를 가리키는 식별자를 붙입니다.
표준 메서드 다섯
모든 자원에 공통으로 붙는 동작을 표준 메서드라고 부릅니다. 흔히 쓰는 것은 다섯입니다. 둘은 컬렉션에 부릅니다. 셋은 자원 하나에 부릅니다.
| 표준 메서드 | 하는 일 | 부르는 대상 | HTTP 로 낼 때 |
|---|---|---|---|
| 목록(List) | 컬렉션 안의 자원들을 돌려준다 | 컬렉션 | GET |
| 조회(Get) | 자원 하나를 돌려준다 | 자원 | GET |
| 생성(Create) | 컬렉션에 새 자원을 넣는다 | 컬렉션 | POST |
| 수정(Update) | 자원 하나의 값 일부를 바꾼다 | 자원 | PATCH |
| 삭제(Delete) | 자원 하나를 지운다 | 자원 | DELETE |
같은 GET 이라도 컬렉션에 부르면 목록이고 자원에 부르면 조회입니다. 동작의 뜻은 메서드 이름과
부르는 대상이 함께 정합니다.
서가 1번의 책을 다루는 다섯 호출을 HTTP 로 적으면 이렇습니다.
GET /shelves/1/books // 목록
GET /shelves/1/books/7 // 조회
POST /shelves/1/books // 생성
PATCH /shelves/1/books/7 // 수정
DELETE /shelves/1/books/7 // 삭제
주소는 둘뿐입니다. 바뀌는 것은 앞의 메서드뿐입니다. 부르는 쪽은 책 대신 서가나 사용자를 만나도 같은 다섯 줄을 씁니다.
이 다섯은 흔히 CRUD(Create, Read, Update, Delete, 생성·조회·수정·삭제)라고 부르는 네 가지 기본 동작에 목록을 더한 것입니다. 조회가 자원 하나와 여럿으로 갈려 다섯이 됩니다.
커스텀 메서드
다섯으로 나타낼 수 없는 일도 있습니다. 책 여러 권을 한 번에 지우는 일이 한 예입니다. 삭제는 자원 하나를 지우는 동작입니다. 여러 권을 한 호출로 지우는 동작은 다섯 안에 없습니다. 이럴 때는 이름을 따로 지은 동작을 둡니다. 이것을 커스텀 메서드라고 합니다.
커스텀 메서드도 자원이나 컬렉션에 붙습니다. 이름 끝에 콜론과 동사를 붙이는 표기가 한 예입니다.
POST /shelves/1/books:batchDelete
책 컬렉션 이름은 손대지 않고 그 뒤에 batchDelete 라는 동작 하나를 덧붙였습니다. 커스텀 메서드는 예외로 둡니다. 표준
메서드로 풀 수 있는 일이면 표준 메서드를 먼저 씁니다.
설계하는 순서
자원 지향 설계는 무엇을 먼저 정할지도 정해 둡니다. 동작은 맨 마지막입니다.
- 어떤 자원을 내놓을지 정합니다
- 자원 사이의 관계와 계층을 정합니다
- 계층을 따라 자원 이름을 정합니다
- 자원마다 담을 필드의 모양, 곧 스키마를 정합니다
- 자원마다 메서드를 붙입니다. 표준 메서드를 먼저 붙입니다. 모자란 것만 커스텀 메서드로 채웁니다
동작을 먼저 떠올리면 함수 목록이 먼저 생깁니다. 대상은 그 함수들 사이에 흩어집니다. 대상을 먼저 정하면 동작 대부분이 표준 메서드로 채워집니다.
데이터베이스 테이블과의 관계
자원은 데이터베이스의 테이블과 닮을 때가 많습니다. 책 테이블이 있고 책 자원이 있는 식입니다. 그래도 둘을 한 벌로 맞추지 않습니다.
인터페이스가 저장 구조를 그대로 따라가면 테이블을 나누거나 합칠 때마다 인터페이스도 바뀝니다. 그러면 부르는 쪽 코드가 같이 깨집니다. 그래서 인터페이스를 밑의 저장 구조와 똑같이 짜는 것은 안티패턴(그럴듯해 보이지만 문제를 낳는 설계)으로 꼽힙니다. 밖에 보이는 모양을 안쪽 구현에 단단히 묶기 때문입니다.
REST 와의 관계
REST(Representational State Transfer, 표현 상태 전이)는 자원에 이름을 붙이고 정해진 몇 개의 동작으로 다루는 웹 아키텍처 스타일입니다. 자원 지향 설계는 REST 에서 많은 원칙을 빌려 왔습니다. 자원, 자원 이름, 모든 자원에 공통인 동작을 빌려 왔습니다.
자원 지향 설계는 앞에서 본 RPC 와 맞서지 않습니다. RPC 는 함수를 부르는 방법입니다. 자원 지향 설계는 RPC 로 부를 함수들을 자원과 표준 메서드로 묶는 방법입니다. 맞서는 쪽은 동작마다 함수 이름을 새로 짓는 방식입니다.
그래서 같은 자원 모델을 HTTP 주소로도 냅니다. 구글이 만든 RPC 프레임워크인 gRPC의 함수로도 냅니다. 어느 쪽으로 내든 자원과 다섯 메서드는 같습니다.
REST 에는 지키라고 정한 조건이 몇 가지 있습니다. 그 가운데 하나가 하이퍼미디어입니다. 응답 안에 다음에 할 수 있는 동작의 링크를 실어 보내라는 조건입니다. 자원 지향 설계는 이 조건을 요구하지 않습니다. 부르는 쪽이 자원 이름과 다섯 메서드를 미리 알고 부릅니다.
잘 맞는 서비스와 어긋나는 서비스
다루는 것이 대부분 대상일 때 잘 맞습니다. 게시글, 사용자, 주문, 파일처럼 만들고 읽고 고치고 지우는 대상이 중심인 서비스입니다. 이런 서비스는 메서드 대부분이 표준 메서드로 채워집니다.
다루는 것이 절차일 때는 어긋납니다. 송금, 번역, 경로 계산처럼 입력을 받아 결과를 돌려주는 일은 대상이
뚜렷하지 않습니다. 자원으로 만들려면 송금 컬렉션 transfers 에 송금 하나를 「생성」하는 식으로
바꿔 말해야 합니다.
송금 기록이 남아야 하는 서비스라면 이렇게 바꿔 말하는 편이 오히려 자연스럽습니다. 하지만 절차가 대부분인 서비스를 억지로 끼우면 커스텀 메서드가 표준 메서드보다 많아집니다. 그러면 부르는 법을 짐작할 수 있다는 이점이 거의 남지 않습니다.
화면 하나가 여러 자원을 한꺼번에 보여 줄 때도 비용이 생깁니다. 자원마다 따로 조회하면 호출이 자원 수만큼 늘어납니다. GraphQL 은 부르는 쪽이 필요한 모양을 한 번의 요청으로 받아 가게 해서 이 불편을 줄이려는 방식입니다.
관련 항목
자원 지향 설계를 이루는 구성 요소
자원 · 컬렉션 · 자원 이름 · 표준 메서드 · 커스텀 메서드 · 스키마 · 필드 · 식별자
자원 지향 설계가 빌려 온 원칙
REST · 통일된 인터페이스 · 표현 · 무상태 · 하이퍼미디어 · 자기 서술적 메시지
표준 메서드를 HTTP 로 낼 때 쓰는 요청 메서드
요청 메서드 · GET · POST · PATCH · DELETE · PUT · 멱등성 · 안전한 메서드
자원 이름이 놓이는 주소 체계
URI · 요청 대상 · 엔드포인트 · URI 템플릿 · 라우팅 · HTTP
자원 모델을 실어 나르는 호출 방식
자원 대신 다른 모양으로 인터페이스를 내는 방식
GraphQL · SOAP · 웹훅
자원 지향 설계가 속하는 상위 분류
API 설계 · API · 아키텍처 스타일 · 인터페이스
자원 모델을 기술하는 표준·문서
OpenAPI · AIP · 인터페이스 기술
자원 모델 위에 얹히는 설계 관례
페이지네이션 · 필드 마스크 · API 버전 관리 · 하위 호환 · 소프트 삭제 · 긴 작업
이름이 닮아 헷갈리는 설계 방식
자원 지향 아키텍처 · 서비스 지향 아키텍처 · 객체 지향 설계 · 도메인 주도 설계
다른 이름: resource-oriented design · 리소스 지향 설계 · 자원 중심 설계