Sử dụng Spectral để tạo nên các bộ quy tắc cho OpenAPI specification

Trong bài viết này, chúng ta sẽ khám phá về Spectral và kiểm chứng liệu công cụ linter này có thật sự là lựa chọn đúng đắn để giúp quá trình thiết kế API trở nên hiệu quả và nhanh chóng hơn?

Upload image

API-first approach đang trở thành xu hướng thu hút nhiều các doanh nghiệp lớn nhỏ khác nhau bởi những lợi ích mà nó mang lại là vô cùng lớn như thời gian phát triển nhanh, chi phí phát triển thấp, dễ dàng tái sử dụng,… Đối với hướng tiếp cận này, các API sẽ được thiết kế cụ thể và rõ ràng trước rồi mới đến giai đoạn viết code.

Trong công đoạn đầu của quá trình thiết kế, một trong những khó khăn đầu tiên mà ta phải đối mặt là sự bất đồng bộ giữa những lập trình viên do mỗi người sẽ có phong cách và suy nghĩ riêng. Nếu cứ để mạnh ai nấy thiết kế một cách tự do thì chắc chắn sẽ dẫn đến các cuộc tranh cãi, đổ lỗi cho nhau khi có vấn đề xảy ra như thiếu thuộc tính, cách đặt tên khác nhau,... Từ đó sẽ dễ gây mất đoàn kết giữa các thành viên với nhau. Chính vì vậy, để các API được xây dựng theo đúng yêu cầu, chúng ta cần có Linting tool (hay còn được gọi là linter). Linter sẽ giúp chúng ta đảm bảo các API được thiết kế ra phải đúng về mặt kỹ thuật và đáp ứng với bộ tiêu chuẩn đã được đề ra.

Vậy làm sao để xác định được đâu là một Linter tốt để sử dụng?

Một Linter tốt sẽ có tính linh hoạt cao, cụ thể là:

  • Cung cấp một loạt các bộ luật sẵn có để người dùng có thể sử dụng ngay mà không cần khởi tạo, đồng thời cho phép họ chỉnh sửa thay đổi những chi tiết nhỏ mà không cần phải viết lại toàn bộ. Đối với những dự án nhỏ thì lựa chọn thiết kế các bộ luật từ con số 0 là vô cùng lãng phí tài nguyên, thế nên việc hỗ trợ sẵn các bộ luật để dùng ngay sẽ giúp tiết kiệm được một lượng lớn thời gian, chi phí cho dự án.
  • Cho phép người dùng tự do viết các bộ luật theo ý muốn. Điều này vô cùng phù hợp với các dự án cỡ lớn bởi tính phức tạp của chúng. Thông thường, các bộ luật được viết sẵn sẽ không thể đáp ứng được hết hoặc không thích hợp với các yêu cầu của những dự án quy mô lớn. Vì thế, việc có khả năng dễ dàng tùy ý thiết kế, chỉnh sửa các bộ luật là một tính năng vô cùng quan trọng về lâu dài.
  • Hỗ trợ cả cho các phiên bản cũ hơn của OpenAPI như OpenAPI v2.
  • Có các tài liệu hướng dẫn rõ ràng cách thức sử dụng, có các ví dụ cụ thể, dễ dàng nâng cấp lên phiên bản mới,...

Trong bài viết này, mình sẽ chọn sử dụng Spectral, một linter hội tụ tất cả các tinh hoa mà mình đã nêu trên, để phân tích và phát hiện các lỗi, cấu trúc đáng ngờ (hành vi này được gọi ngắn gọn là lint) có trong OpenAPI specification nhé.

Spectral là gì?

Upload image

Spectral được phát triển bởi Spotlight là công cụ mã nguồn mở trên Github được dùng để lint OpenAPI specification. Tương tự với eslint, Spectral không có UI của chính nó, mọi sự tương tác đều được thực hiện thông qua các câu lệnh trên terminal.

Spectral đã cho ra đời ba khái niệm cốt lõi để ràng buộc các APIs được thiết kế phải đạt chất lượng cao, bền vững và hợp tiêu chuẩn:

  • Rules: là các điều luật quy định các giá trị mà một object được phép gán và chỉ định các hàm được sử dụng để kiểm tra giá trị được gán đó.
  • Functions: là các hàm nhận giá trị đầu vào và sẽ trả về lỗi nếu giá trị đó không đúng.
  • Rulesets: là một bộ tập hợp các rules và functions.

