REST & Richardson Maturity Model

Apr 27, 2021

Mở đầu

Chắc các bạn đều không còn lạ lẫm gì với API và Web API vì chúng được các bạn phát triển/sử dụng trong công việc hàng ngày của một lập trình viên. Tôi cho là trong phần lớn trường hợp các bạn đều sử dụng từ API nói chung để ám chỉ cái gọi là RESTful API.

Về khái niệm API chúng ta sẽ bàn trong một post khác rộng hơn nhưng trước hết cần hiểu REST là gì?

REST là viết tắt của cụm từ tiếng Anh:  Representational State Transfer. Đây là một phong cách kiến trúc phần mềm ứng dụng cho các distributed hypermedia systems, sử dụng một tập con của HTTP để client và server thực hiện việc trao đổi (exchange ) application data.

REST được trình bày đầu tiên bởi  Roy Fielding vào năm 2000 trong bài luận văn nổi tiếng của ông (Ref : REST). Để nắm bắt thì ta cần biết khái niệm chủ chốt trong REST là resource. Theo đó bất cứ thông tin nào có thể được gọi tên đều có thể là một resource : một tài liệu, một bức ảnh, một dịch vụ tạm hay một tập hợp của các resource khác... REST sử dụng resource identifier để định danh một resource cụ thể liên quan đến sự tương tác giữa các component. Trạng thái của resource (State) tại một thời điểm cụ thể được biết đến là resource representation. Và như vậy REST quy định việc trao đổi thông tin trạng thái  diện của resource.

Cụ thể hơn chúng ta đi vào các nguyên tắc hướng dẫn về REST được đề ra bởi Fielding (REST guiding principles)

  1. Client–server : Bằng cách tách các vấn đề của user interface khỏi các vấn đề của việc lưu trữ giữ liệu, chúng ta cải thiện tính linh động của giao diện người dùng  qua trên nhiều nền tảng và cải thiện khả năng mở rộng bằng cách đơn giản hóa các thành phần máy chủ.
  2. Stateless – Mỗi yêu cầu từ client đến server phải chứa toàn bộ các thông tin cần thiết để server có thể hiểu yêu cầu và không thể tận dụng bất cứ ngữ cảnh được lưu nào trên server. Nghĩa là server không hỗ trợ lưu trữ ngữ cảnh thực hiện giữa 2 request chính vì vậy trạng thái phiên làm việc ( session ) sẽ được lưu hoàn toàn ở local. Ràng buộc này tạo ra thuộc tính visibility, reliability, and scalability. Visibility được cải thiện vì các hệ thống giám sát chỉ cần nhìn vào 1 request để hiểu được bản chất đầy đủ của nó. Độ tin cậy được cải thiện vì là stateless nên nhiệm vụ khôi phục dữ liệu sau các lỗi trở nên đơn giản hơn rất nhiều. Cuối cùng khả năng mở rộng được cải thiện vì không phải lưu trữ trạng thái giữa các yêu cầu cho phép thành phần máy chủ nhanh chóng giải phóng tài nguyên và đơn giản hóa hơn nữa việc triển khai vì máy chủ không phải quản lý việc sử dụng tài nguyên giữa các yêu cầu. Tất nhiên việc sử dụng stateless sẽ dẫn tới giảm hiệu năng mạng do việc tăng truyền tải các dữ liệu trùng lặp theo một chuỗi yêu cầu thay vì chỉ một yêu cầu như trước kia. Thêm vào đó việc lưu trạng thái ứng dụng ở client-side làm giảm sự quản lý của server với tính nhất quán trong hoạt động của ứng dụng.
  3. Cacheable – Thông tin trả về trong response sẽ được đanh dấu là có thể được cache hay không tại client một cách minh hoặc không. Nếu response là có thể được cache thì client sẽ được quyền tái sử dụng các dữ liệu response đó cho các lần sử dụng tiếp theo.
  4. Uniform interface –  REST được định nghĩa bởi 4 ràng buộc về giao diện :
  • Định danh resource
  • Các thao tác trên resource (Resource method) được tiến hành thông qua các đại diện (representations)
  • Hệ thống message có khả năng tự mô tả
  • Hypermedia sẽ là engine của application state

※Chú ý ở đây chúng ta đề cập tới Resource methods và uniform interface một cách nói chung chứ không gắn nó với HTTP methods(GET/POST/PUT/DELETE).

5.  Layered system – Cho phép kiến trúc được cấu thành bởi các lớp phân cấp trong đó mỗi thành phần sẽ không thể tương tác với các lớp khác ngoài lớp mà chúng đang tương tác trực tiếp

