2015-06-15 27 views
11

Tôi có một động cơ Rails 3 cho thấy các tuyến API cho khoảng 20 bộ điều khiển. Những bộ điều khiển này đại diện cho một số tài nguyên khác nhau ở các cấp độ lồng nhau khác nhau và được bao phủ bởi hơn 500 thử nghiệm rspec. API được phiên bản tại v1 bằng cách sử dụng không gian tên và ràng buộc định tuyến dựa trên tiêu đề phiên bản có mặc định là v1. Đây là hệ thống phiên bản được mô tả trong nhiều bài đăng trên blog tuyệt vời và dường như là phương pháp hay nhất.Cách chính xác để phiên bản Rails 3 API

Điều không ai trong số các bài đăng trên blog đó mô tả là cách bạn thực sự quản lý triển khai phiên bản mới. Tôi phải thực hiện một thay đổi đột phá cho đầu ra của một bộ điều khiển duy nhất. Sự thay đổi này ảnh hưởng đến phản ứng JSON của một đối tượng bằng cách thay đổi cấu trúc của một trong các giá trị JSON. Điều này sẽ gây ra các ngắt trong chỉ mục, hiển thị và chỉnh sửa các khung nhìn cho bộ điều khiển đó.

Rõ ràng là tôi có thể sao chép tất cả app/api/v1 đến app/api/v2 [1]. Sau đó tôi có thể thực hiện thay đổi duy nhất của mình cho bộ nối tiếp v2 mới. Bây giờ tôi đã có một số lượng lớn mã trùng lặp cho phiên bản 2 của API mà hầu như không có thay đổi nào. Tôi cần duy trì mã ở hai nơi. Tôi có lẽ sẽ phải có toàn bộ bộ rspec của tôi chạy trên bộ điều khiển phiên bản 2 cũng như các phiên bản 1 với một chút nhỏ của thử nghiệm thêm cho serializer v2. Điều này nghe có vẻ như một ý tưởng khủng khiếp. Chúng tôi có thể có một số bộ điều khiển gốc v2 cho mỗi bộ điều khiển không thay đổi trong không gian tên v1 được thừa kế từ bộ điều khiển v1. Điều này cũng không tốt lắm. Tùy chọn tốt nhất mà tôi có thể nghĩ là có một bộ điều khiển đơn (trong trường hợp này có lẽ chỉ là một bộ nối tiếp đơn) bên trong API v2 của tôi, với một số ma thuật định tuyến để kiểm tra xem bộ điều khiển cho phiên bản yêu cầu có tồn tại hay không trở lại thông qua các phiên bản trước cho đến khi nó tìm thấy một phiên bản. Phiên bản serializer cũng nên có phép thuật tương tự để kiểm tra xem có tồn tại phiên bản này hay không và quay trở lại cho đến khi nó tìm thấy nó. Điều này giới thiệu thêm mã tối thiểu và không ngay lập tức tăng gấp đôi thời gian của bộ thử nghiệm của tôi. Nó sẽ yêu cầu có thể cắm một hàm trực tiếp vào logic định tuyến đường ray trước khi nó có thể trả về 404 cho các bộ điều khiển v2 bị thiếu của tôi. Có thể tôi có thể phân tích các không gian tên cho tất cả các bộ điều khiển dựa trên hệ thống tập tin và tạo ra các tuyến đường ở thời gian khởi động đường ray với dự phòng nhưng sẽ rất khó để quản lý rõ ràng việc xóa các tuyến từ phiên bản trước của API.

Có vẻ như chúng tôi sẽ cần tiếp tục thực hiện việc này cho mọi định dạng chức năng/đầu ra không phụ thuộc thay đổi đến mức mỗi phiên bản trước đó không được chấp nhận và bị xóa. Chúng tôi có một API chưa được phát hành bổ sung bao gồm ~ 75 bộ điều khiển được bao phủ bởi ~ 4000 thông số kỹ thuật. Điều gì sẽ xảy ra khi chúng tôi bắt đầu tài liệu bên ngoài và phát hành các tài liệu này?

Khác với việc thực hiện các thay đổi API theo đợt không khả thi ở mức chúng tôi phát hành tính năng, làm cách nào để người khác quản lý tính năng này? Ý tưởng trên có thể ở tất cả không? Có cách nào tốt hơn?

[1] Phát hành một vấn đề. Chúng tôi đang sử dụng ActiveModel::Serializers để sản xuất phản hồi JSON. ActiveModel :: Serializers doesn't support API versioning, mặc dù có vẻ như là một way around this sử dụng phép thuật ruby ​​để chọn lớp chính xác.

