Thiết kế API: Lựa chọn giữa tên và số định danh trong URL

Tháng Sáu 4, 2021
Nga Pham

Có một sự bất đồng trong việc lựa chọn kiểu API và sự lựa chọn của bạn có tác động lớn để khả năng sử dụng và tuổi thọ của API. Khi bạn nhìn vào các API thông thường, bạn sẽ thấy một số mẫu API khác nhau, và dưới đây là hai URL minh họa cho 2 trường phái đối lập nhau về kiểu của API:

https://ebank.com/accounts/a49a9762-3790-4b4f-adbf-4577a35b1df7

https://library.com/shelves/american-literature/books/moby-dick

Kiểu đầu tiên là phiên bản ẩn danh và được tối giản hóa, là một URl thực tế từ một ngân hàng ở Hoa Kỳ. Kiểu thứ hai được điều chỉnh từ một ví dụ lý thuyết trong Hướng dẫn thiết kế API nền tảng đám mây của Google (Google Cloud Platform API Design Guide).

URL đầu tiên khá trừu tượng. Bạn có thể đoán rằng đó là URL của một tài khoản ngân hàng, tất cả chỉ có thế. Trừ khi bạn có khả năng đặc biệt trong việc ghi nhớ các chuỗi thập lục phân, bạn không thể dễ dàng nhập trực tiếp URL này. Hầu hết mọi người sẽ truy cập bằng cách sao chép và dán, hoặc nhấp vào liên kết để sử dụng URL này. Đồng thời, hầu hết mọi người đều không thể biết ngay hai URL dạng này giống nhau hay khác nhau hoặc dễ dàng tìm thấy những lần xuất hiện khác nhau của một URL trong tập tin log.

URL thứ hai minh bạch hơn, dễ dàng được ghi nhớ, nhập và so sánh với các URL khác. Ý nghĩa của nó khá dễ hiểu: có một cuốn sách được đặt tên là gì đó, nằm trên một giá sách có tên nào đó. Dạng URL này có thể dễ dàng được dịch sang một câu trong ngôn ngữ tự nhiên.

Bạn nên sử dụng cái nào? Thoạt nhìn, có vẻ rõ ràng rằng URL 2 sẽ thích hợp hơn, nhưng sự thật thì không đơn giản như vậy.

Mã số nhận dạng

Có một truyền thống lâu đời, xuất hiện trước cả máy tính, chính là cấp phát các định danh số hoặc chữ cho các thực thể. Các ngân hàng và công ty bảo hiểm phân bổ định danh cho các tài khoản và chính sách của họ. Các nhà sản xuất, bán buôn và bán lẻ xác định sản phẩm bằng mã sản phẩm. Các ấn bản sách được xác định bằng mã số ISBN của chúng. Chính phủ cấp cho công dân mã số an sinh xã hội, số hồ sơ hình sự, số thửa đất… và ví dụ đầu tiên chỉ đơn giản là sự thể hiện ý tưởng này trong định dạng URL của ứng dụng web.

Nếu các mã số định danh như thế này có những nhược điểm được mô tả ở trên: khó đọc, khó so sánh, ghi nhớ và nhập, không có thông tin hữu ích về thực thể mà chúng xác định, vậy thì tại sao chúng lại được sử dụng?

Lý do cơ bản nhất là chúng vẫn có giá trị và rõ ràng ngay cả khi mọi thứ thay đổi. Sự ổn định và chắc chắn là một đặc tính cực kỳ quan trọng. Nếu chúng ta không cấp số nhận dạng cho tài khoản ngân hàng, làm thế nào ta có thể tham chiếu nó một cách đáng tin cậy trong tương lai? Việc xác định tài khoản bằng thông tin mà chúng ta biết về nó là không tin cậy và có thể không phải là nhận dạng của duy nhất một tài khoản. Tất cả các thông tin về chủ sở hữu đều có thể thay đổi, hoặc không rõ ràng, hoặc cả hai. Ngay cả khi chúng ta có mã số nhận dạng đáng tin cậy cho chủ sở hữu, quyền sở hữu tài khoản vẫn có thể thay đổi và việc xác định tài khoản theo địa điểm và thời điểm tạo tài khoản không bảo đảm tính duy nhất.