6.  Code on demand (optional) – REST cho phép chức năng của client được mở rộng thông qua việc download và thực hiện code dạng applet hoặc scripts khiến client trở nên đơn giản hơn thông qua việc giảm số lượng các tính năng cần được phát triển trước.

OK! Như vậy ta thấy REST bản thân nó là 1 architectural style và nêu thiết kế của bạn không tuân thủ những guiding principles như trên thì nó không phải là REST. Ngược lại nếu nó tuân thủ đủ 6 guiding principles như trên thì nó được gọi là RESTful.

Hãy xem xét một ví dụ mà các bạn làm API hay thực hiện để tạo mới 1 User

POST /users HTTP/1.1

{
    "user": {
          "first_name": "User",
          "last_name": "Admin",
          "email": "admin@example.com"
      }
}  

Vậy làm thế nào để lấy về thông tin user vừa tạo? Ta có thể vẫn dùng POST và cho thông tin điều kiện vào body param của POST như dưới đây

POST /users HTTP/1.1

{
    "inquiry_user": {
          "id": "1"
      }
}

Hay có thể để dạng query string như sau:

POST /users?id=1001 HTTP/1.1

Theo bạn chúng có tuân thủ đúng các guiding principles của Fielding đề ra cho REST không? Chúng có đúng là RESTful như chúng ta tưởng hay không?

Richardson Maturity Model


Chung một câu hỏi về mức độ tuân thủ của thiết kế cho các tiêu chuẩn của REST, Leonard Richardson đã tiến hành phân tích hàng trăm mẫu thiết kế web service và chia chúng thành 4 categories dựa trên mức độ tuân tuân thủ REST và mô hình phân chia này được đặt theo tên ông : Richardson Maturity Model

Richardson sử dụng 3 thành tố (factors) để quyết định mức độ trưởng thành của một service trong việc tuân thủ REST :

  • URI
  • HTTP
  • HATEOAS (Hypertext As The Engine Of Application State)

Các service càng sử dụng nhiều các thành tố này thì chúng càng được coi là trưởng thành.

Level 0

Ở level đầu tiên này của mô hình thì mô tả category của các dịch vụ sử dụng HTTP như là một transport system cho tương tác từ xa nhưng không sử dụng bất cứ cơ chế nào của web. Những service này có một URI duy nhất và sử dụng một HTTP method duy nhất (thường là POST) trong đó HTTP sử dụng như một cớ chế tunneling cho phép thực hiện việc tương tác từ xa. Ví dụ như các dịch vụ sử dụng XML-RPC sẽ gửi data như một POX (Plain Old XML ) để thực hiện tương tác. Đây là cách cơ bản nhất để xây dựng một SOA application với giao thức POST duy nhất và sử dụng XML để liên lạc giữa các services.

Level 0

Lấy ví dụ bạn muốn đặt một lịch hẹn với bác sỹ. Phần mềm đặt lịch của bạn cần biết bác sỹ có rảnh vào một thời gian nào đó trong ngày chỉ định không nên nó sẽ gửi request đến dịch vụ đặt chỗ bệnh viện để lấy thông tin đó. Ở level 0, hệ thống của bệnh viện sẽ mở một endpoint duy nhất và ứng dụng của chúng ta sẽ phải thực hiện POST vào endpoint này để yêu cầu thông tin :

POST /appointmentService HTTP/1.1
[various other headers]

<openSlotRequest date = "2021-05-01" doctor = "Thảo"/>

Server sẽ trả về các slot thời gian rảnh của bác sỹ Thảo :

HTTP/1.1 200 OK
[various headers]

<openSlotList>
  <slot start = "14:00" end = "14:50">
    <doctor id = "Thảo"/>
  </slot>
  <slot start = "16:00" end = "16:50">
    <doctor id = "Thảo"/>
  </slot>
</openSlotList>

Tiếp theo khi đã biết bác sỹ rảnh giờ nào ta sẽ tiến hành đặt lịch vào slot thời gian rảnh của bác sỹ.  Chú ý rằng do đang ở level 0 nên endpoint không hề thay đổi và ta vẫn dùng POST :

POST /appointmentService HTTP/1.1
[various other headers]

<appointmentRequest>
  <slot doctor = "Thảo" start = "14:00" end = "14:50"/>
  <patient id = "Kiên"/>
</appointmentRequest>

Nếu mọi chuyện thuận lợi ( không bị ai đó book trước trong khi đang chuẩn bị) thì hệ thống bệnh viện sẽ trả về cho ta :

HTTP/1.1 200 OK
[various headers]

<appointment>
  <slot doctor = "Thảo" start = "14:00" end = "14:50"/>
  <patient id = "Kiên"/>
</appointment>

Còn nếu thất bại rất có thể kết quả trả về sẽ như sau :