Bằng cách tạo nên một bộ ruleset phù hợp với dự án, chúng ta có thể giúp cho quá trình thiết kế API trở nên hiệu quả, ổn định và nhanh chóng hơn. Đồng thời, Spectral cũng đã cung cấp sẵn hai bộ rulesets: OpenAPIAsyncAPI. Đối với những người mới làm quen với Spectral thì việc sử dụng bộ ruleset có sẵn là một lựa chọn vô cùng hoàn hảo để bắt đầu.

Giới thiệu khái quát về Spectral như vậy cũng đủ rồi. Phần tiếp theo chúng ta sẽ cùng nhau trải nghiệm công cụ linter này bằng một demo nho nhỏ nhé.

Điều kiện tiên quyết

Cài đặt Spectral

Quá trình cài đặt Spectral rất đơn giản, ta sẽ tạo một thư mục rỗng và đặt tên theo ý muốn, tiếp đó ta sẽ sử dụng npm để cài đặt bằng câu lệnh:

npm install @stoplight/spectral-cli

Đối với những người dùng Yarn thì hãy sử dụng câu lệnh này:

yarn add @stoplight/spectral-cli

Thực hành với Spectral

1. Viết OpenAPI Specification

Tiếp theo, chúng ta sẽ tạo một OpenAPI YAML specification demo.yaml để cho spectral có thể kiểm tra.

openapi: 3.0.1

info:
  version: 0.0.1
  title: LGV Versatile Supermarket 
  description: demo Spectral
servers:
  - url: http://localhost:8080
tags:
  - name: Product
  - name: Order
    description: Access to user's orders
  - name: User
    description: Include all user features

paths:
  /product/getAll:
    get:
      summary:  Get all product
      tags:
        - "Product"
      description: Get all product 
      operationId: getAllProduct
      responses:
        "200":
          description: Get all product success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/product'
        "500":
          description: Internal Error
components:
  securitySchemes:
    basicAuth:     
      type: http
      scheme: basic 

2. Tạo Rule set cho OpenAPI Specification

Sau khi có được một OpenAPI YAML specification demo.yaml, chúng ta sẽ bắt tay vào thiết kế file rule-demo.yaml nhé. Như mình đã đề cập ở phần trên, Spectral đã cung cấp hai bộ rulesets có sẵn là OpenAPIAsyncAPI. Chúng ta có thể sử dụng chúng bằng cách dùng từ khóa “extends”.

Với dòng lệnh này, spectral sẽ sử dụng bộ ruleset OpenAPI. Trong trường hợp mọi người muốn dùng bộ ruleset AsyncAPI thì có thể thay đổi chuỗi spectral:oas thành spectral:asyncapi.

# or: "spectral:asyncapi"
extends: "spectral:oas"

Chỉ với 1 dòng lệnh đơn giản như thế ta đã thành công tạo ra được một file rule-demo.yaml có thể áp dụng được rồi đấy!

3. Lint file OpenAPI Specification

Để lint demo.yaml bằng rule-demo.yaml, hãy nhập dòng lệnh này vào terminal:

spectral lint demo.yaml --ruleset rule-demo.yaml

Dưới đây là kết quả:

Upload image

Với những người dùng Visual Studio Code, Spectral có hỗ trợ plugin để giúp lint một cách tự động thay vì phải gõ lệnh vào terminal.

Upload image

Cách để cho plugin nhận diện được file rule-demo.yamlcũng rất đơn giản. Mọi người chỉ cần thực hiện các bước như hình là được.

Upload image

Upload image

Upload image

Kết quả giống hệt như khi lint bằng dòng lệnh:

Upload image

Kết quả trả về 2 vấn đề (1 error, 1 warning):

  • Lỗi warning là do chúng ta thiếu mất “contact” object ở phần đầu “info” object.
  • Lỗi error là do chúng ta trỏ tới một schema không tồn tại.

4. Tùy chỉnh, tự tạo các rules

Làm tới bước này có lẽ mọi người nghĩ rằng công việc còn lại chỉ là sửa các vấn đề trên thôi nhỉ. Tuy nhiên, mình vẫn chưa thật sự hài lòng với bộ ruleset được viết sẵn này và muốn tùy chỉnh lại các rules theo ý mình mong muốn:

  • Thay đổi cấp độ nghiêm trọng (severity) từ cấp warning thành error của rule info-contact
  • Viết một rule mới ràng buộc tags phải có field description
  • Viết một rule mới nghiêm cấm sử dụng xác thực cơ bản (basic authentication)

