20/07/2019, 10:03

Service Object trong Rails

Rails app được xây dựng trên mô hình MVC và dựa trên 3 nguyên tắc: Không để models phình to Không để logic phức tạp trong views Giữ controllers sạch sẽ Vậy câu hỏi đặt ra là khi mà ta phải xử lí những tác vụ quá nặng cần rất nhiều logic code và không liên kết với một model cụ ...

Rails app được xây dựng trên mô hình MVC và dựa trên 3 nguyên tắc:

  • Không để models phình to
  • Không để logic phức tạp trong views
  • Giữ controllers sạch sẽ

Vậy câu hỏi đặt ra là khi mà ta phải xử lí những tác vụ quá nặng cần rất nhiều logic code và không liên kết với một model cụ thể nào thì ta nên viết thêm một action, viết thêm controllers khác hay đơn giản là thêm một vài câu query?
Có một cách là sử dụng Service Object để xử bớt các logic từ đó giúp Rails app của chúng ta sạch sẽ, dễ maintain hơn.

Service Object là gì?

Service Object có thể là một class hay một module có một public method thường được đặt tên là call/perform, được thiết kế để xử lí một task hay một bussiness logic.
Service Object là một class

class CloneUserService
  def initialize args
    @name = args[:user]
    @email = args[:email]
    @address = args[:address]
  end
    
  def call
    clone_user
  end
    
  private
    
  attr_reader :name, :email, :address
    
  def clone_user
    user = User.new(
      name: name,
      email: email,
      address: address
    )
    clone_user = user.dup
    clone_user.save
  end 
end

Service Object là một module:

module SendMailService
  class << self
    def send_mail user, topic
      return if user.blank?
      Application::Mailer.send_topic_mail(user, topic).deliver_later
    end
  end
end

Cách sử dụng service object

class CloneUsersController < ApplicationController
  def create
    args = {name: params[:name], email: params[:email], address: params[:address]}
    @user_clone = CloneUserService.new(args).call
    if @user_clone.save
      flash[:success] = "Success"
    else
      flash[:fail] = "Fail"
      render :new
    end
  end
end

Như ta có thể thấy trong CloneUsersController trên đã xử dụng Service Object và ta thấy code khá là ngắn gọn và dễ hiểu. Còn nếu như không xử dụng Service Object thì controller đó sẽ trông như thể nào?

class CloneUsersController < ApplicationController
  def create
    @user = User.new(
      name: name,
      email: email,
      address: address
    )
    @user_clone = @user.dup
      if @user_clone.save
        flash[:success] = "Success"
      else
        flash[:fail] = "Fail"
        render :new
     end
  end
end

Nên viết những gì trong Service Object?

Phía trên chúng ta đã biết Service Object là gì và cách tạo và khai báo một Service Object. Vậy chúng ta nên để Service Objects làm những công việc gì?
Sử dụng service objects là một cách rất hay để đóng gói những objects phức tạp, nhưng không nên lạm dụng mà áp dụng cho tất cả những methods có bussiness logic nặng. Một ví dụ điển hình là chuyển bớt vài trăm dòng code logic trong model vào trong một service object.
Một service object sạch, đẹp, có hiệu quả là một object dễ dàng để test và tuân theo single responsibility principle. Theo ** Amin Shah Gilani**:

Does your code handle routing, params or do other controller-y things? If so, don’t use a service object — your code belongs in the controller.
Are you trying to share your code in different controllers? In this case, don’t use a service object — use a concern.
Is your code like a model that doesn’t need persistence? If so, don’t use a service object. Use a non-ActiveRecord model instead.
Is your code a specific business action? (e.g., “Take out the trash,” “Generate a PDF using this text,” or “Calculate the customs duty using these complicated rules”) In this case, use a service object. That code probably doesn’t logically fit in either your controller or your model.

Tổng kết

Qua bài viết này, tôi muốn chia sẻ với các bạn đôi điều về service objects: cách khai báo, sử dụng service objects và lúc nào chúng ta nên dùng service objects.
Good luck and hapy coding!
Linh tham khảo
https://medium.com/@scottdomes/service-objects-in-rails-75ca74214b77
https://medium.freecodecamp.org/service-objects-explained-simply-for-ruby-on-rails-5-a8cc42a5441f

0