HTTP/1.1 200 OK
[various headers]

<appointmentRequestFailure>
  <slot doctor = "mjones" start = "1400" end = "1450"/>
  <patient id = "jsmith"/>
  <reason>Slot not available</reason>
</appointmentRequestFailure>

Như đã thấy kiểu xây dựng như trên thì hệ thống sẽ chỉ gửi nhận các POX thông qua phương thức HTTP duy nhất với một endpoint duy nhất để triệu gọi phương thức và truyền tham số.

Không sử dụng : URI, HTTP methods, HATEOAS

Level 1

Ở level 1, thay vì dùng chung duy nhất một service endpoint, chúng ta bắt đầu nói chuyện với từng resource.

Level 1

Ở level này chúng ta sử dụng nhiều URIs trong đó mỗi resource có 1 URI riêng tuy nhiên vẫn sử dụng một HTTP verb - thông thường là POST. Và như vậy việc xác nhận thời gian rảnh của bác sỹ sẽ được biến đổi như sau :

POST /doctors/thảo HTTP/1.1
[various other headers]

<openSlotRequest date = "2021-05-01"/>

Kết quả trả về cơ bản là giống tuy nhiên chú ý rằng mỗi slot bây giờ là một resource độc lập:

HTTP/1.1 200 OK
[various headers]


<openSlotList>
  <slot id = "1234" doctor = "Thảo" start = "14:00" end = "14:50"/>
  <slot id = "5678" doctor = "Thảo" start = "16:00" end = "16:50"/>
</openSlotList>

và lúc này ta có thể tiến hành book vào slot mong muốn sử dụng URI của nó

POST /slots/1234 HTTP/1.1
[various other headers]

<appointmentRequest>
  <patient id = "Kiên"/>
</appointmentRequest>

Tương tự như vậy kết quả của việc book thành công sẽ là 1 resource appointment

HTTP/1.1 200 OK
[various headers]

<appointment>
  <slot id = "1234" doctor = "Thảo" start = "14:00" end = "14:50"/>
  <patient id = "Kiên"/>
</appointment>

Và để thao tác với resource appointment này ta sẽ cần sử dụng URI của nó /appointments/1234

Sử dụng : URI nhưng chưa sử dụng HTTP methods, HATEOAS

Level 2:

Ở level 0 và 1 chúng ta chỉ dùng POST verb của HTTP, tuy nhiên ở level 2, interface sẽ được chuẩn hoá hơn nhờ sử dụng hợp lý các HTTP verbs. Ở level 2 thì các services sẽ gồm rất đa dạng các resource được đánh địa chỉ bởi URI, trong đó các service sẽ support một vài HTTP verbs cho phép trên mỗi resource : Create, Read, Update và Delete (CRUD) . Mỗi trạng thái thái của resource được dại diện bởi một thực thể business ( business entity) và có thể được thao tác qua network. Ở mức này thì người sử dụng dịch vụ cần dành thời gian để hiểu rõ các APIs được cung cấp thông qua việc đọc các tài liệu thiết kế API liên quan.

Level 2

Để list các slot trống của bác sỹ ta có thể :

GET /doctors/thảo/slots?date=20210501&status=open HTTP/1.1
Host: example.com

Như thấy ở trên chúng ta đã có sự thay đổi khi chuyển qua dùng GET verb cho mục đích inquiry dữ liệu. HTTP định nghĩa GET là một thao tác an toàn vì nó không thực hiện bất cứ thay đổi nào với trạng thái của resource. Điều này dẫn tới việc ta có thể thực hiện gọi GETs một cách an toàn bao nhiêu lần cũng được với bất cứ thứ tự nào thì đều có cùng kết quả. HTTP hỗ trợ nhiều biện pháp thực hiện caching vì vậy tuân thủ theo rule của HTTP cho phép chúng ta thực hiện được hướng dẫn số 3 một cách dễ dàng. Ngoài ra còn 1 điểm là thông qua việc sử dụng HTTP verbs chúng ta có thể tận dụng việc sử dụng HTTP response code cho các mục đích của mình. Ví dụ thay vì trả về 200 và với content là nội dung lỗi, chúng ta hoàn toàn có thể trả về 409 (Conflict) trong trường hợp đã có người book vào khung thời gian mà ta vừa chọn.

HTTP/1.1 409 Conflict
[various headers]

<openSlotList>
  <slot id = "5678" doctor = "Thảo" start = "16:00" end = "16:50"/>
</openSlotList>

