Bí quyết công nghệ của EzyPlatform - Quản lý API
Back To BlogsAPI metadata
Một API hay một URI trong EzyPlatform sẽ có thể có một số metadata như sau:
- Có phải là dạng API trả về JSON hay là trả về dạng HTML: Đây là thông tin rất quan trọng, để khi có lỗi xảy ra, trình xứ lý lỗi tự động của EzyPlatform sẽ phân biệt được để trả về JSON hay HTML tương ứng.
- Có cần phải xác thực hay không: Đây là thông tin cực kỳ quan trọng để ngăn chặn các truy cập trái phép từ người dùng chưa đăng nhập.
- API này thuộc tính năng nào: Để gom lại các API thuộc cùng một tính năng, thuận tiện cho việc phân quyền.
- API này có cần phải được thanh toán mới được sử dụng không: Để đảm bảo nhà phát triển phải trả phí cho các API có tính phí.
- API này là đồng bộ hay bất đồng bộ: Thường sử dụng cho các tác vụ nặng nề ví dụ như upload/download file.
- API này có phải chỉ dành cho quản lý (admin) hay không: Để yêu cầu access token của quản trị viên thì mới truy cập được.
Trong một API bạn có thể không bổ sung metadata nào hoặc là bổ sung toàn bộ.
Các annotation
Tương ứng với các metadata được liệt kê ở trên chúng ta sẽ có các annotation sau:
- @Api: Để chỉ định API ở dạng trả về JSON. Annotation này được sử dụng ở cả lớp lẫn hàm đều được.
- @Authenticated: Để chỉ định API cần xác thực. Annotation này được sử dụng ở cả lớp lẫn hàm đều được.
- @EzyFeature: Để chỉ định tính năng cho API. Annotation này được sử dụng ở cả lớp lẫn hàm đều được.
- @EzyPayment: Để chỉ định rằng API cần được thanh toán. Annotation này được sử dụng ở cả lớp lẫn hàm đều được.
- @Async: Để chỉ định rằng API được xử lý bất đồng bộ. Annotation này được sử dụng ở hàm.
- @EzyManagement: Để chỉ định API chỉ dành cho quản lý. Annotation này có thể được sử dụng ở cả lớp lẫn hàm.
Mã nguồn ví dụ:
@Api @Authenticated @EzyFeature("media_management") @Controller("/api/v1") @AllArgsConstructor public class AdminApiMediaController { @Async @EzyPayment @EzyManagement @DoPost("/media/add") public void mediaAddPost( HttpServletRequest request, HttpServletResponse response, @AdminId long adminId, @RequestParam("avatar") boolean avatar, @RequestParam(value = "notPublic") boolean notPublic ) throws Exception { mediaControllerService.addMedia( request, response, adminId, avatar, notPublic ); } }
Các API được quản lý thế nào?
Các API được quản lý thông qua hai lớp đó là RequestURIManager và FeatureURIManager. Ban đầu mình định gộp chung quản lý hết vào RequestURIManager, tuy nhiên thấy mã nguồn tương đối dài nên mình tách thành hai để hai lớp ngắn gọn hơn.
Lớp RequestURIManagerLớp này quản lý các metadata của API thông qua các map:
apiURIs
: Một map giữa danh sách các API URI ở dạng trả về JSON và phương thức HTTP, ví dụ{GET: [/api/v1/media/{id}, /api/v1/users/{username}]}
.authenticatedURIs
: Một map giữa danh sách các API URI cần được xác thực và phương thức HTTP, ví dụ{POST: [/api/v1/media/add, /api/v1/users/{username}]}
.handledURIs
: Một map giữa danh sách các API URI và phương thức HTTP, map này chứa toàn bộ các API URI cần quản lý của admin hoặc web, ví dụ{PUT: [/api/v1/admins/{id}, /api/v1/users/{username}]}
.managementURIs
: Một map giữa danh sách các API URI chỉ được truy cập bởi admin và phương thức HTTP, ví dụ{DELETE: [/api/v1/media/{id}, /api/v1/users/{username}]}
.paymentURIs
: Một map giữa danh sách các API URI cần được trả phí và phương thức HTTP, ví dụ{GET: [/api/v1/media/download, /api/v1/users/{username}]}
.
Lớp này chỉ quản lý các API được gắn với tính năng thông qua các map:
1. featureByURI: Một map giữa phương thức và tên tính năng sau đó lại map tiếp với API URI, ví dụ:
{ "/api/users": { "GET": "user_management", "POST": "user_management" }, "/api/products": { "GET": "product_management", "POST": "product_management" } }
2. urisByFeature: Một map giữa danh sách phương thức và API URI sau đó lại map tiếp với tên tính năng, ví dụ:
{ "user_management": { "/api/users": ["GET", "POST"], "/api/users/{id}": ["GET", "PUT", "DELETE"] }, "product_management": { "/api/products": ["GET", "POST"], "/api/products/{id}": ["GET", "PUT", "DELETE"] } }
Ví dụ về cách sử dụng
Thông thường chúng ta sẽ sử dụng RequestURIManager
và FeatureURIManager
ở RequestInterceptor
, ví dụ đoạn mã dưới đây có trong lớp AdminAuthenticationInterceptor
để kiểm tra quyền của người dùng khi truy cập vào API.
String feature = featureUriManager.getFeatureByURI( method, uriTemplate ); Set<Long> roleIds = adminRoleService.getRoleIdsByAdminId(adminId); Set<Long> specialRoleIds = adminRoleService.getSpecialRoleIds(); Map<Long, Map<String, Map<String, Set<HttpMethod>>>> methodsUriMapByFeatureByRoleId = roleFeatureService.getMethodsUriMapByFeatureByRoleId(); if (feature != null) { boolean hasPermission = false; for (Long roleId : roleIds) { if (specialRoleIds.contains(roleId)) { hasPermission = true; break; } Map<String, Map<String, Set<HttpMethod>>> methodsUriMapByFeature = methodsUriMapByFeatureByRoleId.get(roleId); if (methodsUriMapByFeature != null) { Map<String, Set<HttpMethod>> methodsUriMap = methodsUriMapByFeature.get(feature); if (methodsUriMap != null) { Set<HttpMethod> methods = methodsUriMap.get(uriTemplate); if (methods != null && methods.contains(method)) { hasPermission = true; break; } } } } if (!hasPermission) { throw new HttpForbiddenException( singletonMap("permission", "denied") ); } }
Tổng kết
Việc quản lý API là cực kỳ quan trọng, nó là một trong những nút thắt của việc phát triển những dự án lớn như EzyPlatform. Nó phải đảm bảo việc cung cấp đủ các metadata cho các API URI để EzyPlatform có thể tự động kiểm tra và giảm thiểu công việc cho các nhà phát triển.