Tên theo thứ bậc (Hierarchical names)

Đặt tên theo thứ bậc là một kỹ thuật rất mạnh mẽ mà con người đã sử dụng trong nhiều thế kỷ để tổ chức thông tin và diễn tả thế giới. Hệ thống phân loại tự nhiên – được xây dựng bởi Carolus Linnaeus vào những năm 1700 là một ví dụ rất nổi tiếng cho vấn đề này.

URL theo dạng thứ hai dựa trên ý tưởng này, được hình thành từ hệ thống phân cấp của những cái tên đơn giản. Các URL này có đặc tính ngược lại với các mã số và ký tự nhận dạng đơn giản: chúng dễ dàng hơn đối với người sử dụng, dễ dàng được xây dựng và lấy thông tin, nhưng chúng không ổn định khi có sự thay đổi.

Nếu bạn biết về phân loại Linnaeus, bạn sẽ hiểu rằng các phần tử của nó đã được đổi tên và cấu trúc phân cấp được tái cấu trúc một cách rộng rãi qua thời gian. Trên thực tế, tốc độ thay đổi đã tăng lên khi áp dụng các công nghệ hiện đại như phân tích DNA. Khả năng thay đổi là rất quan trọng đối với hầu hết các phương án đặt tên và bạn luôn luôn nên đặt câu hỏi về sự thay đổi tên khi thiết kế. Việc đổi tên và tổ chức lại hệ thống phân cấp tên, hóa ra lại quan trọng hoặc được mong chờ trong hầu hết các kịch bản sử dụng, ngay cả khi các nhà thiết kế API không thể lường trước được ngay từ đầu.

Nhược điểm của URL thứ hai là khi thay đổi tên của một cuốn sách hoặc một giá sách, các tham chiếu đến nó dựa trên thứ bậc theo cách này sẽ trở nên vô dụng. Đổi tên có lẽ không hợp lý trong trường hợp một cuốn sách đã được xuất bản hàng loạt, nhưng có thể áp dụng cho các tài liệu khoa học khác mà bạn có thể tìm thấy trong thư viện và việc đổi tên giá sách thì hoàn toàn dễ hiểu. Tương tự, nếu di chuyển một cuốn sách giữa các giá sách, thì các tham chiếu dựa trên URL này cũng sẽ hỏng.

Có một quy tắc chung ở đây. Các URL dựa trên mã số nhận dạng không rõ ràng (đôi khi được gọi là liên kết tĩnh – permalink) vốn dĩ ổn định và đáng tin cậy, nhưng chúng không thân thiện lắm với con người. Cách để làm cho URL thân thiện với con người là xây dựng chúng từ những thông tin có ý nghĩa đối với con người, chẳng hạn như tên và cấp bậc. Trong trường hợp đó, một trong hai điều không may sẽ xảy ra: bạn phải cấm đổi tên các thực thể và tổ chức lại hệ thống phân cấp, hoặc chuẩn bị giải quyết hậu quả khi liên kết dựa trên các URL này bị hỏng.

Trên đây là những ảnh hưởng đối với API sử dụng URL, nhưng vấn đề định dạng URL còn ảnh hưởng tới cả dữ liệu được lưu trong cơ sở dữ liệu và trao đổi giữa các thành phần triển khai. Các URL được API hiển thị thường dựa trên danh tính mà các triển khai API lưu trữ trong cơ sở dữ liệu, vì vậy các quyết định thiết kế ảnh hưởng đển URL thông thường cũng sẽ ảnh hưởng đến cơ sở dữ liệu, thiết kế triển khai API và ngược lại. Nếu bạn sử dụng tên phân cấp để xác định các thực thể trong quá trình triển khai và API, hậu quả khi các tham chiếu bị hỏng sẽ rất phức tạp. Không chỉ riêng API, đây là một vấn đề rất quan trọng đối với thiết kế tổng thể của toàn hệ thống.

Vẹn cả đôi đường