Level 2  giới thiệu về việc sử dụng HTTP verbs và HTTP response codes bên cạnh URIs và vẫn chưa sử dụng HATEOAS. Level 2 là một use-case tốt của việc tuân thủ các nguyên lý của REST trong đó ủng hộ việc sử dụng các động từ khác nhau dựa trên các phương thức yêu cầu HTTP và hệ thống có thể có nhiều tài nguyên được định danh.

Level 3

Level 3 sử dụng cả 3 thành tố: URIs, HTTP và HATEOAS. Đây là level trưởng thành nhất trong mô hình của Richardson vì nó khuyên khích khả năng dễ dàng khám phá thông qua thực hiện nguyên lý self-descriptive messages nhờ sử dụng HATEOAS. Các service thuộc level này sẽ dẫn dắt người dùng qua một chuỗi các resources, làm cho trạng thái của ứng dụng thay đổi như là một kết qủa.

Level 3

Trong ví dụ trên, client biết trước cần/có thể  làm gì sau khi có danh sách các slot rảnh của bác sỹ. Ở level này chúng ta cần service có khả năng tự trả lời câu hỏi làm thế nào có thể book một appointment từ danh sách các open slot

Chúng ta bắt đầu từ việc lấy về các slot rảnh rỗi của bác sỹ :

GET /doctors/thảo/slots?date=20210501&status=open HTTP/1.1
Host: example.com

Lúc này hệ thống sẽ trả về có chút khác biệt :

HTTP/1.1 200 OK
[various headers]

<openSlotList>
  <slot id = "1234" doctor = "Thảo" start = "14:00" end = "14:50">
     <link rel = "/linkrels/slot/book" 
           uri = "/slots/1234"/>
  </slot>
  <slot id = "5678" doctor = "thảo" start = "16:00" end = "16:50">
     <link rel = "/linkrels/slot/book" 
           uri = "/slots/5678"/>
  </slot>
</openSlotList>

Như dễ dàng quan sát thấy  chúng ta có thêm element <link> để chỉ cho ta biết việc gì cần làm tiếp để tiến hành book appointment. Giả sử chúng ta tiến hành book theo chỉ dẫn :

POST /slots/1234 HTTP/1.1
[various other headers]

<appointmentRequest>
  <patient id = "Kiên"/>
</appointmentRequest>

Lúc này trong response bên cạnh thông tin appointment còn có các hypermedia control hướng dẫn tao các thao tác với resource appointment như  : Cancel, Change Time, Update contact Info, Help ...

HTTP/1.1 201 Created
Location: http://example.com/slots/1234/appointment
[various headers]

<appointment>
  <slot id = "1234" doctor = "Thảo" start = "14:00" end = "14:50"/>
  <patient id = "jsmith"/>
  <link rel = "/linkrels/appointment/cancel"
        uri = "/slots/1234/appointment"/>
  <link rel = "/linkrels/appointment/addTest"
        uri = "/slots/1234/appointment/tests"/>
  <link rel = "self"
        uri = "/slots/1234/appointment"/>
  <link rel = "/linkrels/appointment/changeTime"
        uri = "/doctors/mjones/slots?date=20100104&status=open"/>
  <link rel = "/linkrels/appointment/updateContactInfo"
        uri = "/patients/jsmith/contactInfo"/>
  <link rel = "/linkrels/help"
        uri = "/help/appointment"/>
</appointment>

Như vậy có thể thấy điểm lợi đầu tiên của level 3 là tăng cường sự độc lập giữa client-server. Ở đây server hoàn toàn có thể thay đổi schema của resource mà không hề phá hỏng client.  Đồi thời vì message là self-descriptive nên tạo điều kiện cho người phát triển client dễ dàng hơn trong việc khám phá các resource cung cấp bởi server.

Kết luận

Thông qua bài viết này giới thiệu với mọi người :

  • REST là gì
  • RESTful cần tuân thủ gì
  • Mô hình trưởng thành trong tuân thủ REST được đề án bởi Richardson :
Level 0: Cho phép method được gọi thông qua giao thức HTTP
Level 1 : giải quyết câu hỏi về việc xử lý sự phức tạp bằng cách sử dụng  chia để trị, chia nhỏ một dịch vụ thành nhiều resource với URI riêng.
Level 2 : giới thiệu một bộ resource methods sử dụng HTTP verbstiêu chuẩn để xử lý các tình huống tương tự theo cùng một cách, loại bỏ các biến thể không cần thiết
Level 3 : giới thiệu khả năng khám phá, cung cấp cách làm cho một giao thức mô tả

Tài liệu tham khảo

https://martinfowler.com/articles/richardsonMaturityModel.html#level0

https://blog.ndepend.com/rest-vs-restful/#:~:text=What's the difference between a REST API and a RESTful one%3F&text=The short answer is that,one that implements that pattern.

https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm



Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.