REST APIλ 무μμΈκ°
μΉ μλΉμ€λ₯Ό λ§λ€ λ κ°μ₯ λ¨Όμ κ³ λ―Όνλ κ²μ
βν΄λΌμ΄μΈνΈμ μλ²κ° μ΄λ»κ² λνν κΉ?βμ΄λ€.
REST APIλ μ΄ λνμ κ·μΉμ΄λ€.
λ§μΉ μ°μ²΄κ΅μμ νΈμ§λ₯Ό λ³΄λΌ λ
μ ν΄μ§ νμ(μ£Όμ, μ°ν, λ΄μ©)μ΄ μλ―μ΄,
REST APIλ μ ν΄μ§ κ·μΉμΌλ‘ λ°μ΄ν°λ₯Ό μ£Όκ³ λ°λλ€.
REST(Representational State Transfer)λ
2000λ
λ‘μ΄ νλ©(Roy Fielding)μ΄ μ μν
μΉμ μ₯μ μ μ΅λν νμ©νλ μν€ν
μ² μ€νμΌμ΄λ€.
μ REST APIλ₯Ό λ°°μμΌ ν κΉ?
μ΄μ 1: μ°μ
νμ€
λλΆλΆμ μΉ μλΉμ€κ° REST APIλ₯Ό μ¬μ©νλ€.
μ΄μ 2: λ¨μν¨
HTTPλ₯Ό κ·Έλλ‘ νμ©νλ―λ‘ λ³λ νμ΅ λΉμ©μ΄ μ λ€.
μ΄μ 3: λͺ
νμ±
URIλ§ λ΄λ μ΄λ€ 리μμ€μΈμ§ μ μ μλ€.
μ΄μ 4: λ©΄μ νμ
REST API μ€κ³λ λ°±μλ λ©΄μ μ λ¨κ³¨ μ§λ¬Έμ΄λ€.
κΈ°λ³Έ κ°λ μμ½
π·οΈ REST 6κ°μ§ μ μ½ μ‘°κ±΄
1. Client-Server (ν΄λΌμ΄μΈνΈ-μλ²)
κ°λ
: ν΄λΌμ΄μΈνΈμ μλ²μ μν μ λͺ
νν λΆλ¦¬
μλΉ λΉμ :
μλ(ν΄λΌμ΄μΈνΈ)μ μ£Όλ¬Έλ§ νκ³ ,
μ£Όλ°©(μλ²)μ μλ¦¬λ§ νλ€.
κ°μμ μν μλ§ μ§μ€νλ€.
2. Stateless (무μν)
κ°λ
: μλ²λ ν΄λΌμ΄μΈνΈμ μνλ₯Ό μ μ₯νμ§ μμ
μ°μ²΄κ΅ λΉμ :
μ°μ²΄λΆλ λ§€λ² μ£Όμλ₯Ό νμΈνκ³ λ°°λ¬νλ€.
μ΄μ μ μ΄λλ‘ λ°°λ¬νλμ§ κΈ°μ΅νμ§ μλλ€.
3. Cacheable (μΊμ κ°λ₯)
κ°λ
: HTTPμ μΊμ κΈ°λ₯μ κ·Έλλ‘ νμ©
4. Uniform Interface (μΌκ΄λ μΈν°νμ΄μ€)
κ°λ
: 리μμ€ μλ³, HTTP λ©μλ μ¬μ©
5. Layered System (κ³μΈ΅ν)
κ°λ
: μ€κ°μ νλ‘μ, κ²μ΄νΈμ¨μ΄ μΆκ° κ°λ₯
6. Code-On-Demand (μ νμ¬ν)
κ°λ
: μλ²κ° ν΄λΌμ΄μΈνΈμ μ€ν μ½λ μ μ‘ κ°λ₯
π·οΈ HTTP λ©μλ
| λ©μλ | μλ―Έ | λ©±λ±μ± | μμ μ± | μ¬μ© μμ |
|---|---|---|---|---|
| GET | μ‘°ν | O | O | κ²μκΈ λͺ©λ‘ μ‘°ν |
| POST | μμ± | X | X | κ²μκΈ μμ± |
| PUT | μ 체 μμ | O | X | κ²μκΈ μ 체 μμ |
| PATCH | λΆλΆ μμ | X | X | κ²μκΈ μ λͺ©λ§ μμ |
| DELETE | μμ | O | X | κ²μκΈ μμ |
λ©±λ±μ±: λμΌν μμ²μ μ¬λ¬ λ² ν΄λ κ²°κ³Όκ° κ°μ
μμ μ±: μλ² μνλ₯Ό λ³κ²½νμ§ μμ
π·οΈ HTTP μν μ½λ
2xx - μ±κ³΅
- 200 OK: μμ² μ±κ³΅
- 201 Created: μμ± μ±κ³΅
- 204 No Content: μ±κ³΅ (μλ΅ Body μμ)
4xx - ν΄λΌμ΄μΈνΈ μ€λ₯
- 400 Bad Request: μλͺ»λ μμ²
- 401 Unauthorized: μΈμ¦ νμ
- 403 Forbidden: κΆν μμ
- 404 Not Found: 리μμ€ μμ
- 409 Conflict: μΆ©λ
- 422 Unprocessable Entity: μ²λ¦¬ λΆκ°
- 429 Too Many Requests: μμ² κ³Όλ€
5xx - μλ² μ€λ₯
- 500 Internal Server Error: μλ² μ€λ₯
- 502 Bad Gateway: κ²μ΄νΈμ¨μ΄ μ€λ₯
- 503 Service Unavailable: μλΉμ€ λΆκ°
URI μ€κ³ μμΉ
π·οΈ Best Practices
1. λͺ μ¬ μ¬μ©, λμ¬ κΈμ§
β
Good:
GET /api/users
POST /api/users
GET /api/users/123
DELETE /api/users/123
β Bad:
/api/getUsers
/api/createUser
/api/deleteUser
2. 볡μν μ¬μ©
β
Good: /api/users
β Bad: /api/user
3. κ³μΈ΅ ꡬ쑰 νν
β
Good:
GET /api/users/123/posts
GET /api/posts/456/comments
β Bad:
GET /api/posts?userId=123
4. μλ¬Έμ μ¬μ©, νμ΄νμΌλ‘ ꡬλΆ
β
Good: /api/user-profiles
β Bad: /api/UserProfiles
μ€μ μμ
π·οΈ Spring Boot API ꡬν
@RestController
@RequestMapping("/api/posts")
public class PostController {
@Autowired
private PostService postService;
// κ²μκΈ λͺ©λ‘ μ‘°ν (νμ΄μ§, νν°λ§, μ λ ¬)
@GetMapping
public ResponseEntity<Page<PostResponse>> getPosts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "createdAt:desc") String sort
) {
Pageable pageable = PageRequest.of(page, size);
Page<PostResponse> posts = postService.getPosts(status, pageable);
return ResponseEntity.ok(posts);
}
// κ²μκΈ λ¨κ±΄ μ‘°ν
@GetMapping("/{id}")
public ResponseEntity<PostResponse> getPost(@PathVariable Long id) {
PostResponse post = postService.getPost(id);
return ResponseEntity.ok(post);
}
// κ²μκΈ μμ±
@PostMapping
public ResponseEntity<PostResponse> createPost(
@Valid @RequestBody PostCreateRequest request
) {
PostResponse post = postService.createPost(request);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(post.getId())
.toUri();
return ResponseEntity.created(location).body(post);
}
// κ²μκΈ μμ
@PutMapping("/{id}")
public ResponseEntity<PostResponse> updatePost(
@PathVariable Long id,
@Valid @RequestBody PostUpdateRequest request
) {
PostResponse post = postService.updatePost(id, request);
return ResponseEntity.ok(post);
}
// κ²μκΈ μμ
@DeleteMapping("/{id}")
public ResponseEntity<Void> deletePost(@PathVariable Long id) {
postService.deletePost(id);
return ResponseEntity.noContent().build();
}
}
π·οΈ μλ¬ μλ΅ νμ€ν
@Getter
@Builder
public class ErrorResponse {
private String timestamp;
private int status;
private String error;
private String message;
private String path;
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(
ResourceNotFoundException ex,
HttpServletRequest request
) {
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now().toString())
.status(404)
.error("Not Found")
.message(ex.getMessage())
.path(request.getRequestURI())
.build();
return ResponseEntity.status(404).body(error);
}
}
π·οΈ API λ²μ λ
URI λ²μ λ (μΆμ²)
GET /api/v1/users
GET /api/v2/users
μ₯μ : λͺ
ννκ³ μΊμ κ°λ₯
λ¨μ : URIκ° λ³κ²½λ¨
Header λ²μ λ
GET /api/users
Accept: application/vnd.company.v1+json
π·οΈ HATEOAS
{
"id": 123,
"title": "REST API μ€κ³",
"_links": {
"self": {
"href": "/api/posts/123"
},
"comments": {
"href": "/api/posts/123/comments"
},
"author": {
"href": "/api/users/456"
}
}
}
μ€μ 체ν¬λ¦¬μ€νΈ
β URI μ€κ³
- λͺ μ¬ μ¬μ© (λμ¬ κΈμ§)
- 볡μν μ¬μ©
- μλ¬Έμ, νμ΄ν μ¬μ©
- κ³μΈ΅ ꡬ쑰 νν
- νμΌ νμ₯μ μ κ±°
β HTTP λ©μλ
- GET: μ‘°ν (λ©±λ±μ± O, μμ μ± O)
- POST: μμ± (λ©±λ±μ± X)
- PUT: μ 체 μμ (λ©±λ±μ± O)
- PATCH: λΆλΆ μμ
- DELETE: μμ (λ©±λ±μ± O)
β μν μ½λ
- 2xx: μ±κ³΅ (200, 201, 204)
- 4xx: ν΄λΌμ΄μΈνΈ μ€λ₯ (400, 401, 403, 404)
- 5xx: μλ² μ€λ₯ (500, 502, 503)
β μλ΅ νμ
- μΌκ΄λ JSON ꡬ쑰
- μλ¬ μλ΅ νμ€ν
- νμ΄μ§ μ 보 ν¬ν¨
- HATEOAS λ§ν¬ (μ ν)
β 보μ
- HTTPS μ¬μ©
- μΈμ¦/μΈκ° (JWT, OAuth)
- Rate Limiting
- CORS μ€μ
- μ λ ₯ κ²μ¦
β λ¬Έμν
- Swagger/OpenAPI
- μμ μ½λ
- μλ¬ μ½λ λͺ©λ‘
- λ³κ²½ μ΄λ ₯
μμ½
REST API μ€κ³λ μΌκ΄μ±κ³Ό μ§κ΄μ±μ΄ ν΅μ¬μ΄λ€.
π ν΅μ¬ ν¬μΈνΈ:
- 리μμ€ μ€μ¬: URIλ λͺ μ¬λ‘, λμ¬λ HTTP λ©μλλ‘
- 무μν: λ§€ μμ²μ νμν μ 보 ν¬ν¨
- HTTP νμ©: λ©μλμ μν μ½λλ₯Ό μ¬λ°λ₯΄κ² μ¬μ©
- μΌκ΄μ±: λ€μ΄λ° κ·μΉμ νλ‘μ νΈ μ 체μ μ μ©
- κ³μΈ΅ ꡬ쑰: 리μμ€ κ° κ΄κ³λ₯Ό URIλ‘ νν
- μλ¬ μ²λ¦¬: λͺ νν μλ¬ λ©μμ§μ μν μ½λ
π― μ€κ³ μμΉ:
β
Do:
- GET /api/users (λͺ©λ‘ μ‘°ν)
- POST /api/users (μμ±)
- GET /api/users/123 (λ¨κ±΄ μ‘°ν)
- PUT /api/users/123 (μμ )
- DELETE /api/users/123 (μμ )
β Don't:
- POST /api/getUserList
- GET /api/createUser
- GET /api/user/delete?id=123
π μν μ½λ κ°μ΄λ:
- 200: GET, PUT μ±κ³΅
- 201: POST μ±κ³΅ (Location ν€λ ν¬ν¨)
- 204: DELETE μ±κ³΅
- 400: μλͺ»λ μμ²
- 401: μΈμ¦ νμ
- 403: κΆν μμ
- 404: 리μμ€ μμ
- 500: μλ² μ€λ₯
π Best Practices:
- λ²μ λμ URIμ λͺ μ (/api/v1/users)
- νμ΄μ§μ 쿼리 νλΌλ―Έν° (?page=1&size=20)
- νν°λ§λ 쿼리 νλΌλ―Έν° (?status=active)
- μ λ ¬μ sort νλΌλ―Έν° (?sort=createdAt:desc)
- μλ¬ μλ΅μ νμ€ νμ μ μ§
- HATEOASλ‘ λ€μ νλ μλ΄ (μ ν)
REST APIλ λ¨μν HTTPλ₯Ό μ¬μ©νλ κ²μ΄ μλλΌ,
μΉμ μν€ν
μ²λ₯Ό μ΅λν νμ©νλ μ€κ³ μ² νμ΄λ€.
λͺ
ννκ³ μΌκ΄λ APIλ κ°λ°μ κ²½νμ ν₯μμν€κ³ ,
μ μ§λ³΄μ λΉμ©μ ν¬κ² μ€μ¬μ€λ€.