Object as a Service

October 30, 2014

Мне нравится концепция сервис объектов для гурманов, на работе мы её часто используем.

Оригинальный материал можно найти по ссылке , у нас же прижились два правила:

  1. У сервисного объекта есть метод call (обязательно).
  2. Название начинается с глагола (за этим не следим, но так само получается).

В проекте среднего размера сервисные объекты хорошо заменяют архитектуру, организуя код в компактные классы. Приведу пару примеров:

ООП:

class AuthenticateUser
  def self.call(*args)
    new(*args).call
  end

  # NOTE: пользуемся keyword arguments
  # http://brainspec.com/blog/2012/10/08/keyword-arguments-ruby-2-0/
  def initialize(cookies, user, permanent: true)
    @cookies = cookies
    @user = user
    @permanent = permanent
  end

  def call
    if @user == "admin"
      set_cookie("admin")
    elsif @user.is_a?(Customer)
      set_cookie("c-#{@user.id}")
    elsif @user.is_a?(Reseller)
      set_cookie("r-#{@user.id}")
    else
      raise "unknown user: #{@user.inspect}"
    end
  end

  private

  def set_cookie(value)
    if @permanent
      @cookies.signed.permanent[:uid] = value
    else
      @cookies.signed[:uid] = value
    end
  end
end

Простой сервисный объект:

class GetCurrentUser
  def self.call(cookies)
    if cookies.signed[:uid] == 'admin'
      'admin'
    else
      user_type, user_id = cookies.signed[:uid].to_s.split('-', 2)
      if user_type == 'c'
        Customer.find(user_id)
      elsif user_type == 'r'
        Reseller.find(user_id)
      end
    end
  end
end

Сервисными объектами легко думать. Когда я вижу запись AuthenticateUser.call(cookies, @user), сразу догадываюсь, что это сервисный объект, у которого одна единственная обязанность - логинить пользователя. Не заглядывая в код ясно, что там все в порядке.

comments powered by Disqus