API ์ค๊ณ ์์น
API ์ค๊ณ ์์น¶
ํ ํฝ: Programming ๋ ์จ: 13 of 16 ์ ์ ์ง์: ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ, HTTP ๊ธฐ์ด, JSON/XML ํ์ ๋ชฉํ: ๋ผ์ด๋ธ๋ฌ๋ฆฌ, ๋ชจ๋, ์น ์๋น์ค๋ฅผ ์ํ ๋ช ํํ๊ณ ์ผ๊ด๋ ์ ์ง๋ณด์ ๊ฐ๋ฅํ API ์ค๊ณ ๋ฐฉ๋ฒ ํ์ต
์๊ฐ¶
API(Application Programming Interface)๋ ์ํํธ์จ์ด ๊ตฌ์ฑ ์์๊ฐ ํต์ ํ๋ ๋ฐฉ๋ฒ์ ์ ์ํ๋ ๊ณ์ฝ์ ๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํจ์, REST ์๋ํฌ์ธํธ ๋๋ ์์คํ ์ธํฐํ์ด์ค๋ฅผ ์ค๊ณํ๋ , ์ข์ API ์ค๊ณ๋ ์ํํธ์จ์ด๋ฅผ ๋ ์ฝ๊ฒ ์ฌ์ฉํ๊ณ , ์ ์ง๋ณด์ํ๊ณ , ๋ฐ์ ์ํฌ ์ ์๊ฒ ๋ง๋ญ๋๋ค.
์๋ชป๋ API ์ค๊ณ๋ ๋ค์์ ์ด๋ํฉ๋๋ค: - ์ธํฐํ์ด์ค๋ฅผ ์๋ชป ์ฌ์ฉํ๋ ํผ๋์ค๋ฌ์ด ์ฌ์ฉ์ - ์์ ๋ณ๊ฒฝ์๋ ๊นจ์ง๋ ์ทจ์ฝํ ์ฝ๋ - ๋ถ๋ช ํํ ๋ฌธ์๋ก ์ธํ ์ง์ ๋ถ๋ด - ์๋น์์ ํตํฉ ์ด๋ ค์
ํ๋ฅญํ API ์ค๊ณ๋ ๋ณด์ด์ง ์์ต๋๋คโ์ฌ์ฉ์๋ ๋ง์ฐฐ ์์ด ๋ชฉํ๋ฅผ ๋ฌ์ฑํฉ๋๋ค.
API๋ ๋ฌด์์ธ๊ฐ?¶
API๋ ๊ตฌํ ์ธ๋ถ ์ฌํญ์ ์จ๊ธฐ๋ฉด์ ๊ธฐ๋ฅ์ ๋ ธ์ถํ๋ ์ถ์ํ(abstraction)์ ๋๋ค:
# ๋์จ: ๊ตฌํ ์ธ๋ถ์ฌํญ ๋
ธ์ถ
user_data = database.execute_raw_sql("SELECT * FROM users WHERE id = ?", user_id)
# ์ข์: API๋ฅผ ํตํ ์ถ์ํ
user = user_service.get_user_by_id(user_id)
API์ ์ ํ¶
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ/๋ชจ๋ API: ์ฝ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจ์์ ํด๋์ค
- ์น API: HTTP ๊ธฐ๋ฐ ์๋น์ค(REST, GraphQL, gRPC)
- ์ด์์ฒด์ API: ์์คํ ์ฝ, ํ์ผ I/O, ํ๋ก์ธ์ค ๊ด๋ฆฌ
- ํ๋์จ์ด API: ๋๋ฐ์ด์ค ๋๋ผ์ด๋ฒ, ํ์จ์ด ์ธํฐํ์ด์ค
์ด ๋ ์จ์ ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ์ค๊ณ ๊ณผ์ ๋ฅผ ๋ํํ๋ ์น API์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ API์ ์ฃผ๋ก ์ด์ ์ ๋ง์ถฅ๋๋ค.
REST API ์ค๊ณ¶
REST(Representational State Transfer)๋ HTTP ๋ฉ์๋์ URI๋ฅผ ์ฌ์ฉํ์ฌ ๋ฆฌ์์ค๋ฅผ ํํํ๋ ์น API์ ์ํคํ ์ฒ ์คํ์ผ์ ๋๋ค.
ํต์ฌ ์์น¶
1. ๋ฆฌ์์ค์ URI¶
๋ฆฌ์์ค๋ ๋ช ์ฌ(noun)์ด์ง, ๋์ฌ(verb)๊ฐ ์๋๋๋ค. URI๋ ๋ฆฌ์์ค๋ฅผ ์๋ณํ๊ณ , HTTP ๋ฉ์๋๋ ์์ ์ ๋ํ๋ ๋๋ค.
์ข์ URI ์ค๊ณ:
GET /users # ์ฌ์ฉ์ ๋ชฉ๋ก
GET /users/123 # ์ฌ์ฉ์ 123 ์กฐํ
POST /users # ์ฌ์ฉ์ ์์ฑ
PUT /users/123 # ์ฌ์ฉ์ 123 ์
๋ฐ์ดํธ
DELETE /users/123 # ์ฌ์ฉ์ 123 ์ญ์
GET /users/123/orders # ์ฌ์ฉ์ 123์ ์ฃผ๋ฌธ ์กฐํ
๋์ URI ์ค๊ณ:
GET /getUsers # URI์ ๋์ฌ
POST /createUser # URI์ ๋์ฌ
GET /users/delete/123 # URI ๊ฒฝ๋ก์ ์ก์
GET /user_orders?id=123 # ์ผ๊ด์ฑ ์๋ ๋ค์ด๋ฐ
๋ค์ด๋ฐ ๊ท์น:
- ์ปฌ๋ ์
์๋ ๋ณต์ ๋ช
์ฌ ์ฌ์ฉ(/users, /user ์๋)
- ์๋ฌธ์ ์ฌ์ฉ, ํ์ดํ์ผ๋ก ๊ตฌ๋ถ(/order-items, /OrderItems ์๋)
- ๊ฒฝ๋ก ์ธ๊ทธ๋จผํธ๋ก ๊ณ์ธต ๊ตฌ์กฐ ํํ(/users/123/addresses/456)
2. HTTP ๋ฉ์๋¶
HTTP ๋ฉ์๋๋ฅผ ์๋ฏธ๋ก ์ ์ผ๋ก ์ฌ์ฉ:
| ๋ฉ์๋ | ๋ชฉ์ | ๋ฉฑ๋ฑ์ฑ? | ์์ ์ฑ? |
|---|---|---|---|
| GET | ๋ฆฌ์์ค ์กฐํ | Yes | Yes |
| POST | ๋ฆฌ์์ค ์์ฑ | No | No |
| PUT | ๋ฆฌ์์ค ๊ต์ฒด | Yes | No |
| PATCH | ๋ถ๋ถ ์ ๋ฐ์ดํธ | No | No |
| DELETE | ๋ฆฌ์์ค ์ ๊ฑฐ | Yes | No |
๋ฉฑ๋ฑ์ฑ(Idempotent): ๋์ผํ ์์ฒญ์ ์ฌ๋ฌ ๋ฒ ํด๋ ํ ๋ฒ์ ์์ฒญ๊ณผ ๊ฐ์ ํจ๊ณผ ์์ ์ฑ(Safe): ์์ฒญ์ด ์๋ฒ ์ํ๋ฅผ ๋ณ๊ฒฝํ์ง ์์
// Express.js ์์
const express = require('express');
const app = express();
app.get('/users/:id', (req, res) => {
const user = userService.findById(req.params.id);
res.json(user);
});
app.post('/users', (req, res) => {
const user = userService.create(req.body);
res.status(201).json(user);
});
app.put('/users/:id', (req, res) => {
const user = userService.replace(req.params.id, req.body);
res.json(user);
});
app.patch('/users/:id', (req, res) => {
const user = userService.update(req.params.id, req.body);
res.json(user);
});
app.delete('/users/:id', (req, res) => {
userService.delete(req.params.id);
res.status(204).send();
});
3. HTTP ์ํ ์ฝ๋¶
๊ฒฐ๊ณผ๋ฅผ ์ ๋ฌํ๊ธฐ ์ํด ์ ์ ํ ์ํ ์ฝ๋ ์ฌ์ฉ:
2xx ์ฑ๊ณต:
- 200 OK: ํ์ค ์ฑ๊ณต ์๋ต
- 201 Created: ๋ฆฌ์์ค ์์ฑ๋จ(POST)
- 204 No Content: ์๋ต ๋ณธ๋ฌธ ์์ด ์ฑ๊ณต(DELETE)
4xx ํด๋ผ์ด์ธํธ ์ค๋ฅ:
- 400 Bad Request: ์ ํจํ์ง ์์ ์
๋ ฅ
- 401 Unauthorized: ์ธ์ฆ ํ์
- 403 Forbidden: ์ธ์ฆ๋์์ง๋ง ๊ถํ ์์
- 404 Not Found: ๋ฆฌ์์ค๊ฐ ์กด์ฌํ์ง ์์
- 409 Conflict: ๋ฆฌ์์ค ์ํ ์ถฉ๋
- 422 Unprocessable Entity: ์ ํจ์ฑ ๊ฒ์ฆ ์ค๋ฅ
5xx ์๋ฒ ์ค๋ฅ:
- 500 Internal Server Error: ์ผ๋ฐ ์๋ฒ ์ค๋ฅ
- 503 Service Unavailable: ์ผ์์ ์ค๋จ
# Flask ์์
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route('/users', methods=['POST'])
def create_user():
data = request.get_json()
if not data or 'email' not in data:
return jsonify({'error': 'Email is required'}), 400
if user_exists(data['email']):
return jsonify({'error': 'User already exists'}), 409
user = create_user_in_db(data)
return jsonify(user), 201
@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = find_user(user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
return jsonify(user), 200
4. ํ์ด์ง๋ค์ด์ ¶
์ปฌ๋ ์ ์ ๊ฒฝ์ฐ, ์๋์ ์ธ ์๋ต์ ๋ฐฉ์งํ๊ธฐ ์ํด ํ์ด์ง๋ค์ด์ ๊ตฌํ:
์คํ์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ :
GET /users?offset=20&limit=10
Response:
{
"data": [...],
"pagination": {
"offset": 20,
"limit": 10,
"total": 150
}
}
์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ (์ค์๊ฐ ๋ฐ์ดํฐ์ ๋ ์ ํฉ):
GET /users?cursor=eyJpZCI6MTIzfQ==&limit=10
Response:
{
"data": [...],
"next_cursor": "eyJpZCI6MTMzfQ=="
}
5. ํํฐ๋ง, ์ ๋ ฌ, ๊ฒ์¶
์ ์ฐํ ๋ฐ์ดํฐ ์กฐํ๋ฅผ ์ํ ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์ ์ ๊ณต:
GET /users?role=admin&sort=-created_at&search=john
// Java Spring Boot ์์
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping
public ResponseEntity<Page<User>> getUsers(
@RequestParam(required = false) String role,
@RequestParam(required = false) String search,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "ASC") Sort.Direction direction
) {
Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy));
Page<User> users = userService.findUsers(role, search, pageable);
return ResponseEntity.ok(users);
}
}
6. HATEOAS¶
HATEOAS(Hypermedia As The Engine Of Application State)๋ ์๋ต์ ๊ด๋ จ ๋ฆฌ์์ค์ ๋ํ ๋งํฌ๋ฅผ ํฌํจํฉ๋๋ค:
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"_links": {
"self": { "href": "/users/123" },
"orders": { "href": "/users/123/orders" },
"addresses": { "href": "/users/123/addresses" }
}
}
์ด๋ API๋ฅผ ์์ฒด ๋ฌธ์ํํ๊ณ ๋ฐ๊ฒฌ ๊ฐ๋ฅํ๊ฒ ๋ง๋ญ๋๋ค.
RPC ์คํ์ผ API¶
RPC(Remote Procedure Call) API๋ ๋ฆฌ์์ค๊ฐ ์๋ ํจ์ ํธ์ถ๋ก ์์ ์ ๋ชจ๋ธ๋งํฉ๋๋ค.
gRPC¶
gRPC๋ ํจ์จ์ ์ธ ๋ฐ์ด๋๋ฆฌ ์ง๋ ฌํ๋ฅผ ์ํด ํ๋กํ ์ฝ ๋ฒํผ(Protocol Buffers)๋ฅผ ์ฌ์ฉํฉ๋๋ค:
// user.proto
syntax = "proto3";
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc CreateUser (CreateUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
int32 id = 1;
}
# Python gRPC ์๋ฒ
import grpc
from concurrent import futures
import user_pb2
import user_pb2_grpc
class UserService(user_pb2_grpc.UserServiceServicer):
def GetUser(self, request, context):
user = find_user(request.id)
if not user:
context.abort(grpc.StatusCode.NOT_FOUND, 'User not found')
return user_pb2.User(id=user.id, name=user.name, email=user.email)
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)
server.add_insecure_port('[::]:50051')
server.start()
RPC vs REST ์ธ์ ์ฌ์ฉํ ๊น¶
RPC(gRPC)๋ฅผ ์ฌ์ฉํ ๋: - ๊ณ ์ฑ๋ฅ๊ณผ ๋ฎ์ ์ง์ฐ์ด ์ค์ํ ๊ฒฝ์ฐ - ์๋ฐฉํฅ ์คํธ๋ฆฌ๋ฐ์ด ํ์ํ ๊ฒฝ์ฐ - ๋ด๋ถ ๋ง์ดํฌ๋ก์๋น์ค ํต์ - ํ์ ์์ ์ฑ์ด ์ค์ํ ๊ฒฝ์ฐ
REST๋ฅผ ์ฌ์ฉํ ๋: - ๊ณต๊ฐ API - ๊ด๋ฒ์ํ ํด๋ผ์ด์ธํธ ํธํ์ฑ(๋ธ๋ผ์ฐ์ , ๋ชจ๋ฐ์ผ) - ์ฌ๋์ด ์ฝ์ ์ ์๋ ๋๋ฒ๊น - ์บ์ฑ๊ณผ ํ์ค HTTP ํด๋ง
GraphQL¶
GraphQL์ ํด๋ผ์ด์ธํธ๊ฐ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ํํ ์์ฒญํ ์ ์๊ฒ ํ๋ ์ฟผ๋ฆฌ ์ธ์ด์ ๋๋ค.
์คํค๋ง ์ ์¶
type User {
id: ID!
name: String!
email: String!
orders: [Order!]!
}
type Order {
id: ID!
total: Float!
items: [OrderItem!]!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String, email: String): User!
}
์ฟผ๋ฆฌ์ ๋ฎคํ ์ด์ ¶
// ํด๋ผ์ด์ธํธ ์ฟผ๋ฆฌ - ํ์ํ ํ๋๋ง ์์ฒญ
const query = `
query GetUser($id: ID!) {
user(id: $id) {
name
email
orders {
total
}
}
}
`;
// ์๋ฒ ๋ฆฌ์กธ๋ฒ(Node.js)
const resolvers = {
Query: {
user: (parent, { id }, context) => {
return context.db.findUserById(id);
},
},
User: {
orders: (user, args, context) => {
return context.db.findOrdersByUserId(user.id);
},
},
Mutation: {
createUser: (parent, { name, email }, context) => {
return context.db.createUser({ name, email });
},
},
};
N+1 ๋ฌธ์ ¶
GraphQL์ ์ค์ฒฉ ํ๋๋ฅผ ํด๊ฒฐํ ๋ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ์ผ์ผํฌ ์ ์์ต๋๋ค:
query {
users {
name
orders { # N+1: ์ฌ์ฉ์๋น ํ๋์ ์ฟผ๋ฆฌ
total
}
}
}
ํด๊ฒฐ์ฑ : DataLoader๋ ์์ฒญ์ ๋ฐฐ์น ์ฒ๋ฆฌํ๊ณ ์บ์ํฉ๋๋ค:
const DataLoader = require('dataloader');
const orderLoader = new DataLoader(async (userIds) => {
const orders = await db.findOrdersByUserIds(userIds);
return userIds.map(id => orders.filter(o => o.userId === id));
});
const resolvers = {
User: {
orders: (user, args, { orderLoader }) => {
return orderLoader.load(user.id);
},
},
};
GraphQL์ด ๋น๋๋ ๊ฒฝ์ฐ¶
GraphQL์ ์ฌ์ฉํ ๋: - ํด๋ผ์ด์ธํธ๊ฐ ์ ์ฐํ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ํ์๋ก ํ ๋(๋ชจ๋ฐ์ผ, ์น ๋์๋ณด๋) - ๊ณผ๋ค ๊ฐ์ ธ์ค๊ธฐ(over-fetching)๋ ๋ถ์กฑ ๊ฐ์ ธ์ค๊ธฐ(under-fetching)๋ฅผ ํผํ๊ณ ์ถ์ ๋ - ๋ฐฑ์๋ ๋ณ๊ฒฝ ์์ด ๋น ๋ฅธ ํ๋ก ํธ์๋ ๋ฐ๋ณต
GraphQL์ ํผํด์ผ ํ ๋: - ๊ฐ๋จํ CRUD ์์ (REST๊ฐ ๋ ๊ฐ๋จ) - ํ์ผ ์ ๋ก๋(GraphQL์ ํ์ผ ์ฒ๋ฆฌ๊ฐ ์ทจ์ฝ) - ์บ์ฑ์ด ์ค์ํ ๊ฒฝ์ฐ(HTTP ์บ์ฑ์ด REST์์ ๋ ์ ์๋)
API ์ค๊ณ ์์น¶
1. ์ผ๊ด์ฑ¶
์ผ๊ด์ฑ์ ์ธ์ง ๋ถํ๋ฅผ ์ค์ ๋๋ค. ๊ท์น์ ์ ํ๊ณ ๋ฐ๋ฅด์ธ์:
๋ค์ด๋ฐ ๊ท์น:
# ์ผ๊ด์ฑ ์์
GET /users
GET /orders
GET /products
# ์ผ๊ด์ฑ ์์
GET /users
GET /getOrders
GET /product_list
์๋ต ํ์:
// ์ผ๊ด๋ ์ฑ๊ณต ์๋ต
{
"data": { ... },
"meta": { ... }
}
// ์ผ๊ด๋ ์ค๋ฅ ์๋ต
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Email is required",
"fields": { "email": "This field is required" }
}
}
2. ์ต์ ๋๋์ ์์น¶
API๋ ์ฌ์ฉ์๊ฐ ์์ํ๋ ๋๋ก ๋์ํด์ผ ํฉ๋๋ค. ๋๋ผ์ด ๋์์ ํผํ์ธ์:
# ๋๋ผ์: ๋ฉ์๋ ์ด๋ฆ์ด ์ฝ๊ธฐ ์ ์ฉ์ ์์ํ์ง๋ง ์ํ๋ฅผ ๋ณ๊ฒฝ
def get_user_or_create(email):
user = find_user(email)
if not user:
user = create_user(email) # ๋๋!
return user
# ๋ช
ํํจ: ์ด๋ฆ์ด ๊ฐ๋ฅํ ๋ณ๊ฒฝ์ ๋ํ๋
def find_or_create_user(email):
user = find_user(email)
if not user:
user = create_user(email)
return user
3. ๋ฉฑ๋ฑ์ฑ¶
๋ฉฑ๋ฑ์ฑ ์์ ์ ๋ถ์์ฉ ์์ด ์์ ํ๊ฒ ์ฌ์๋ํ ์ ์์ต๋๋ค:
# ๋ฉฑ๋ฑ์ฑ: PUT์ ์ ์ฒด ๋ฆฌ์์ค๋ฅผ ๊ต์ฒด
PUT /users/123
{ "name": "John", "email": "john@example.com" }
# ๋น๋ฉฑ๋ฑ์ฑ: PATCH๋ ์ฆ๊ฐ์ํฌ ์ ์์
PATCH /users/123
{ "login_count": 5 } # ์ฌ์๋ํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
# ๋ ๋์: ์๋ฒ ์ ์ด ์ฆ๊ฐ
POST /users/123/login
4. ํ์ ํธํ์ฑ¶
๊ธฐ์กด ํด๋ผ์ด์ธํธ๋ฅผ ์ ๋ ๊นจ๋จ๋ฆฌ์ง ๋ง์ธ์. API๋ฅผ ์ ์คํ๊ฒ ๋ฐ์ ์ํค์ธ์:
์์ ํ ๋ณ๊ฒฝ: - ์ ํ์ ํ๋ ์ถ๊ฐ - ์ ์๋ํฌ์ธํธ ์ถ๊ฐ - ํ๋ ํ๊ธฐ(์ ์ ๊ธฐ๊ฐ ํฌํจ)
ํ๊ดด์ ๋ณ๊ฒฝ: - ํ๋ ์ ๊ฑฐ - ํ๋ ํ์ ๋ณ๊ฒฝ - ํ๋ ์ด๋ฆ ๋ณ๊ฒฝ - ์ ํจ์ฑ ๊ฒ์ฆ ๊ท์น ๋ณ๊ฒฝ
// ํ์ ํธํ: ์๋ก์ด ์ ํ์ ํ๋
{
"name": "John",
"email": "john@example.com",
"phone": "555-1234" // ์๋ก์ด, ์ ํ์
}
// ํ๊ดด์ : ํ๋ ์ ๊ฑฐ
{
"name": "John"
// "email" ์ ๊ฑฐ - ํด๋ผ์ด์ธํธ๊ฐ ๊นจ์ง!
}
๋ฒ์ ๊ด๋ฆฌ ์ ๋ต¶
ํ๊ดด์ ๋ณ๊ฒฝ์ด ํ์ํ ๋, API๋ฅผ ๋ฒ์ ๊ด๋ฆฌํ์ธ์:
1. URL ๋ฒ์ ๊ด๋ฆฌ¶
GET /v1/users
GET /v2/users
์ฅ์ : ๋ช ํํ๊ณ , ๋ผ์ฐํ ํ๊ธฐ ์ฌ์ ๋จ์ : URL ๋ณ๊ฒฝ, ์บ์ฑ ๋ฌธ์
2. ํค๋ ๋ฒ์ ๊ด๋ฆฌ¶
GET /users
Accept: application/vnd.myapi.v2+json
์ฅ์ : ๊นจ๋ํ URL ๋จ์ : ๊ฐ์์ฑ ๋ฎ์, ๋ธ๋ผ์ฐ์ ์์ ํ ์คํธํ๊ธฐ ์ด๋ ค์
3. ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์¶
GET /users?version=2
์ฅ์ : ํ ์คํธํ๊ธฐ ์ฌ์ ๋จ์ : ๋ฒ์ ๊ด๋ฆฌ์ ํํฐ๋ง์ด ํผ์ฌ
๊ถ์ฅ์ฌํญ: ์ฃผ์ ๋ฒ์ ์๋ URL ๋ฒ์ ๊ด๋ฆฌ, ์ฌ์ํ ๋ณ๊ฒฝ์๋ ๊ธฐ๋ฅ ํ๋๊ทธ ์ฌ์ฉ.
์ธ์ฆ๊ณผ ๊ถํ ๋ถ์ฌ¶
API ํค¶
GET /users
X-API-Key: abc123xyz
๊ฐ๋จํ์ง๋ง ๋ณด์์ด ์ฝํจ. ๋ฎ์ ์ํ๋์ ๋ด๋ถ API์ ์ฌ์ฉ.
OAuth 2.0¶
GET /users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
์์๋ ๊ถํ ๋ถ์ฌ๋ฅผ ์ํ ์ ๊ณ ํ์ค. ์๋ํํฐ ํตํฉ์ ์ฌ์ฉ.
JWT(JSON Web Tokens)¶
// Node.js JWT ์์
const jwt = require('jsonwebtoken');
// ํ ํฐ ์์ฑ
const token = jwt.sign(
{ userId: 123, role: 'admin' },
process.env.SECRET_KEY,
{ expiresIn: '1h' }
);
// ํ ํฐ ๊ฒ์ฆ
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).send('Unauthorized');
try {
const payload = jwt.verify(token, process.env.SECRET_KEY);
req.user = payload;
next();
} catch (err) {
res.status(403).send('Invalid token');
}
}
์๋ ์ ํ๊ณผ ์ค๋กํ๋ง¶
API๋ฅผ ๋จ์ฉ์ผ๋ก๋ถํฐ ๋ณดํธํ์ธ์:
# Python Flask ์๋ ์ ํ
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["100 per hour"]
)
@app.route('/api/search')
@limiter.limit("10 per minute")
def search():
return perform_expensive_search()
์๋ต ํค๋:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1614556800
์ ํ ์ด๊ณผ ์ 429 Too Many Requests ๋ฐํ.
์ค๋ฅ ์๋ต¶
์ผ๊ด๋๊ณ ์ ์ฉํ ์ค๋ฅ ๋ฉ์์ง ์ ๊ณต:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Email is required"
},
{
"field": "age",
"message": "Age must be at least 18"
}
],
"request_id": "req_abc123"
}
}
// C++ ๋ผ์ด๋ธ๋ฌ๋ฆฌ API ์ค๋ฅ ์ฒ๋ฆฌ
class ApiException : public std::exception {
private:
std::string message_;
int code_;
public:
ApiException(int code, const std::string& msg)
: code_(code), message_(msg) {}
int code() const { return code_; }
const char* what() const noexcept override {
return message_.c_str();
}
};
class UserService {
public:
User getUser(int id) {
if (id < 0) {
throw ApiException(400, "Invalid user ID");
}
auto user = db.findById(id);
if (!user) {
throw ApiException(404, "User not found");
}
return user;
}
};
๋ฌธ์ํ¶
ํ๋ฅญํ API๋ ํ๋ฅญํ ๋ฌธ์๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค.
OpenAPI(Swagger)¶
# openapi.yaml
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users/{userId}:
get:
summary: Get user by ID
parameters:
- name: userId
in: path
required: true
schema:
type: integer
responses:
'200':
description: User found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string
format: email
์ฅ์ : - ์๋ ์์ฑ๋ ์ธํฐ๋ํฐ๋ธ ๋ฌธ์ - ํด๋ผ์ด์ธํธ SDK ์์ฑ - ์ ํจ์ฑ ๊ฒ์ฆ
ํ์ ๋ฌธ์ ์์¶
- ์์ํ๊ธฐ: ์ฒซ API ํธ์ถ์ ์ํ ๋น ๋ฅธ ์์
- ์ธ์ฆ: ์๊ฒฉ ์ฆ๋ช ์ ์ป๊ณ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
- ์๋ ์ ํ: ์ค๋กํ๋ง ๊ท์น๊ณผ ํค๋
- ์ค๋ฅ ์ฝ๋: ์์ ๊ฐ ํฌํจ๋ ํฌ๊ด์ ๋ชฉ๋ก
- SDK: ์ธ๊ธฐ ์๋ ์ธ์ด์ฉ ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ๋ณ๊ฒฝ ๋ก๊ทธ: ๋ฒ์ ํ์คํ ๋ฆฌ์ ๋ง์ด๊ทธ๋ ์ด์ ๊ฐ์ด๋
๋ด๋ถ API ์ค๊ณ(๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ชจ๋)¶
์ต์ ํ๋ฉด์ ¶
ํ์ํ ๊ฒ๋ง ๋ ธ์ถ:
// ๋์จ: ๋ด๋ถ ์ฌํญ ๋
ธ์ถ
public class UserService {
public Database db; // ๋
ธ์ถ๋จ!
public void validateEmail(String email) { ... } // ๊ณต๊ฐ์ด์ง๋ง ๋ด๋ถ์ฉ
}
// ์ข์: ์ต์ ์ธํฐํ์ด์ค
public class UserService {
private Database db;
public User getUser(int id) { ... }
public User createUser(UserData data) { ... }
private void validateEmail(String email) { ... } // ๋น๊ณต๊ฐ
}
์บก์ํ¶
๊ตฌํ ์ธ๋ถ์ฌํญ ์จ๊ธฐ๊ธฐ:
# ๋์จ: ๊ตฌํ ๋
ธ์ถ
class ShoppingCart:
def __init__(self):
self.items = [] # ๋ด๋ถ ๋ฆฌ์คํธ์ ์ง์ ์ ๊ทผ
def add_item(self, item):
self.items.append(item)
cart = ShoppingCart()
cart.items.append(invalid_item) # ์ ํจ์ฑ ๊ฒ์ฆ ์ฐํ ๊ฐ๋ฅ!
# ์ข์: ์บก์ํ๋จ
class ShoppingCart:
def __init__(self):
self._items = []
def add_item(self, item):
self._validate_item(item)
self._items.append(item)
def get_items(self):
return self._items.copy() # ์ฐธ์กฐ๊ฐ ์๋ ๋ณต์ฌ๋ณธ ๋ฐํ
์ ์ฐฝํ ์ธํฐํ์ด์ค์ ๋น๋ ํจํด¶
์ฌ์ฉํ๊ธฐ ์ฆ๊ฑฐ์ด API ๋ง๋ค๊ธฐ:
// ์ ์ฐฝํ ์ธํฐํ์ด์ค
const query = new QueryBuilder()
.select('name', 'email')
.from('users')
.where('age', '>', 18)
.orderBy('name')
.limit(10)
.build();
// ๋น๋ ํจํด
const user = new UserBuilder()
.withName('John Doe')
.withEmail('john@example.com')
.withRole('admin')
.build();
์ผ๊ด๋ ๋ค์ด๋ฐ๊ณผ ๋งค๊ฐ๋ณ์ ์์¶
# ์ผ๊ด์ฑ: ๋ฆฌ์์ค ๋จผ์ , ๊ทธ ๋ค์ ์ต์
user_service.get_user(user_id, include_deleted=False)
order_service.get_order(order_id, include_items=True)
# ์ผ๊ด์ฑ ์์
user_service.get_user(include_deleted=False, user_id=123)
order_service.find_order(123, True) # True๊ฐ ๋ฌด์์ ์๋ฏธํ๋๊ฐ?
์ฐ์ต ๋ฌธ์ ¶
์ฐ์ต ๋ฌธ์ 1: ์์ ์ ์ํ REST API ์ค๊ณ¶
๋ค์ ์๊ตฌ์ฌํญ์ ๊ฐ์ง ์จ๋ผ์ธ ์์ ์ ์ํ REST API๋ฅผ ์ค๊ณํ์ธ์: - ์ฑ ํ์(์ฅ๋ฅด, ์ ์๋ณ ํํฐ๋ง) - ์ฑ ๊ฒ์ - ์ฌ์ฉ์ ๊ณ์ ๊ณผ ์ธ์ฆ - ์ฅ๋ฐ๊ตฌ๋ - ์ฃผ๋ฌธ ๋ฐฐ์น - ์ฃผ๋ฌธ ๋ด์ญ
์ ๊ณตํ ๊ฒ: 1. HTTP ๋ฉ์๋๊ฐ ํฌํจ๋ ์๋ํฌ์ธํธ ๋ชฉ๋ก 2. ์ํ ์์ฒญ/์๋ต ๋ณธ๋ฌธ 3. ๊ฐ ์๋ํฌ์ธํธ์ ์ํ ์ฝ๋ 4. ํ์ด์ง๋ค์ด์ ์ ๋ต
์ฐ์ต ๋ฌธ์ 2: API ๊ฒํ ¶
์ด API ์ค๊ณ๋ฅผ ๊ฒํ ํ๊ณ ์ข์ API ์ค๊ณ ์์น์ ์๋ฐ ์ฌํญ์ ์๋ณํ์ธ์:
GET /getBook?id=123
POST /createBook?title=NewBook&author=JohnDoe
GET /updateBook?id=123&title=UpdatedTitle
DELETE /books/remove/123
GET /books?page=1&limit=1000
POST /users/login (์๊ฒฉ ์ฆ๋ช
์ด ํ๋ ค๋ 200 ๋ฐํ)
๋ฌด์์ด ์๋ชป๋์๋์ง ๋์ดํ๊ณ ๊ฐ์ ์์ ์ ์ํ์ธ์.
์ฐ์ต ๋ฌธ์ 3: ๋ฒ์ ๊ด๋ฆฌ ์ ๋ต¶
ํ์ฌ API์ ๋ค์ ์๋ํฌ์ธํธ๊ฐ ์์ต๋๋ค:
GET /users/123
Response: { "name": "John", "email": "john@example.com" }
ํ๊ดด์ ๋ณ๊ฒฝ์ ์ถ๊ฐํด์ผ ํฉ๋๋ค: name์ first_name๊ณผ last_name์ผ๋ก ๋ถํ . ๋ค์์ ์ํํ๋ ๋ฒ์ ๊ด๋ฆฌ ์ ๋ต์ ์ค๊ณํ์ธ์:
1. ๊ธฐ์กด ํด๋ผ์ด์ธํธ๋ฅผ ๊นจ๋จ๋ฆฌ์ง ์์
2. ์ ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ์ ๋ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ํจ
3. ๋ง์ด๊ทธ๋ ์ด์
๊ฒฝ๋ก ์ ๊ณต
์ฐ์ต ๋ฌธ์ 4: ์ค๋ฅ ์ฒ๋ฆฌ ์ค๊ณ¶
๋ค์์ ์ฒ๋ฆฌํด์ผ ํ๋ ๋ฑ ํน API๋ฅผ ์ํ ํฌ๊ด์ ์ธ ์ค๋ฅ ์๋ต ํ์์ ์ค๊ณํ์ธ์: - ์ ํจ์ฑ ๊ฒ์ฆ ์ค๋ฅ(์ฌ๋ฌ ํ๋) - ์์ก ๋ถ์กฑ - ๊ณ์ ์ ๊น - ์๋ ์ ํ - ์๋ฒ ์ค๋ฅ
๊ฐ ์ค๋ฅ ์ ํ์ ๋ํ JSON ์์ ๋ฅผ ์ ๊ณตํ์ธ์.
์ฐ์ต ๋ฌธ์ 5: ๋ผ์ด๋ธ๋ฌ๋ฆฌ API ์ค๊ณ¶
๋ค์์ ์ง์ํ๋ ์ ํธํ๋ ์ธ์ด๋ก SQL ์ฟผ๋ฆฌ ๋น๋๋ฅผ ์ํ ์ ์ฐฝํ API๋ฅผ ์ค๊ณํ์ธ์: - ์ฌ๋ฌ ์ปฌ๋ผ์ผ๋ก SELECT - ํ ์ด๋ธ ์ด๋ฆ์ผ๋ก FROM - ์ฌ๋ฌ ์กฐ๊ฑด์ผ๋ก WHERE(AND/OR) - JOIN ์์ - ORDER BY - LIMIT๊ณผ OFFSET
์ฌ์ฉ ์์ :
query.select('id', 'name')
.from('users')
.where('age > ?', 18)
.orderBy('name')
.limit(10)
์์ฝ¶
์ข์ API ์ค๊ณ๋ ์ฌ์ฉ์์ ๋ํ ๊ณต๊ฐ์ ๋๋ค:
- REST: ๋ฆฌ์์ค ์งํฅ, HTTP ์๋ฏธ๋ก ์ฌ์ฉ, ๊ณต๊ฐ API์ ์ต์
- RPC/gRPC: ์ก์ ์งํฅ, ํจ์จ์ , ๋ด๋ถ ๋ง์ดํฌ๋ก์๋น์ค์ ์ต์
- GraphQL: ์ ์ฐํ ์ฟผ๋ฆฌ, ๋ฐ์ดํฐ ์ค์ฌ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ต์
- ์ผ๊ด์ฑ: ์์ธก ๊ฐ๋ฅํ ๋ค์ด๋ฐ, ๊ตฌ์กฐ, ๋์
- ๋ฉฑ๋ฑ์ฑ: PUT๊ณผ DELETE์ ์์ ํ ์ฌ์๋
- ๋ฒ์ ๊ด๋ฆฌ: ํด๋ผ์ด์ธํธ๋ฅผ ๊นจ๋จ๋ฆฌ์ง ์๊ณ ๋ณ๊ฒฝ ๊ด๋ฆฌ
- ๋ฌธ์ํ: ์์ ๋ฅผ ํตํด API๋ฅผ ์๋ช ํ๊ฒ ๋ง๋ค๊ธฐ
- ์ค๋ฅ ์ฒ๋ฆฌ: ๋ช ํํ๊ณ ์คํ ๊ฐ๋ฅํ ์ค๋ฅ ๋ฉ์์ง
๊ธฐ์ตํ์ธ์: API๋ ์ ํ์ ๋๋ค. API ์๋น์๋ฅผ ๊ณ ๊ฐ์ผ๋ก ๋ํ์ธ์โ๊ทธ๋ค์ ๊ฒฝํ์ ํฌ์ํ์ธ์.
๋ด๋น๊ฒ์ด์ ¶
โ ์ด์ : ์ฑ๋ฅ ์ต์ ํ | ๋ค์: ๋ฒ์ ๊ด๋ฆฌ ์ํฌํ๋ก์ฐ โ