Đây là file rule-demo.yaml mới sau khi chỉnh sửa theo các yêu cầu trên.

formats: ["oas3"]
extends: "spectral:oas"

rules:
  # rule 1
  info-contact: error
  # rule 2
  no-http-basic:
    description: "Please choose a more secure alternative to HTTP Basic."
    severity: error
    given: $.components.securitySchemes[*]
    then:
      field: scheme
      function: pattern
      functionOptions:
        notMatch: basic
  # rule 3
  tag-description:
    description: Please provide a description for each tag.
    severity: error
    given: $.tags[*]
    then:
      field: description
      function: truthy

Cấu trúc của một rule gồm có các thuộc tính:

  • given (bắt buộc): xác định những phần trong file specification mà rule sẽ được áp dụng vào. Các vị trí được xác định bằng cách sử dụng cú pháp của JSONPath. Ví dụ ở rule 2, vị trí áp dụng rule sẽ được định vị từ root -> components -> securitySchemes -> tất cả các con của securitySchemes.
  • then (bắt buộc): chọn hàm sẽ áp dụng vào những phần được xác định trong thuộc tính given. Thuộc tính then có ba thuộc tính bên trong: field (không bắt buộc), function (bắt buộc), functionOptions (không bắt buộc). Ví dụ ở rule 2, hàm được dùng trong thuộc tính function là hàm pattern. Đây là hàm dùng regrex mà chúng ta cung cấp để kiểm tra chuỗi có khớp hay không bằng thuộc tính notMatch hoặc match. Để biết thêm vể các hàm được viết bởi Spectral, mọi người có thể truy cập vào link này.
  • severity (không bắt buộc): biểu thị cấp độ nghiêm trọng của rule. Spectral chia thành 5 mức với các từ khóa error, warn, info, hint và off.
  • description (không bắt buộc): miêu tả ngắn về rule.

Cấu trúc rule 1 khác với hai rule còn lại bởi vì chúng ta chỉ muốn thay đổi cấp độ nghiêm trọng (severity) từ warning thành error.

Các thuộc tính được nêu trên đều là những thuộc tính cơ bản để giúp chúng ta thiết kế rule. Ngoài ra, Spectral còn tồn tại các thuộc tính như aliases, overrides, recommended,... hay các chức năng như tùy chỉnh hàm, chia sẻ các ruleset. Tất cả các tài liệu liên quan đến những thứ mà mình đề cập ở những phần trước đó đều nằm ở đường link này.

Dưới đây là kết quả sau khi lint demo.yaml bằng rule-demo.yaml mới:

Upload image

5. Sửa các lỗi tìm được

Phần cuối cùng cần làm chính là sửa những lỗi mà Spectral tìm thấy:

openapi: 3.0.1

info:
  version: 0.0.1
  title: LGV Versatile Supermarket 
  # fix info-contact error
  contact:
    email: example123@gmail.com
  description: demo Spectral
servers:
  - url: http://localhost:8080
tags:
  - name: Product
    # fix tag-description error
    description: All products in the store
  - name: Order
    description: Access to user's orders
  - name: User
    description: Include all user features

paths:
  /product/getAll:
    get:
      summary:  Get all product
      tags:
        - "Product"
      description: Get all product 
      operationId: getAllProduct
      responses:
        "200":
          description: Get all product success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/product'
        "500":
          description: Internal Error
components:
  # fix no-http-basic
  securitySchemes:
    ApiKeyAuth:    
      type: apiKey
      in: header     
      name: X-API-KEY  
  # fix invalid-ref error
  schemas:
    product:
      description: This is the product model
      type: object
      properties:
        id:
          type: string

Dưới đây là kết quả sau khi lint demo.yaml đã được sửa:

Upload image

Spectral không còn tìm thấy lỗi nào nữa, như vậy là chúng ta đã thành công sửa toàn bộ hết lỗi rồi đấy.

Lời kết

Với công cụ như Spectral, quá trình thiết kế API sẽ trở nên hiệu quả và nhanh chóng hơn nhờ vào các rule được định nghĩa ngay từ ban đầu, các APIs sẽ đồng nhất với nhau về mặt cấu trúc và đạt được tiêu chuẩn đã đề ra.

Atekco - Home for Authentic Technical Consultants