+0

Bạn đã đọc câu trả lời cho câu hỏi này chưa? Có vẻ thích hợp. http://stackoverflow.com/questions/389169/best-practices-for-api-versioning – stolli

+0

Martin, bạn đã giải quyết được vấn đề dựa trên các câu trả lời dưới đây chưa? – Anatoly

Trả lời

2

Nếu tôi hiểu tất cả nhu cầu của bạn một cách chính xác, điều này sẽ không thể đủ giải pháp cho việc định tuyến các yêu cầu v2:

  1. Kiểm tra cho sự tồn tại tài nguyên dưới v2.
  2. Nếu không tìm thấy, hãy kiểm tra xem đó không phải là một trong các tài nguyên bị vô hiệu hóa. Nếu có, hãy trả lại 404.
  3. Dự phòng cho tài nguyên v1 nếu tìm thấy.

Dưới đây là một ví dụ mã (một phạm vi cho từng bước trong danh sách trên)

scope constraints: lambda { |request| request.url.split('api/')[1].split('/')[0] == 'v2' } do 
    # New resources introduced in v2 
end 

# Resource was not found in v2 API, check if it is removed 
scope constraints: lambda { |request| request.url.split('api/')[1].split('/')[0] == 'v2' } do 
    # Resources removed from v2 
    resources :resource1, to: proc { [404, {}, ['']] } 
end 

# Fallback for v2 routes that don't have v2 controller defined 
scope constraints: lambda { |request| ['v1', 'v2'].include?(request.url.split('api/')[1].split('/')[0]) } do 
    # Original v1 resources 
end 

Về serializers, như bạn đề cập đến bản thân những người có thể dễ dàng cố định bằng cách luôn luôn cung cấp bộ điều khiển mới khi thay đổi chúng, hoặc thậm chí bằng cách thực hiện một số phép thuật kiểm tra phiên bản từ URL trong default_serializer_options và đặt bộ nối tiếp dựa trên đó.

dự án
6

Các ActiveModel :: serializers có số issues liên quan đến Versioning, one of them cung cấp một ý tưởng làm thế nào để thực hiện phiên bản thông qua module Namespace nhưng nó đã đóng cửa 2 ngày trước theo sau là một từ ngữ phát triển của:

Như bạn nhận thấy chúng tôi cũng đã thảo luận về các vấn đề khác và PR của , và tôi rất vui khi đọc những suy nghĩ thực sự tốt đẹp từ tất cả các bạn.

Vì vậy, vấn đề với phiên bản AMS vẫn tồn tại nhưng chưa được giải quyết.

Quay lại câu hỏi ban đầu:

Rõ ràng rằng tôi có thể sao chép tất cả các ứng dụng/api/v1 để ứng dụng/api/v2. Tôi có thể sau đó thực hiện thay đổi duy nhất của mình cho trình nối tiếp v2 mới của tôi. Bây giờ tôi đã có một số lượng lớn mã trùng lặp cho phiên bản 2 của API có hầu như không có thay đổi nào. Tôi cần duy trì mã ở hai nơi.

Có sự thỏa hiệp giữa độ phức tạp thừa kế với các hiệu ứng phụ VS trùng lặp mã. Trong trường hợp có hệ thống mã hóa V1 được kiểm tra tốt nên được khóa cho bất kỳ sửa đổi nào, việc bảo trì có nghĩa là không có lỗi trong khi chạy bộ thử hồi quy. Chu kỳ phát triển phiên bản 1 đã hoàn thành, các bài kiểm tra viết, hành vi hợp đồng đã được ký kết. Bản sao mã V1-V2 có ý nghĩa và tránh được lỗi hồi quy.

tôi có lẽ sẽ cần phải có toàn bộ chạy rspec bộ của tôi trên phiên bản 2 điều khiển cũng như các phiên bản 1 cái với một chút nhỏ thêm thử nghiệm cho các serializer v2. Điều này nghe có vẻ như một ý tưởng khủng khiếp.

Tôi không đồng ý đó là một ý tưởng tồi tệ, đây là sự cân bằng giữa hành vi mong đợi và tiện lợi tưởng tượng với sự phát triển. Nó cũng không dễ dàng để tránh bộ spec được sao chép. Bộ điều khiển, mô hình có thể được tái sử dụng nhưng spec codebase sẽ có nhiều khả năng trùng lặp hơn 100% tự tin rằng những thay đổi mới không phá vỡ phiên bản API trước đó.

