Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Ba phương pháp chứng minh để cấu hình Ruby API Wrapper

Khi bạn dùng Ruby để wrap một API, bạn phải có cách để cấu hình nó. Có thể wrapper cần username và secret key, hoặc chỉ cần host.

Có vài cách khác nhau để xử lý điều này. Vậy bạn nên chọn cái nào?

Cách dễ dàng, toàn cục

Bạn có thể muốn dịch vụ của bạn hành động như nó luôn ở đó. Không quan trọng bạn ở đâu trong ứng dụng, bạn sẽ có nó sẵn sàng dùng. Nếu không, bạn sẽ mất ba dòng cấu hình cho mỗi dòng dùng nó!

Bạn có thể làm cấu hình toàn cục, dùng hằng số hoặc thuộc tính lớp:

config/initializers/product_api.rb
ProductApi.root = "https://staging-host.example.com/" ProductApi.user = "justin" ProductApi.secret = "mysecret123"
app/controllers/products_controller.rb
def show @product = ProductApi.find(params[:id]) end

Nhiều gem dùng pattern này. Nó khá dễ viết, và thực sự dễ dùng. Nhưng nó có một số vấn đề lớn:

  • Bạn chỉ có thể có một ProductApi.

    Nếu bạn muốn dùng Product API như hai user khác nhau, hoặc chạm máy chủ khác nhau từ một ứng dụng đơn, bạn hết vận.

  • ProductApi có dữ liệu toàn cục dễ dàng thay đổi vô tình.

    Nếu một thread hoặc phần ứng dụng thay đổi ProductApi.user, mọi thứ khác dùng ProductApi sẽ hỏng. Và những bug đó đau đớn để truy tìm.

Vì vậy, biến lớp có một số vấn đề. Điều gì nếu bạn cấu hình instance của lớp Product API của bạn, thay vào đó?

Sẽ trông như thế nào với #initialize?

Nếu bạn dùng instance, bạn sẽ tạo và cấu hình wrapper API khi bạn cần nó:

app/controllers/products_controller.rb
def show product_api = ProductApi.new( root: "https://staging-host.example.com/", user: "justin", secret: "mysecret123") @product = product_api.find(params[:id]) end

Bây giờ, bạn có thể truyền chi tiết khác nhau cho API của bạn bất cứ khi nào bạn dùng nó. Không phương thức hoặc thread khác dùng instance của bạn, vậy bạn không phải lo lắng về nó thay đổi mà bạn không biết.

Điều này dường như tốt hơn. Nhưng nó vẫn không dễ dàng như nó nên. Bởi vì bạn phải cấu hình API của bạn mỗi lần bạn dùng nó.

Hầu hết thời gian bạn không quan tâm API được thiết lập như thế nào, bạn chỉ muốn dùng nó với tùy chọn hợp lý. Nhưng khi bạn làm việc với instance, mọi phần ứng dụng dùng API phải biết cách cấu hình nó.

Nhưng có một cách để có sự tiện lợi của truy cập toàn cục, dùng mặc định tốt, trong khi vẫn có thể thay đổi nếu bạn cần.

Và pattern này xuất hiện mọi lúc trong một nơi thú vị: phát triển OS X và iOS.

Làm sao bạn có mặc định tốt và sự linh hoạt?

Điều gì xảy ra nếu bạn có thể cấu hình mỗi instance của wrapper API, nhưng bạn cũng có một instance "mặc định" toàn cục khi bạn chỉ không quan tâm?

Bạn sẽ thấy pattern "defaultSomething" hoặc "sharedWhatever" này khắp SDK iOS và Mac OS:

[[NSURLSession sharedSession] downloadTaskWithURL:@"https://www.google.com"]; [[NSFileManager defaultManager] removeItemAtPath:...];

Và bạn vẫn có thể yêu cầu instance của các lớp này nếu bạn cần nhiều hơn những gì default cho:

NSURLSession *session = [NSURLSession sessionWithConfiguration:...]; NSFileManager fileManager = [[NSFileManager alloc] init];

Bạn có thể xây dựng cái gì đó giống vậy trong Ruby, với phương thức lớp default_api:

app/controllers/products_controller.rb
def show @product = ProductApi.default_product_api.find(params[:id]) end ... def show_special special_product_api = ProductApi.new( root: "https://special-product-host.example.com/" user: "justin" secret: "mysecret123") @special_product = special_product_api.find(params[:id]) end

Và triển khai có thể trông giống như thế này:

class ProductApi def initialize(root:, user:, secret:) @root, @user, @secret = root, user, secret end def self.default_api @default_api ||= new( root: ENV['PRODUCT_API_ROOT'], user: ENV['PRODUCT_API_USER'], secret: ENV['PRODUCT_API_SECRET']) end def find(product_id) ... end end

Ở đây, tôi dùng biến môi trường trong default_api, nhưng bạn cũng có thể dùng file config. Và bạn có thể chuyển ||= để dùng thread- hoặc request-local storage thay vào đó.

Nhưng đây là một khởi đầu tốt.

Hầu hết gem tôi đã thấy, như gem Twitter, sẽ yêu cầu bạn cấu hình và tạo mỗi đối tượng API khi bạn cần chúng. Đây là giải pháp OK (dù tôi thường thấy mọi người gán chúng cho toàn cục vẫn vậy).

Nhưng nếu bạn đi thêm một bước, và cũng dùng một đối tượng mặc định đã cấu hình sẵn, bạn sẽ có thời gian thoải mái hơn nhiều.