Sử dụng Service Object trong Rails giúp bảo trì code
Nếu bạn đi theo hướng Ruby on Rails, bạn sẽ nghe thấy nhiều từ 'service' hoặc thậm chí còn gặp nó trong thư mục app/services. Service Objects Service Object thực hiện tương tác của user với ứng dụng. Nó chứa business logic điều phối các thành phần tạo tác khác. Thật ra khi nhìn vào thư mục ...
Nếu bạn đi theo hướng Ruby on Rails, bạn sẽ nghe thấy nhiều từ 'service' hoặc thậm chí còn gặp nó trong thư mục app/services.
Service Objects
Service Object thực hiện tương tác của user với ứng dụng. Nó chứa business logic điều phối các thành phần tạo tác khác.
Thật ra khi nhìn vào thư mục services của một ứng dụng có thể cho người lập trình biết những chức năng thật sự của ứng dụng thực hiện mà thường không rõ ràng khi chúng ta xem các controllers và các models.
Để hiểu rõ hơn sẽ có một ví dụ về ứng dụng Rails sử dụng services.
-
app/
- services/
- create_invoice.rb
- correct_invoice.rb
- pay_invoice.rb
- register_user.rb
- register_user_with_google.rb
- change_password.rb
- services/
Chúng ta thấy rằng đây là ứng dụng hóa đơn chỉ nhìn vào mô hình User và hóa đơn nhưng khi nhìn vào service cho chúng ta biết về mục đích chính tương tác người dùng với ứng dụng.
Ứng dụng này cho phép ngườni dùng tạo hóa đơn, chính sửa và thành toán. Ngoài ra còn có chức năng đăng ký với tài khoản Google và chức năng đổi mật khẩu.
Lợi ích Services là tập trung lõi logic của ứng dụng vào trong object riêng biệt thay vì phân tán nó vào các controllers và các models. Dưới đây cho biết cách làm sử dụng và làm việc với service.
Quá trình thực hiện
- Nhận Input
- Thực hiện các chức năng
- Trả về kết quả
Khởi tạo
Một service thực thi tương tác của user vậy nó khởi tạo với đối tượng user đó. Trong ứng dụng web, service là user thực hiện yêu cầu. Ngoài ra khởi tạo biến còn có thêm các khung cảnh nếu áp dụng được.
Input
Service object nhận input của user có thể là form submit hoặc dữ liệu dạng JSON. Trong code của ứng dụng input có thể lấy nhiều forms.
Single value - một trường ít nhìn thấy.
Hash of values - ví dụ các params một controller rails phổ biến là dễ sử dụng. Tuy nhiên có hạn chế binding service với định dạng đầu vào là khi định dạng input thay đổi serivice bên trong phải thay đổi theo.
Form Object một đối tượng riêng biệt thể hiện input của user. Nó xử lý phân tích, xác định đầu định dạng đầu vào, giải phóng service. Nó rất hữu ích để tách phân tích cú pháp các params phức tạp từ công việc thực sự thực hiện trong service.
Ví dụ: InvoiceForm có thể biến đối 3 trường riêng biệt vào một Time object để làm cho tiện khi làm việc với code ứng dụng.
#form_object.rb class InvoiceForm attr_reader :params def initialize params @params = params end def billing_date Time.new(params[:year], params[:month], params[:day]) if time_data_present? rescue ArgumentError end def company_name params[:_messed_up_company_name] || params[:company_name] end def valid? billing_date.present? && company_name.present? end private def time_data_present? [params[:year], params[:month], params[:day]].all?(&:present?) end end form = InvoiceForm.new({year: "2014", month: "07", day: "18"})` form.billing_date # => Time instance
Sử dụng
Đặc điểm của service là khi chúng ta gọi một service nó thực hiện gửi một thông điệp gọi đến serivce instance. Phương thức gọi sử dụng data đạt vào khởi tạo và input data để thực hiện công việc của service. Nó bao gồm :
- creating/ updating/ deleting một record
- điều phối việc tạo / cập nhật nhiều record
- phân cấp cho các dịch vụ khác cho việc gửi emails, gửi thông báo
Cuối cùng phương thức sẽ trả về kết quả.
Kết quả
Service Object có thể thự hiện các thao tác. Đối với người lập trình biết rằng sẽ có thứ nào đó thực hiện sai vì vậy cần phải có thông báo khi thành công hoặc có lỗi xảy ra khi sử dụng service. Có 4 cách để kiểm tra : Boolean value cách đơn giản nhất chỉ trả về true khi thành công và false khi có lỗi. Nó chỉ trả về giá trị nhưng không thể mang thông tin nào khác được.
ActiveRecord Object nếu vài trò của service là tạo mới hoặc cập nhật rails models, kết quả là nó trả về một object. Chúng ta có thể kiểm tra của lỗi trong biến trả về để xác định cách gọi service thành công. Status Object chúng ta dùng các đối tượng dụng tiện ích để báo hiệu thành công hay lỗi nó giúp trong nhiều trường hợp phức tạp.
#status_object.rb class Success attr_reader :data def initialize(data) @data = data end def success? true end end class Error attr_reader :error def initialize(error) @error = error end def success? false end end class AuthorizationError < Error attr_reader :requesting_user def initialize(requesting_user, requested_clearance) @requesting_user = requesting_user @requested_clearance = requested_clearance super("User #{requesting_user.id} does not have required clearance level #{requested_clearance}") end end AuthorizationError.new(current_user, :admin)
Nêu lên ngoại lệ chúng ta sẽ nêu lên ngoại lệ với bật kỳ kiểu lỗi trong đối tượng service. Giống như các đối tượng status thì các ngoại lện có thể chứa dữ liệu và có nghĩa đầy đủ trong domain.
#exceptions.rb class MyAppError < StandardError attr_reader :data def initialize(data) super(data) @data = data end end class AuthorizationError < MyAppError def requesting_user data end end raise AuthorizationError, current_user
Điều hiển nhiên là khi gọi hoàn thành mà không có ngoại lệ coi như thành công. chúng ta nên khởi tạo service với dependencies như input. Nó cho phép trích xuất phương thức private trong service mà không cần phải nhận input là đối số. Chúng ta có thể thực hiện theo cấu trúc sau
#service_patterns.rb class ServiceObject attr_reader :input # ... private def private_method_with_input input.field # access input as method end end ServiceObject.new(user, dependency, input).call # Other ways: ServiceObject.new(user, dependency).call(input) ServiceObject.call(user, dependency, input)
Sử dụng Services Như chúng ta đã thấy cách đối tượng service có thể thực hiện bây giờ chúng ta sẽ áp dụng vào code của ứng dụng. Trong khung cảnh Rails thì controller là danh giới giữa giao diện người dùng và ứng dụng. Một ứng dụng sử dụng các serviecs có thể khởi tạo trong các controller actions, chỉ cho nó làm việc và phản ứng lại đến user.
#invoices_controller.rb class InvoicesController < ApplicationController # ... def create form = InvoiceForm.new(params) result = CreateInvoice.new(current_user, form).call @invoice = result.invoice if result.success? redirect_to @invoice else render :edit, error: result.error end end # ... end
Sử dụng các controllers với các services làm cho controller thật nhẹ vì tất cả business logic được đóng gói trong serivces và models và phân tích input trong đối tượng form.
Đây là một API cho phép dùng Service objects, Status objects và Rails Responder.
#responder.rb class APIResponder < ActionController::Responder private def display(resource, options = {}) super(resource.data, options) end def has_errors? !resource.success? end def json_resource_errors { error: resource.error, message: resource.error_message, code: resource.code, details: resource.details } end def api_location nil end end class Success attr_reader :data def initialize(data) @data = data end def success? true end end class Error attr_reader :error, :code, :details def initialize(error = nil, code = nil, details = nil) @error = error @code = code @details = details end def error_message error.to_s end def success? false end end class ValidationError < Error def initialize(details) super(:validation_failed, 101, details) end def error_message "Validation failed: see details" end end class InvoicesController < ApplicationController respond_to :json def create form = InvoiceForm.new(params) result = CreateInvoice.new(current_user, form).call # => ValidationError.new(invoice.errors) respond_with result # { error: "validation_error", code: 101, message: "..." details: { ... } } end def update result = UpdateInvoice.new(current_user, params[:id]).call # => Success.new(invoice) respond_with(result) # { billing_date: ..., company_name: ... } end # ... def self.responder APIResponder end end
Kết luận
Trong bài viết này chủ yếu vào Rails là một dependency có thể mô tả các service object. Vì chúng ta có thể dùng service object với bật kỳ web framework khác như mobile hoặc ứng console. Bài viết này nhằm mục đích giúp cho biết cách sử dụng các service objects giúp cho việc bảo trì code.