Các lựa chọn tốt nhất mà tôi có thể nghĩ đến là phải có một bộ điều khiển duy nhất (trong trường hợp này có lẽ chỉ là một serializer đơn) bên trong API v2 của tôi, với một số ma thuật định tuyến để kiểm tra xem một bộ điều khiển cho các phiên bản cần thiết tồn tại và quay trở lại thông qua các phiên bản trước cho đến khi tìm thấy một phiên bản.

Có, điều này nghe có vẻ tốt và giúp tránh mã ứng dụng (không phải là bộ mặc dù) trùng lặp nhưng yêu cầu nỗ lực phát triển bổ sung với bảo trì.Những gì bạn đang cố gắng gọi là copy-on-write, chỉ những thay đổi được sao chép lại. Đây là kỹ thuật tối ưu hóa nổi tiếng. Tuy nhiên, dự phòng HTTP có vẻ thích hợp hơn.

Có lẽ tôi có thể phân tích không gian tên cho tất cả các bộ điều khiển dựa trên hệ thống tập tin và tạo ra các tuyến đường vào lúc khởi động đường ray với fallbacks nhưng nó sẽ rất khó để quản lý một cách rõ ràng loại bỏ các tuyến đường từ một phiên bản trước của API.

Hãy tưởng tượng bạn có nhiều hơn 2 phiên bản API và một cuộc gọi API nhất định có 2 tổ tiên dự phòng mà thứ hai bị lỗi do nhà phát triển gây ra, bạn sẽ chặn không chỉ 404 trừ 500 ngoại lệ không? Điều gì xảy ra nếu phiên bản DB mới nhất phá vỡ tính tương thích ngược?

Chúng tôi có API chưa được phát hành bổ sung bao gồm ~ 75 bộ điều khiển được bao phủ bởi ~ 4000 thông số kỹ thuật. Điều gì sẽ xảy ra khi chúng tôi bắt đầu bên ngoài làm tài liệu và phát hành những tài liệu này?

Đây giống như câu hỏi về kiến ​​trúc hơn là triển khai cụ thể. Nếu API có xu hướng lớn, các mẫu thiết kế API có thể giúp tránh việc xây dựng API nguyên khối có thể khó hỗ trợ và duy trì.

Tôi muốn giới thiệu Phải làm gì:

  • trùng lặp V1 đến V2 hoàn toàn, bộ rspec bao gồm
  • đừng sợ tốn thời gian 2x các bài kiểm tra chạy
  • chờ đợi cho đến khi AMS phát hành một phiên bản (phát hành v0.10.x)
  • chia monolith API để những cá nhân có trụ sở

Nếu mã trùng lặp không được chấp nhận có thể tùy chọn khác là để lặp lại Rails ứng dụng và triển khai đến máy chủ và văn cùng các yêu cầu với cấu hình Nginx:

location /v1 { 
    proxy_pass http://http://unix:/tmp/v1_backend.socket:/v1/; 
} 

location /v2 { 
    proxy_pass http://http://unix:/tmp/v2_backend.socket:/v2/; 
} 

mã đặc biệt này cho thấy chỉ dành riêng cho một ví dụ, tôi không nói đó là một ý tưởng tốt để có 10 các ứng dụng Rails khác nhau với mỗi phiên bản riêng.

Quay lại câu hỏi ban đầu, việc tạo phiên bản API rất khó và đối với một số ứng dụng khách API, bạn nên có điểm cuối API URL mặc định (mới nhất).

+1

+1 câu trả lời hay, tôi cũng muốn thêm rằng nếu bạn muốn chia sẻ các chức năng trợ giúp giữa các phiên bản, bạn có thể trích xuất chúng thành một mixin và bạn có thể chạy thử nghiệm đối với mixin, và ngay khi chức năng bắt đầu thay đổi trong chức năng chung bạn cần phải di chuyển mã đó bên ngoài mixin và vào các bộ điều khiển, cũng sử dụng đối tượng biểu mẫu để xử lý apis của bạn, nó sẽ đơn giản hơn để kiểm tra (đơn giản hơn nhiều khi kiểm tra đơn vị lớp biểu mẫu hơn bộ điều khiển) https://www.reinteractive.net/posts/158-form-objects-in-rails – bbozo

+0

Tất cả những điều thú vị bắt đầu khi bạn di chuyển cơ sở dữ liệu của bạn (và bạn thường làm điều đó thường xuyên khi chuyển sang phiên bản tiếp theo của API). Làm thế nào để bạn xử lý trường hợp đó? – EugZol

+0

@EugZol không có câu trả lời nào khác để giữ tính tương thích ngược và thả nó khi không dùng phiên bản cũ hơn của API. – Anatoly

Các vấn đề liên quan