Đối mặt với những cái giá này, bạn nên chọn kiểu URL nào? Lựa chọn tốt nhất là không nên lựa chọn. Bạn cần cả hai để hỗ trợ đầy đủ các chức năng, mang lại cho API của bạn một mã số định danh ổn định, đồng thời dễ sử dụng bằng các tên phân cấp.

Bản thân API nền tảng đáp mây của Google Cloud Platform (GCP) hỗ trợ cả hai loại URL cho các thực thể mà không vô hiệu hóa việc đổi tên hoặc đặt tên lại. Lấy ví dụ, các dự án GCP có cả danh tính bất biến được nhúng trong URL tĩnh, và một cái tên có thể sử dụng trong chức năng tìm kiếm. Chẳng hạn, danh tính của một dự án GCP là bionic-bison-166600 – với định danh không quá khó hiểu như UUID tuân thủ RFC, chúng chỉ cần ổn định và duy nhất – trong khi tên của nó được hiển thị là My First Project Renamed.

Định danh để tra cứu – Tên để tìm kiếm

Từ các nguyên tắc của nền tảng web, chúng ta biết rằng mỗi URL xác định một thực thể cụ thể. Rõ ràng, https://ebank.com/accounts/a49a9762-3790-4b4f-adbf-4577a35b1df7 là URL của một tài khoản ngân hàng.

Bất cứ khi nào sử dụng URL này, bây giờ hoặc trong tương lai, nó sẽ luôn tham chiếu đến cùng một tài khoản ngân hàng. Và đương nhiên, bạn có thể nghĩ rằng https://library.com/shelves/american-literature/books/moby-dick là URL của một cuốn sách cụ thể. Nếu bạn cho rằng việc đổi tên và chuyển vị trí sách không bao giờ có ý nghĩa trong một API của thư viện, thì ngay về mặt giả thuyết, bạn cũng có thể bảo vệ quan điểm đó. Nhưng nếu không, bạn phải nghĩ về URL này theo một cách khác.

Khi sử dụng API này trong hiện tại, nó tham chiếu đến một bản sao cụ thể của Moby Dick hiện đang có trên kệ Văn học Mỹ. Nhưng ngày mai, nếu cuốn sách hoặc giá sách được chuyển đi hoặc đổi tên, nó cũng có thể tham chiếu đến một bản sao mới thay thế bản cũ hoặc không có sách. Điều này cho thấy rằng URL thứ hai không phải URL của một cuốn sách cụ thể, mà là một thứ khác. Bạn nên nghĩ về nó như là URL của một kết quả tìm kiếm, cụ thể là:

Tìm cuốn sách [hiện tại] có tên là "moby-dick" và [hiện] ở trên giá sách [hiện tại] có tên là "văn-học-mỹ"

Đây là một URL khác cho cùng một kết quả tìm kiếm, trong đó có sự khác biệt hoàn toàn ở kiểu URL, không có nghĩa là:

https://library.com/search?kind=book&name=moby-dick&shelf=(name=american-literature)

Ý tưởng rằng các URL dựa trên thứ bậc thực sự là URL của kết quả tìm kiếm chứ không phải là URL của các thực thể trong kết quả tìm kiếm đó chính là ý tưởng cốt lõi giải thích sự khác biệt giữa đặt tên và xác định danh tính.

Kết hợp tên và mã số nhận dạng

Để sử dụng liên kết tĩnh và URL tìm kiếm cùng nhau, bạn có thể bắt đầu bằng cách cấp phát liên kết tĩnh cho mỗi thực thể. Ví dụ: để tạo một tài khoản ngân hàng mới, bạn có thể POST chi tiết tài khoản lên https://ebank.com/accounts. Phản hồi thành công có status 201 cùng với HTTP “Location” header có giá trị là URL của tài khoản mới: https://ebank.com/accounts/a49a9762-3790-4b4f-adbf-4577a35b1df7

Nếu bạn đang thiết kế một API cho thư viện, bạn cũng có thể làm tương tự, bằng cách POST nội dung sau vào body của request https://library.com/locations:

Nó sẽ trả về URL cho giá sách:

https://library.com/shelf/20211fcf-0116-4217-9816-be11a4954344

Sau đó, POST phần body sau lên https://library.com/inventory để tạo một cuốn sách:

