Data API 1 — REST & vì sao data team cần API
Một pipeline dữ liệu chạy đẹp nhưng không ai dùng được kết quả thì cũng vô nghĩa. Bài toán muôn thuở của team dữ liệu ngân hàng: đội core banking cần điểm tín dụng, app mobile cần lịch sử giao dịch đã làm sạch, đội chống gian lận cần feature real-time. Cách cũ là gửi file CSV qua email, cấp quyền SELECT thẳng vào warehouse, hay dựng view chung cho mọi người. Cách này rồi sẽ sụp đổ. Bài này mở màn series về xây API phục vụ dữ liệu, giải thích REST từ gốc và vì sao FastAPI là lựa chọn hợp lý cho team dữ liệu.
1. Vì sao data team cần xây API
1.1. Chia sẻ file / DB trực tiếp: nợ kỹ thuật tích luỹ
Ba cách "chia sẻ nhanh" đều có mặt trái nghiêm trọng khi quy mô tăng:
| Cách chia sẻ | Vấn đề |
|---|---|
| Gửi file CSV/Excel | Dữ liệu chết ngay khi gửi, không real-time, không kiểm soát ai đọc, dễ lộ PII |
Cấp quyền SELECT vào warehouse | Consumer phụ thuộc schema vật lý — bạn đổi tên cột là họ vỡ; không giới hạn được truy vấn nặng; khó audit |
| View/materialized view chung | Vẫn dính chặt vào cấu trúc bảng, không encapsulate được logic nghiệp vụ, không phân quyền theo dòng dữ liệu |
Điểm chung: consumer bị buộc chặt (coupling) vào cách bạn lưu trữ dữ liệu. Mỗi thay đổi vật lý ở phía bạn là một sự cố tiềm tàng ở phía họ.
1.2. API là hợp đồng (contract)
API đảo ngược quan hệ đó. Bạn công bố một hợp đồng — "gọi GET /v1/accounts/{id}/transactions thì nhận về JSON dạng này" — và tự do đổi mọi thứ bên trong (đổi warehouse, thêm cache, tính lại feature) miễn giữ nguyên hợp đồng. Consumer không biết và không cần biết dữ liệu nằm ở đâu.
Với team dữ liệu, API mở ra các mô hình phục vụ:
- Data-as-a-Service: cung cấp dữ liệu đã làm sạch/chuẩn hoá qua endpoint thay vì đổ file. VD:
GET /v1/customers/{id}/profile-360. - Feature serving: phục vụ feature cho model online với độ trễ thấp. VD:
GET /v1/features/customer/{id}trả về 40 feature đã tính sẵn cho mô hình chấm điểm. - Inference endpoint: bọc mô hình ML sau một endpoint. VD:
POST /v1/score/creditnhận hồ sơ, trả về điểm và phân khúc rủi ro. - Internal data product: đóng gói một tập dữ liệu thành sản phẩm nội bộ có phiên bản, SLA, tài liệu — các đội khác "mua" bằng cách gọi API.
Lợi ích cụ thể: kiểm soát truy cập tập trung (auth), audit đầy đủ (ai gọi gì lúc nào), giới hạn tải (rate limit), che giấu độ phức tạp, và versioning để tiến hoá mà không phá vỡ khách hàng cũ.
2. REST là gì
REST (Representational State Transfer) là một phong cách kiến trúc cho hệ thống phân tán, do Roy Fielding mô tả năm 2000. Nó không phải chuẩn bắt buộc mà là tập nguyên tắc. Ba nguyên tắc cốt lõi cần nhớ:
- Resource (tài nguyên): mọi thứ đáng để tham chiếu đều là một tài nguyên, định danh bằng URL. Khách hàng, tài khoản, giao dịch — mỗi loại là một resource.
/v1/accounts/12345trỏ tới đúng một tài khoản. - Stateless (không trạng thái): mỗi request tự chứa đủ thông tin để server xử lý; server không nhớ ngữ cảnh giữa các request. Nhờ vậy mới scale ngang được — request nào rơi vào server nào cũng xử lý được như nhau. (Token auth được gửi kèm mỗi request chính là hệ quả của nguyên tắc này.)
- Representation (biểu diễn): client thao tác trên biểu diễn của resource, phổ biến nhất là JSON. Cùng một tài khoản có thể biểu diễn thành JSON, XML, hay CSV; client và server thoả thuận định dạng qua content negotiation (mục 5).
Một hệ quả quan trọng: thao tác được thể hiện qua HTTP method, không nhét vào URL. GET /getTransaction?id=5 là "sai tinh thần" REST; đúng ra là GET /v1/transactions/5.
3. HTTP methods và ngữ nghĩa
HTTP method (còn gọi là "động từ") diễn tả bạn muốn làm gì với resource:
| Method | Ý nghĩa | Safe | Idempotent |
|---|---|---|---|
GET | Đọc resource, không đổi trạng thái | ✅ | ✅ |
POST | Tạo resource mới / kích hoạt xử lý | ❌ | ❌ |
PUT | Thay thế toàn bộ resource | ❌ | ✅ |
PATCH | Sửa một phần resource | ❌ | ❌* |
DELETE | Xoá resource | ❌ | ✅ |
Hai tính chất phải hiểu rõ:
- Safe: không làm thay đổi trạng thái server. Chỉ
GET(vàHEAD,OPTIONS) là safe. Gọi bao nhiêu lần cũng không hại — nên proxy/cache được phép cache thoải mái. - Idempotent: gọi 1 lần hay N lần cho cùng kết quả trạng thái.
PUT /accounts/5 {balance: 0}chạy 3 lần thì tài khoản vẫnbalance=0. Ngược lạiPOST /transactionschạy 3 lần tạo ra 3 giao dịch — nguy hiểm khi client retry vì lỗi mạng. Đây là lý do các API tạo giao dịch tài chính thường yêu cầu idempotency key để chống trùng lặp.
(*) PATCH có thể idempotent tuỳ cách thiết kế payload; đừng mặc định là có.
4. Status code
Server trả về mã trạng thái 3 chữ số. Nhóm theo chữ số đầu:
- 2xx — Thành công
200 OK: request thành công (điển hình choGET,PUT,PATCH).201 Created: đã tạo resource mới (điển hình choPOST), nên kèm headerLocationtrỏ tới resource mới.
- 4xx — Lỗi phía client (client gửi sai, đừng retry y hệt)
400 Bad Request: cú pháp/định dạng sai.401 Unauthorized: chưa xác thực (thiếu/sai token).403 Forbidden: đã xác thực nhưng không có quyền.404 Not Found: resource không tồn tại.422 Unprocessable Entity: cú pháp đúng nhưng dữ liệu không hợp lệ về mặt nghiệp vụ (FastAPI dùng mã này cho lỗi validation).429 Too Many Requests: vượt rate limit.
- 5xx — Lỗi phía server (lỗi của bạn)
500 Internal Server Error: lỗi không lường trước.
Phân biệt 401 vs 403 và 400 vs 422 là dấu hiệu của một API được thiết kế nghiêm túc. Ranh giới quan trọng nhất: 4xx là lỗi của người gọi, 5xx là lỗi của bạn — đặt sai mã sẽ khiến monitoring báo động nhầm.
5. Thiết kế resource, URL và JSON
5.1. Quy ước đặt URL
- Dùng danh từ số nhiều:
/v1/accounts,/v1/transactions— không phải/getAccount. - Phân cấp thể hiện quan hệ:
/v1/accounts/12345/transactions= "các giao dịch của tài khoản 12345". - Phân trang (pagination): dữ liệu lớn phải cắt trang. Kiểu offset:
?limit=50&offset=100. Kiểu cursor (tốt hơn cho dữ liệu thay đổi liên tục):?limit=50&cursor=eyJpZCI6MTB9. - Lọc & sắp xếp qua query string:
?kind=debit&sort=-created_at&min_amount=1000000. - Versioning: đặt version vào path —
/v1/.... Khi cần breaking change, mở/v2/và cho/v1/sống song song một thời gian. Đây là cơ chế bảo vệ hợp đồng đã nói ở mục 1.2.
5.2. Request / Response JSON và content negotiation
Body trao đổi thường là JSON, với header khai báo định dạng:
Content-Type: application/json— client nói "body tôi gửi là JSON".Accept: application/json— client nói "tôi muốn nhận JSON".
Server đọc Accept để quyết định định dạng trả về — đó chính là content negotiation. Một response GET /v1/accounts/12345 điển hình:
{
"id": 12345,
"account_no": "0123456789",
"balance": 15000000,
"currency": "VND",
"customer_id": 987
}
Endpoint này khi chạy sẽ dịch ra một truy vấn SQL trên warehouse. Ví dụ endpoint lấy lịch sử giao dịch GET /v1/accounts/12345/transactions bên dưới chạy đúng câu sau (chạy được trên sandbox PostgreSQL):
-- ▶ Chạy được
SELECT id, amount, kind, created_at
FROM transactions
WHERE account_id = 12345
ORDER BY created_at DESC
LIMIT 50;
Điểm mấu chốt: consumer chỉ thấy JSON gọn gàng, hoàn toàn không biết bảng transactions tồn tại. Bạn được tự do đổi câu SQL, thêm cache, hay chuyển sang bảng khác mà không ảnh hưởng người gọi.
6. Hệ sinh thái Python và vì sao chọn FastAPI
Python có ba framework web phổ biến:
| Framework | Đặc điểm | Phù hợp |
|---|---|---|
| Flask | Micro-framework, tối giản, linh hoạt; cần thêm nhiều thư viện phụ | API nhỏ, prototype nhanh |
| Django | Full-stack, kèm ORM/admin/auth; nặng, thiên về web app | Ứng dụng lớn có giao diện, cần admin sẵn |
| FastAPI | Async-first, dựa trên type hints; tự sinh docs, validate tự động | API dữ liệu, microservice, ML serving |
Series này chọn FastAPI vì bốn lý do đúng vào nhu cầu của team dữ liệu:
- Async native: dựa trên ASGI, xử lý hàng nghìn kết nối I/O đồng thời — hợp cho API "gọi DB/dịch vụ khác rồi trả về" (xem thêm Python 8 — SQL, API & Async).
- Type hints → validation tự động: khai báo kiểu dữ liệu bằng type hint Python + Pydantic, FastAPI tự validate request và trả
422khi sai, không phải viết tay. - Tự sinh tài liệu: mỗi API tự động có trang docs tương tác (Swagger UI, ReDoc) từ chính code — cực quý khi các đội khác cần "hợp đồng" rõ ràng.
- Hiệu năng cao: thuộc nhóm nhanh nhất trong các framework Python.
Các bài tiếp theo trong series đi sâu từng phần: khởi động FastAPI (api-02), validate dữ liệu với Pydantic (api-03), tích hợp DB (api-04), auth & bảo mật (api-05), async & hiệu năng (api-06), testing & docs (api-07), và triển khai (api-08). Nếu bạn đến từ góc độ ingestion (kéo dữ liệu từ API bên ngoài), xem pyde-06 — APIs & Ingestion — đó là chiều ngược lại của cùng câu chuyện.
7. REST vs GraphQL vs gRPC (ngắn)
REST không phải lựa chọn duy nhất:
- REST: dựa trên HTTP + resource + JSON. Đơn giản, phổ biến, cache tốt, ai cũng hiểu. Nhược: dễ "over-fetch" (lấy dư dữ liệu) hoặc "under-fetch" (phải gọi nhiều lần).
- GraphQL: client tự khai báo chính xác field cần, trong một endpoint duy nhất. Giải quyết over/under-fetch, hợp cho frontend phức tạp. Đổi lại: khó cache, dễ bị truy vấn nặng, phức tạp hơn khi vận hành.
- gRPC: dùng HTTP/2 + Protocol Buffers (nhị phân), rất nhanh, có streaming, hợp cho giao tiếp giữa các service nội bộ độ trễ thấp. Đổi lại: không đọc được bằng mắt, khó dùng trực tiếp từ trình duyệt.
Nguyên tắc chọn cho team dữ liệu ngân hàng: REST cho API công khai/nội bộ đa dụng (dễ hiểu, dễ audit), gRPC cho đường truyền feature giữa các service ML khi độ trễ là tối thượng, cân nhắc GraphQL khi consumer là frontend cần dữ liệu linh hoạt.
8. Vòng đời một request REST
Sơ đồ trên gói gọn mọi khái niệm trong bài: client gọi bằng HTTP method trên một resource có versioning, đi qua auth/rate limit (đẻ ra các status code 401/403/429), được validate (422 nếu sai), rồi API dịch sang SQL và trả biểu diễn JSON kèm status 200.
Use case thực tế
Bối cảnh: Ngân hàng NCB có app mobile cần hiển thị màn hình "Sao kê tài khoản". Trước đây, đội mobile được cấp quyền SELECT thẳng vào một read-replica của warehouse. Hậu quả sau 6 tháng:
- Đội dữ liệu đổi tên cột
txn_amount→amountđể chuẩn hoá; app mobile crash toàn bộ màn hình sao kê trong 3 giờ. - Một query lỗi từ app quét toàn bảng 400 triệu dòng, đẩy replica lên 100% CPU, ảnh hưởng cả report BI.
- Không audit được ai xem tài khoản nào — không đáp ứng yêu cầu của phòng Tuân thủ.
Giải pháp: dựng một FastAPI service account-history-api đứng giữa.
- Định nghĩa hợp đồng:
GET /v1/accounts/{id}/transactions?limit=50&cursor=..., trả JSON cố định schema. - Endpoint chạy đúng câu SQL đã kiểm soát (có
LIMIT, có index trênaccount_id, created_at) — chặn được query quét toàn bảng. - Mọi request qua auth: token của app mobile chỉ được xem tài khoản của chính user đã đăng nhập (
403nếu vượt quyền). Ghi log đầy đủ cho phòng Tuân thủ. - Rate limit 100 request/phút/user (
429khi vượt) để bảo vệ backend.
Kết quả: khi đội dữ liệu đổi tên cột txn_amount → amount, họ chỉ sửa một dòng mapping trong service; app mobile không hề biết và không phải deploy lại. Sự cố schema-breaking từ "3 giờ downtime" xuống "0". Đây chính là giá trị của tầng API: đổi hợp đồng chỉ khi bạn chủ động, còn thay đổi nội bộ là vô hình với consumer.
Ghi nhớ
- Chia sẻ file/DB trực tiếp buộc consumer dính chặt vào schema vật lý; API là hợp đồng cho phép bạn đổi nội bộ mà không phá vỡ người dùng.
- Với team dữ liệu, API mở ra: data-as-a-service, feature serving, inference endpoint, internal data product.
- REST = resource (URL) + stateless + representation (JSON); thao tác thể hiện qua HTTP method, không nhét vào URL.
- Nhớ safe (chỉ
GET) và idempotent (GET/PUT/DELETEcó,POSTkhông) — quyết định việc retry có an toàn không. - Status code: 4xx là lỗi client, 5xx là lỗi server; phân biệt
401(chưa auth) vs403(không quyền),400(sai cú pháp) vs422(sai nghiệp vụ). - Thiết kế URL: danh từ số nhiều, phân cấp theo quan hệ, phân trang/lọc qua query string, versioning bằng
/v1. - Chọn FastAPI vì async, type hints, validation tự động và tự sinh docs — các bài sau đi sâu từng phần.
- REST cho API đa dụng dễ audit; gRPC cho đường feature nội bộ độ trễ thấp; GraphQL khi frontend cần dữ liệu linh hoạt.
Bài viết liên quan
Exception handling, context manager (with), đọc/ghi file, JSON/CSV và logging đúng cách.
Định nghĩa hàm, tham số, *args/**kwargs, lambda, module/package, pip và virtualenv.
Lớp, kế thừa, đa hình, dunder methods, dataclass, type hints và nguyên tắc viết code sạch.
Kết nối CSDL với SQLAlchemy, gọi REST API với requests/httpx, và lập trình bất đồng bộ asyncio.