Tản mạn
Đối với 1 dự án lớn, mà viết API, thì việc tạo Document cho dự án không phải là một chuyện đơn giản. Đối với trường hợp update requirement liên tục từ khách hàng, thì nó càng trở nên mất thời gian.
Nhưng nếu không update liên tục thì sẽ gây rất nhiều khó khăn cho các lập trình viên khác khi sử dụng API của chúng ta, cũng như khó khắn trong việc test API.
Hiện nay có rất nhiều công cụ mã nguồn mở để giúp cho lập trình viên có thể viết document rõ ràng và có thể test endpoint trực tiếp mà k cần sử dụng đến các công cụ như postman để có thể test API.Hai mã nguồn phổ biến hiện nay là Swagger và API Blue Print.
Bài này, mình xin gới thiệu 1 công cụ đó là SWAGGER, nó giúp chúng ta viết document chuẩn restfull 1 cách dễ dàng cho dự án của mình. Và lưu ý xin xò là nó hỗ trợ cho nhiều loại ngôn ngữ chứ ko chỉ Ruby.
Triển khai
Mình sẽ viết API với ngôn ngữ Ruby on Rails
1. Xây dụng API
Các bạn có thể tham khảo tài liệu: http://apionrails.icalialabs.com/book/ để tạo ra Project cho riêng mình. Hoặc có thể tạo nhanh một rails project để test như sau:
1 2 3 4 5 6 7 8 |
rails new swagger-rails cd swagger-rails rails g model User name:string email:string age:integer rails db:migrate |
Sau đó add 2 gem sau vào Gemfile:
1 2 3 |
gem "swagger-block" gem "swagger-ui-engine" |
rồi chạy bundle install.
2. Document cho API
2.1 Swagger là gì?
- Là một bộ công cụ mã nguồn mở để xây dựng OpenAPI specifications giúp chúng ta có thể thiết kế, xây dựng tài liệu và sử dụng REST APIs.
- Swagger cung cấp 3 tools chính cho các developers :
Swagger-Editor dùng để design lên các APIs hoàn toàn mới hoặc edit lại các APIs có sẵn thông qua 1 file config.
Swagger-Codegen dùng để generate ra code từ các file config có sẵn.
Swagger-UI dùng để generate ra file html,css,… từ 1 file config.
- Việc viết document cho Swagger có hai cách tiếp cận chính như sau:
Top-down approach: thiết kế các API trước khi code.
Bottom-up approach: từ các API có sẵ thiết kế file config để mô tả chúng
- Để dễ hiểu, các bạn có thể truy cập đường link demo với Swagger UI http://petstore.swagger.io/ .
- Với mỗi API chúng ta có thể biết được chi tiết input và output cũng như trường nào bắt buộc gửi lên, kết quả trả về có thể nhận những status nào. Đặc biệt, ta có thể input data để thử kiểm tra kết quả.
2.2 Cài đặt Swagger UI
- Config wagger block:
Mỗi doc sẽ đi liền với nhau theo dạng 1 action – 1 controller
Swagger Block cung cấp 1 method là: Swagger::Blocks.build_root_json có nhiệm vụ nhận vào các class chưa thông tin về APINếu như thực hiện theo step hướng dẫn của gem swagger-block,
chúng ta có thể config ở controller, nhưng vấn đề là controller sẽ phình rất to nếu như đặt doc ở đó.Ngoài ra, nếu các bạn đã biết về mô hình MVC, hay nguyên lý SOLID thì đặt config ở Controller là không nên.
Vì thế, mình sẽ tạo 1 folder swagger để lưu trữ các file liên quan tới request của swagger123456789101112131415161718192021222324252627282930313233343536373839404142<span class="token comment"># app/controllers/concerns/swagger/users_api.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">UsersApi</span>extend <span class="token constant">ActiveSupport</span> <span class="token punctuation">:</span><span class="token punctuation">:</span> <span class="token constant">Concern</span>include <span class="token constant">Swagger</span> <span class="token punctuation">:</span><span class="token punctuation">:</span> <span class="token constant">Blocks</span>included <span class="token keyword">do</span>swagger_path <span class="token string">'/users/{id}'</span> <span class="token keyword">do</span> <span class="token comment">#link path request to server</span>operation <span class="token symbol">:get</span> <span class="token keyword">do</span> <span class="token comment"># method get</span>key <span class="token symbol">:summary</span><span class="token punctuation">,</span> <span class="token string">'Find User by ID'</span> <span class="token comment"># summary</span>key <span class="token symbol">:description</span><span class="token punctuation">,</span> <span class="token string">'Returns a single user if the user has access'</span> <span class="token comment"># description</span><span class="token comment"># định nghĩa params</span>parameter <span class="token keyword">do</span>key <span class="token symbol">:name</span><span class="token punctuation">,</span> <span class="token symbol">:id</span> <span class="token comment"># name hiển thị là id</span>key <span class="token symbol">:in</span><span class="token punctuation">,</span> <span class="token symbol">:path</span> <span class="token comment"># phương thức add params vào path. VD: lúc request lên sẽ là users/1</span>key <span class="token symbol">:description</span><span class="token punctuation">,</span> <span class="token string">'ID of user to fetch'</span>key <span class="token symbol">:required</span><span class="token punctuation">,</span> <span class="token keyword">true</span> <span class="token comment"># validate</span>key <span class="token symbol">:type</span><span class="token punctuation">,</span> <span class="token symbol">:integer</span> <span class="token comment"># type params</span>key <span class="token symbol">:format</span><span class="token punctuation">,</span> <span class="token symbol">:int64</span> <span class="token comment"># format params</span><span class="token keyword">end</span>response <span class="token number">200</span> <span class="token keyword">do</span><span class="token comment"># when get user succes</span>key <span class="token symbol">:description</span><span class="token punctuation">,</span> <span class="token string">'user response'</span>schema <span class="token keyword">do</span>key <span class="token punctuation">:</span><span class="token string">'$ref'</span><span class="token punctuation">,</span> <span class="token symbol">:user_info</span><span class="token keyword">end</span><span class="token keyword">end</span>response <span class="token symbol">:bad_request</span> <span class="token keyword">do</span><span class="token comment"># when get user failed</span>key <span class="token symbol">:description</span><span class="token punctuation">,</span> <span class="token string">'unexpected error'</span>schema <span class="token keyword">do</span>key <span class="token punctuation">:</span><span class="token string">'$ref'</span><span class="token punctuation">,</span> <span class="token symbol">:ErrorModel</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token comment"># ...</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Sau đó include nó vào trong controller:
12345678<span class="token comment"># app/controllers/users_controller.rb</span><span class="token keyword">class</span> <span class="token class-name">UsersController</span> <span class="token operator"><</span> <span class="token constant">ApplicationControllers</span>include <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">UsersApi</span><span class="token comment"># ...</span><span class="token keyword">end</span> - Response và Parameters:
Response và parameters sẽ rất hay bị trùng trong các API. Ví dụ: API login xong, bạn sẽ phải trả về thông tin User. Trong API show/edit thông tin user, bạn cũng sẽ phải trả về thông tin User, … Chúng ta sẽ phải tìm cách để sao cho có thể tái sử dụng code.
- Response:
Trong SwaggerUI, chúng ta có thể sử dụng $ref để gọi tới 1 schema đã được định nghĩa sẵn. Hình dung nó như 1 cái link để trỏ tới 1 Schema JSON khác.
12345678910111213141516171819<span class="token comment"># app/models/concerns/swagger/user_schema.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">UserSchema</span>extend <span class="token constant">ActiveSupport</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Concern</span>include <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Blocks</span>included <span class="token keyword">do</span>swagger_schema <span class="token symbol">:User</span> <span class="token keyword">do</span>key <span class="token symbol">:required</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token symbol">:name</span><span class="token punctuation">,</span> <span class="token symbol">:email</span><span class="token punctuation">]</span>property <span class="token symbol">:name</span> <span class="token keyword">do</span>key <span class="token symbol">:type</span><span class="token punctuation">,</span> <span class="token symbol">:string</span><span class="token keyword">end</span>property <span class="token symbol">:email</span> <span class="token keyword">do</span>key <span class="token symbol">:type</span><span class="token punctuation">,</span> <span class="token symbol">:string</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Sau đó, bạn cần include nó vào trong model user.rb
123456<span class="token comment"># app/models/user.rb</span><span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token operator"><</span> <span class="token constant">ApplicationRecord</span>include <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">UserSchema</span><span class="token keyword">end</span>Ngoài ra, thông thường chúng ta sẽ có 1schema ErrorSchema – nó sẽ được dùng khi API trả về thông tin lỗi.
12345678910111213141516171819<span class="token comment"># app/models/concerns/swagger/error_schema.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">ErrorSchema</span>extend <span class="token constant">ActiveSupport</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Concern</span>include <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Blocks</span>included <span class="token keyword">do</span>swagger_schema <span class="token symbol">:ErrorOutput</span> <span class="token keyword">do</span>key <span class="token symbol">:required</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token symbol">:errors</span><span class="token punctuation">]</span>property <span class="token symbol">:errors</span> <span class="token keyword">do</span>key <span class="token symbol">:type</span><span class="token punctuation">,</span> <span class="token symbol">:array</span>items <span class="token keyword">do</span>key <span class="token symbol">:type</span><span class="token punctuation">,</span> <span class="token symbol">:string</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Tiếp theo, chúng ta cần tạo file common_response để sử dụng schema trên
123456789101112131415<span class="token keyword">module</span> <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">ErrorResponses</span><span class="token keyword">module</span> <span class="token constant">NotFoundError</span><span class="token keyword">def</span> <span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">extended</span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>base<span class="token punctuation">.</span>response <span class="token number">404</span> <span class="token keyword">do</span>key <span class="token symbol">:description</span><span class="token punctuation">,</span> <span class="token string">"404 - Resource not found"</span>schema <span class="token keyword">do</span>key <span class="token punctuation">:</span><span class="token string">'$ref'</span><span class="token punctuation">,</span> <span class="token symbol">:ErrorOutput</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token comment"># ...</span><span class="token keyword">end</span>
- Parameters:
Cũng giống như phần Response, thì Parameter cũng sẽ được dùng chung bởi nhiều API, vì vậy, chúng ta cũng sẽ tiến hành định nghĩa riêng chỗ cho các parameter này. Ví dụ như các token authentication hay các fields id
1234567891011121314<span class="token comment"># app/controllers/concerns/swagger/parameters.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Parameters</span><span class="token keyword">def</span> <span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">extended</span><span class="token punctuation">(</span>base<span class="token punctuation">)</span>base<span class="token punctuation">.</span>parameter <span class="token symbol">:user_id</span> <span class="token keyword">do</span>key <span class="token symbol">:in</span><span class="token punctuation">,</span> <span class="token symbol">:path</span>key <span class="token symbol">:description</span><span class="token punctuation">,</span> <span class="token string">'User ID'</span>key <span class="token symbol">:required</span><span class="token punctuation">,</span> <span class="token keyword">true</span>key <span class="token symbol">:type</span><span class="token punctuation">,</span> <span class="token symbol">:integer</span>key <span class="token symbol">:format</span><span class="token punctuation">,</span> <span class="token symbol">:int64</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Tiếp theo, chúng ta khai báo nó vào trong file root của swagger
123456789101112131415<span class="token comment"># app/controllers/concerns/swagger/sample_app_root.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">SampleAppRoot</span>extend <span class="token constant">ActiveSupport</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Concern</span>include <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Blocks</span>included <span class="token keyword">do</span>swagger_root <span class="token keyword">do</span><span class="token comment"># ...</span>extend <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Parameters</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Cuối cùng cũng xong việc setting, còn lại bây giờ chỉ là sử dụng swagger vào trong các API
123456789101112131415161718192021222324252627282930<span class="token comment"># app/controllers/concerns/swagger/users_api.rb</span><span class="token keyword">module</span> <span class="token constant">Swagger</span> <span class="token punctuation">:</span><span class="token punctuation">:</span> <span class="token constant">UsersApi</span>extend <span class="token constant">ActiveSupport</span> <span class="token punctuation">:</span><span class="token punctuation">:</span> <span class="token constant">Concern</span>include <span class="token constant">Swagger</span> <span class="token punctuation">:</span><span class="token punctuation">:</span> <span class="token constant">Blocks</span>included <span class="token keyword">do</span>include <span class="token constant">Swagger</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">ErrorSchema</span>swagger_path <span class="token string">"/ users / {id}"</span> <span class="token keyword">do</span>operation <span class="token symbol">:get</span> <span class="token keyword">do</span>key <span class="token symbol">:description</span><span class="token punctuation">,</span> <span class="token string">"Returns the specified user"</span>key <span class="token symbol">:operationId</span><span class="token punctuation">,</span> <span class="token symbol">:find_user_by_id</span>parameters <span class="token symbol">:user_id</span> <span class="token comment"># Include parameters defined by root</span>response <span class="token number">200</span> <span class="token keyword">do</span>key <span class="token symbol">:description</span><span class="token punctuation">,</span> <span class="token string">"User specified by its ID"</span>schema <span class="token keyword">do</span>key <span class="token punctuation">:</span><span class="token string">"$ref"</span><span class="token punctuation">,</span> <span class="token symbol">:User</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token comment"># Captures the error response which is separately defined </span>extend <span class="token constant">Swagger</span> <span class="token punctuation">:</span><span class="token punctuation">:</span> <span class="token constant">ErrorResponses</span> <span class="token punctuation">:</span><span class="token punctuation">:</span> <span class="token constant">NotFoundError</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span><span class="token keyword">end</span>Bây giờ, cùng nhau nhìn lại cấu trúc project của chúng ta:
- Response:
2.3 Setup Swagger UI
Kết quả bên trên, chúng ta đã có 1 file json chứa đầy đủ thông tin rồi. Nhiệm vụ bây giờ chỉ còn là làm thế nào để show nó ra cho bên client có thể xem một cách dễ dàng được. Chúng ta sẽ sử dụng gem “swagger_ui_engine”
Thực hiện add gem trên vào gem file, run bundle và cuối cùng là bước config nó trong file config/routes.rb
1 2 |
mount <span class="token constant">SwaggerUiEngine</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Engine</span><span class="token punctuation">,</span> at<span class="token punctuation">:</span> <span class="token string">"/api_docs"</span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<span class="token comment"># config/initialize/swagger_ui_engine.rb</span> <span class="token keyword">if</span> <span class="token constant">Rails</span><span class="token punctuation">.</span>env<span class="token punctuation">.</span>development<span class="token operator">?</span> <span class="token operator">||</span> <span class="token constant">Rails</span><span class="token punctuation">.</span>env<span class="token punctuation">.</span>staging<span class="token operator">?</span> <span class="token constant">SwaggerUiEngine</span><span class="token punctuation">.</span>configure <span class="token keyword">do</span> <span class="token operator">|</span>config<span class="token operator">|</span> config<span class="token punctuation">.</span>swagger_url <span class="token operator">=</span> <span class="token punctuation">{</span> v1<span class="token punctuation">:</span> <span class="token string">"/api/v1/api_doc"</span> <span class="token punctuation">}</span> config<span class="token punctuation">.</span>validator_enabled <span class="token operator">=</span> <span class="token keyword">true</span> config<span class="token punctuation">.</span>json_editor <span class="token operator">=</span> <span class="token keyword">true</span> config<span class="token punctuation">.</span>request_headers <span class="token operator">=</span> <span class="token keyword">true</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Bây giờ, chúng ta start server và truy cập vào đường link: /api_docs, bạn sẽ thấy được thành quả của mình.