{
"kind": "Shelf",
"name": "American-Literature",
}

Kết quả trả về là URL của cuốn sách:

https://library.com/book/745ba01d-51a1-4615-9571-ee14d15bb4af

URL cố định này sẽ luôn tham chiếu đến bản sao cụ thể này của cuốn sách Moby Dick, bất kể nó được gọi như thế nào và nằm ở đâu trong thư viện. Ngay cả khi nó bị mất hoặc bị xóa, URL này vẫn tiếp tục nhận dạng nó.

Dựa trên các thực thể này, các URL tìm kiếm sau cũng được mong chờ là hợp lệ:

https://library.com/shelf/american-literatature/book/moby-dick
https://library.com/search?kind=book&name=moby-dick&shelf=(name=american-literature)

Bạn có thể triển khai cả hai kiểu URL tìm kiếm này trong cùng một API nếu bạn có đủ thời gian, hoặc nếu không, hãy chọn một phong cách bạn thích và gắn bó với nó.

Bất cứ khi nào khách hàng GET một trong các URL tìm kiếm tren, URL nhận dạng (liên kết cố định của nó, trong trường hợp này là https://library.com/book/745ba01d-51a1-4615-9571-ee14d15bb4af)của thực thể được tìm thấy sẽ được gửi cùng phản hồi (response), có thể nằm trong header (HTTP Content-Location header tồn tại nhằm thực hiện mục đích này) hoặc body, hoặc cả hai (lý tưởng nhất là ở cả hai). Điều này cho phép khách hàng giữa tự do chuyển đổi giữa các URL liên kết cố định và URL tìm kiếm cho các thực thể giống nhau.

Nhược điểm của hai kiểu URL

Mọi thiết kế đều có nhược điểm của nó. Rõ ràng, việc triển khai cả URL cố định và URL tìm kiếm trong cùng một API khó khăn và đòi hỏi nhiều thời gian hơn.

Một thách thức nghiêm trọng hơn là bạn phải hướng dẫn người dùng của mình về việc sử dụng URL nào trong những trường hợp nào. Khi họ lưu trữ URL trong cơ sở dữ liệu hoặc để tạo dấu trang (bookmark), có thể họ sẽ muốn sử dụng URL nhận dạng (cố định), hoặc họ cũng có thể sử dụng URL tìm kiếm cho các mục đích khác.

Bạn cũng cần phải cẩn thận về cách lưu trữ các mã số nhận dạng. Các mã số nhận dạng cần được lưu trữ một cách bền vững, vì chúng được sử dụng để tạo liên kết cố định. Sử dụng tên để đại diện cho các tham chiếu hoặc danh tính trong cơ sở dữ liệu thường không phải là một lựa chọn đúng đắn. Nếu bạn thấy tên trong cơ sở dữ liệu được sử dụng theo cách này, bạn nên kiểm tra lại thật kỹ cách sử dụng đó.

Những người viết lệnh để truy cập API có thể chọn giữa URL tìm kiếm và URL liên kết cố định. Viết tập lệnh với URL tìm kiếm thường dễ dàng và nhanh chóng hơn, vì có thể dễ dàng tạo URl tìm kiếm từ tên hoặc số đã biết. Ngược lại, thường mất nhiều công sức hơn để phân tách cú pháp URL cố định ra khỏi tiêu đề và nội dung phải hồn của API.

Nhược điểm của việc sử dụng URL tìm kiếm trong các tập lệnh là chúng sẽ bị hỏng nếu một thực thể API được đổi tên hoặc được di chuyển trong hệ thống phân cấp, giống như cách mà các tập lệnh có xu hướng bị hỏng khi các tập tin được đổi tên hoặc di chuyển. Vì bạn đã quen với việc sửa các lệnh khi tên tập tin thay đổi, bạn có thể quyết định tiếp tục sử dụng các URL tìm kiếm và chỉ cần sửa mã nguồn khi chúng bị hỏng. Tuy nhiên, nếu bạn coi trọng độ tin cậy và tính ổn định hơn, hãy lựa chọn liên kết cố định cho mã nguồn của bạn.

Nguồn: Google Cloud Blog