Trong thiết kế kiến trúc của các hệ thống phân tán, API không chỉ là cầu nối cho sự tương tác giữa các hệ thống; chúng còn là các hợp đồng kết nối các chồng công nghệ (tech stack), văn hóa tổ chức, và thậm chí cả các kỷ nguyên phát triển khác nhau. Trong các chi tiết thiết kế của API RESTful, một chủ đề tưởng chừng nhỏ lại gây ra những tranh cãi không ngừng: Tên trường JSON nên sử dụng camelCase hay snake_case?
Đây không chỉ là một lựa chọn thẩm mỹ. Nó chạm đến sự "khác biệt trở kháng" (impedance mismatch) giữa các lớp lưu trữ phía backend và các lớp trình bày phía frontend, liên quan đến hiệu suất tuần tự hóa (serialization), hiệu quả truyền tải mạng, Trải nghiệm nhà phát triển (DX), và tâm lý học nhận thức.
Dựa trên lịch sử của các ngôn ngữ lập trình, cơ chế triển khai kỹ thuật cơ bản, và các quyết định kiến trúc của những gã khổng lồ trong ngành như Google và Stripe, bài viết này sẽ cung cấp một hướng dẫn ra quyết định ở cấp độ chuyên gia.
nút
1. Nguồn Gốc Lịch Sử: Một Lựa Chọn Ký Hiệu
Để hiểu cuộc tranh luận này, chúng ta phải theo dõi sự phát triển của các ngôn ngữ máy tính. Các quy ước đặt tên không tự nhiên mà có; chúng là sản phẩm của những hạn chế phần cứng và văn hóa cộng đồng trong các thời kỳ cụ thể.
Nguồn Gốc của snake_case: C và Triết Lý Unix
Sự phổ biến của snake_case (ví dụ: user_id) bắt nguồn từ C và Unix vào những năm 1970. Mặc dù các bàn phím đời đầu (như Teletype Model 33) đã có phím Shift, nhiều trình biên dịch thời đó lại không phân biệt chữ hoa chữ thường. Để phân biệt rõ các từ trên màn hình có độ phân giải thấp, các lập trình viên đã dùng dấu gạch dưới để mô phỏng khoảng trắng trong ngôn ngữ tự nhiên. Thói quen này đã ăn sâu vào các tiêu chuẩn cơ sở dữ liệu SQL. Cho đến ngày nay, kiểu đặt tên cột mặc định cho PostgreSQL và MySQL vẫn là snake_case, tạo tiền đề cho sự 'xung đột ánh xạ' (mapping friction) giữa API và cơ sở dữ liệu trong tương lai.
Sự Trỗi Dậy của camelCase: Thống Trị của Java và JavaScript
camelCase (ví dụ: userId) phát triển cùng với Lập trình Hướng đối tượng (Smalltalk, C++, Java). Java đã thiết lập tiêu chuẩn công nghiệp về "PascalCase cho các lớp, camelCase cho các phương thức/biến." Bước ngoặt quyết định là sự ra đời của JavaScript. Mặc dù JSON có nguồn gốc từ các đối tượng nguyên bản của JS, thư viện chuẩn JS (ví dụ: getElementById) đã áp dụng camelCase trên diện rộng. Khi AJAX và JSON thay thế XML trở thành các định dạng trao đổi dữ liệu thống trị, camelCase đã giành được trạng thái "bản địa" trong lĩnh vực Web.
2. Xung Đột Cốt Lõi: Khác Biệt Trở Kháng Giữa Các Chồng Công Nghệ
Khi dữ liệu lưu chuyển giữa các ngôn ngữ khác nhau, không thể tránh khỏi việc gặp phải "khác biệt trở kháng."
Góc Nhìn Backend (Python/Ruby/SQL)
Ở phía backend, cộng đồng Python (PEP 8) và Ruby đặc biệt khuyến nghị sử dụng snake_case.
- Python/Django/FastAPI: Các mô hình Pydantic của bạn thường tương ứng trực tiếp với cấu trúc bảng cơ sở dữ liệu:
class UserProfile(BaseModel):
first_name: str # Python convention
last_name: strNếu API yêu cầu camelCase, bạn phải cấu hình các bí danh (alias) hoặc bộ chuyển đổi (converter) trong lớp tuần tự hóa. Mặc dù khả thi, điều này làm tăng thêm một lớp logic ánh xạ.
- Cơ sở dữ liệu SQL: Hầu hết tên cột trong cơ sở dữ liệu sử dụng
snake_case. Nếu API sử dụngcamelCase, lớp ORM phải xử lý việc chuyển đổi một cách nhất quán. - Lựa chọn của Stripe: Lý do Stripe và GitHub kiên quyết chọn
snake_casephần lớn là vì kiến trúc ban đầu của họ được xây dựng trên chồng công nghệ Ruby. Việc trực tiếp lộ các mô hình nội bộ đảm bảo tính nhất quán của backend.
Góc Nhìn Frontend (JavaScript/TypeScript)
Trong trình duyệt, camelCase là phong cách thống trị tuyệt đối.
- Phân mảnh phong cách code: Nếu một API trả về
snake_case, mã frontend sẽ trở nên không nhất quán:
const user = await fetchUser();
console.log(user.first_name); // Violates ESLint camelcase rule
render(user.email_address);ESLint sẽ gắn cờ đây là cảnh báo, buộc các nhà phát triển phải tắt quy tắc hoặc chuyển đổi dữ liệu ngay lập tức khi nhận được.
- Khó khăn khi Destructuring: Trong phát triển JS/TS hiện đại, destructuring đối tượng rất phổ biến. Nếu các trường là
snake_case, chúng phải được đổi tên trong quá trình destructuring để tránh làm ô nhiễm phạm vi cục bộ:
// Verbose renaming
const { first_name: firstName, last_name: lastName } = response.data;Điều này làm tăng mã boilerplate và khả năng xảy ra lỗi.
3. Những Hiểu Lầm Về Hiệu Suất: Tuần Tự Hóa và Truyền Tải Mạng
Về hiệu suất, tồn tại hai hiểu lầm phổ biến: "Việc chuyển đổi tên trường quá chậm" và "Dấu gạch dưới làm tăng kích thước tải trọng." Hãy làm rõ bằng dữ liệu.
Hiểu Lầm 1: Chi Phí Chuyển Đổi Thời Gian Chạy
- Ngôn ngữ động: Trong Python hoặc Ruby, việc chuyển đổi tên trường thông qua thay thế regex trên mỗi yêu cầu thực sự tiêu tốn CPU. Tuy nhiên, các framework hiện đại (như Pydantic v2, được viết lại bằng Rust) giảm thiểu chi phí này thông qua các ánh xạ Schema được tính toán trước.
- Ngôn ngữ tĩnh (Go/Java/Rust): Điều này thực sự là "chi phí bằng không." Các thẻ struct của Go (
json:"firstName") hoặc bộ nhớ đệm ánh xạ của Java Jackson được xác định tại thời điểm biên dịch hoặc khởi động. Thực thi thời gian chạy liên quan đến việc sao chép byte đơn giản, không phải tính toán động.
Lưu ý: Không bao giờ thực hiện chuyển đổi đệ quy toàn cục ở frontend (luồng chính của trình duyệt) bằng cách sử dụng các interceptor (ví dụ: Axios). Đối với các phản hồi lớn, điều này gây ra tình trạng giật lag trang và tiêu tốn bộ nhớ. Kết luận: Backend nên xử lý việc chuyển đổi.
Hiểu Lầm 2: Kích Thước Truyền Tải và Nén
Về lý thuyết, first_name dài hơn firstName một byte. Tuy nhiên, với tính năng nén Gzip hoặc Brotli được bật (cấu hình HTTP tiêu chuẩn), sự khác biệt này gần như biến mất.
- Nguyên tắc: Các thuật toán nén dựa vào "tham chiếu chuỗi trùng lặp." Trong một mảng JSON, các khóa có tính lặp lại cao. Thuật toán lưu trữ
first_namevào một từ điển khi gặp lần đầu và thay thế các lần xuất hiện tiếp theo bằng một con trỏ nhỏ. - Thử nghiệm hiệu suất: Các thử nghiệm cho thấy rằng dưới mức nén Gzip cấp 6, sự khác biệt kích thước giữa hai kiểu thường nằm trong khoảng từ 0.5% đến 1%. Trừ khi bạn đang truyền dữ liệu qua các liên kết vệ tinh, đây không phải là vấn đề đáng lo ngại.
4. Trải Nghiệm Nhà Phát Triển (DX) và Tâm Lý Học Nhận Thức
Kiến trúc không chỉ là về máy móc; mà còn là về con người.
- Khả năng đọc của snake_case: Tâm lý học nhận thức cho thấy dấu gạch dưới cung cấp sự phân tách trực quan rõ ràng.
parse_db_xmlđược bộ não phân tích nhanh hơnparseDbXml, đặc biệt khi xử lý các từ viết tắt liên tiếp (ví dụ:XMLHTTPRequestso vớiXmlHttpRequest). Đây là một lý do tài liệu của Stripe được coi là rất dễ đọc. - Tính nhất quán của camelCase: Trạng thái dòng chảy (flow state) được mang lại bởi sự nhất quán toàn diện (full-stack) là quan trọng hơn. Đối với các nhóm JS/TS full-stack, việc sử dụng
camelCasetrên cả frontend và backend cho phép tái sử dụng trực tiếp các định nghĩa kiểu (Interface/Zod Schema), loại bỏ hoàn toàn gánh nặng nhận thức của việc "dịch chuyển trong đầu".
5. Tiêu Chuẩn Ngành và Lý Do
| Tổ Chức | Lựa Chọn | Logic Cốt Lõi & Bối Cảnh |
|---|---|---|
| camelCase | Hướng dẫn API của Google (AIP-140) bắt buộc lowerCamelCase cho JSON. Ngay cả khi các định nghĩa Protobuf nội bộ sử dụng snake_case, lớp chuyển đổi bên ngoài sẽ tự động chuyển sang camelCase để phù hợp với hệ sinh thái Web. |
|
| Microsoft | camelCase | Với việc .NET Core đón nhận mã nguồn mở và sự ra đời của TypeScript, Microsoft đã hoàn toàn chuyển sang các tiêu chuẩn Web, từ bỏ PascalCase ban đầu. |
| Stripe | snake_case | Một công ty điển hình với chồng công nghệ Ruby. Họ che giấu sự khác biệt bằng cách cung cấp các Client SDK cực kỳ mạnh mẽ. Khi bạn sử dụng Node SDK, mặc dù snake_case được truyền đi, các chữ ký phương thức của SDK thường tuân theo các quy ước của JS. |
| JSON:API | camelCase | Đặc tả do cộng đồng phát triển này khuyến nghị rõ ràng camelCase, phản ánh sự đồng thuận của cộng đồng Web. |
6. Lời Khuyên Kiến Trúc Chuyên Sâu: Tách Rời và DTO
Một anti-pattern phổ biến là "Truyền thẳng" (Pass-through): trực tiếp tuần tự hóa các thực thể cơ sở dữ liệu để trả về chúng.
- Nếu bạn làm điều này, và các cột DB của bạn là
snake_case, API của bạn sẽ trở thànhsnake_case. - Rủi ro: Điều này vi phạm nguyên tắc ẩn thông tin, làm lộ cấu trúc bảng và tạo ra sự ghép nối chặt chẽ giữa API và DB. Nếu DB được đổi tên, API sẽ bị hỏng.
Thực hành tốt nhất: Giới thiệu một lớp DTO (Data Transfer Object). Bất kể cơ sở dữ liệu cơ bản được đặt tên như thế nào, bạn nên định nghĩa một hợp đồng API độc lập (DTO). Vì bạn đang định nghĩa một DTO, tại sao không định nghĩa nó là camelCase để tuân thủ các tiêu chuẩn Web? Các công cụ ánh xạ hiện đại (MapStruct, AutoMapper, Pydantic) xử lý việc chuyển đổi này một cách dễ dàng.
7. Hướng Tới Tương Lai: GraphQL và gRPC
GraphQL: Cộng đồng gần như 100% đón nhận camelCase. Nếu nhóm của bạn có kế hoạch giới thiệu GraphQL trong tương lai, việc thiết kế API REST với camelCase ngay bây giờ là một chiến lược "tương thích tiến" khôn ngoan.
gRPC: Tiêu chuẩn Protobuf quy định: các tệp .proto sử dụng snake_case cho định nghĩa trường, nhưng chúng phải được chuyển đổi sang camelCase khi ánh xạ sang JSON. Đây là giải pháp tiêu chuẩn của Google cho các môi trường đa ngôn ngữ.
8. Tóm Tắt và Ma Trận Quyết Định
Không có đúng hay sai tuyệt đối, chỉ có sự đánh đổi. Dưới đây là khuôn khổ quyết định cuối cùng:
Đề Xuất: Mặc Định sử dụng camelCase
Đối với phần lớn các API RESTful mục đích chung mới, phục vụ cho các ứng dụng Web/Di động, camelCase được khuyến nghị mạnh mẽ.
Lý do: Phù hợp với sự thống trị của JSON/JavaScript/TypeScript, chấp nhận thói quen của 90% người tiêu dùng.
Công cụ: Nhận được sự hỗ trợ tốt nhất từ các trình tạo mã OpenAPI, Swagger UI và các IDE hiện đại.
Khi nào nên sử dụng snake_case?
- 1. Người dùng cụ thể: Người dùng chính của API là các nhà khoa học dữ liệu Python hoặc các chuyên viên vận hành hệ thống (Curl/Bash).
- 2. Hệ thống kế thừa: Các API hiện có đã sử dụng
snake_case. Tính nhất quán > Thực hành tốt nhất. Không trộn lẫn các phong cách trong cùng một hệ thống. - 3. Cực đoan về tốc độ Backend: Sử dụng Python/Ruby mà không có đủ tài nguyên để duy trì một lớp DTO, truyền thẳng các mô hình cơ sở dữ liệu.
Bảng Quyết Định
| Kích Thước | Phong Cách Khuyến Nghị |
|---|---|
| Web Frontend / Ứng dụng Di động | camelCase (Không khác biệt trở kháng, an toàn kiểu) |
| Phân tích Dữ liệu / Tính toán Khoa học | snake_case (Phù hợp với thói quen của Python/R) |
| Backend Node.js / Go / Java | camelCase (Hỗ trợ gốc hoặc thư viện hoàn hảo) |
| Backend Python / Ruby | camelCase (Khuyến nghị bộ chuyển đổi) hoặc snake_case (Chỉ dành cho công cụ nội bộ) |
| Đội ngũ Full-Stack | Mức độ full-stack càng cao, camelCase càng được khuyến nghị |
Bản chất của thiết kế API là sự thấu cảm. Trong bối cảnh API Web, việc gói gọn sự phức tạp ở backend (xử lý ánh xạ) và mang lại sự tiện lợi cho người dùng (tuân thủ thói quen JS) là lựa chọn thể hiện sự chuyên nghiệp cao hơn.
