Recommendation of Service class in Ruby on Rails

Hello, this is @ hairgai. This time, I will write my own usage of the pros and cons of the Service class.

What is the Service class?

A class group that describes a function that is derived from (and is self-recognizing) a service in DDD (Domain Driven Design). There are many people who explain in detail, so I will omit it, but I often use it because I can write business logic neatly between the model and the controller. This time (although my usage may be wrong), I will write my own usage and the part that seems to be its merit.

Basic usage

First of all, I would like to introduce the basic usage with a code example. (I'm not sure if this is the correct answer, but I think it's good because it's easy to see.) For example, if you describe the function of "following" on SNS etc. as a service layer in one file, it will look like this. ** * I'm repeating, but this is not the correct answer. ** **

class FollowService
  attr_reader :user, :target_user
  attr_accessor :follow

  def initialize(user, target_user)
    @user = user
    @target_user = target_user
  end

  def perform
    check!

    create_follow!
    run_after_worker!
  end

  private

  def check!
    check_following!
    check_blocking!
  end

  def check_following!
    return true unless user.following?(target_user)

    raise ArgumentError, 'User following target user'
  end

  def check_blocking!
    return true unless user.blocking?(target_user)

    raise ArgumentError, 'User blocking target user'
  end

  def create_follow!
    self.follow = user.follows.create!(target_user: target_user)
  end

  def run_after_worker!
    AfterFollowWorker.perform_in(0.2.seconds, follow.id)
  end
end

Create with pure Ruby classes

Basically, I don't make anything using Gem etc. By implementing it in pure Ruby, we are aiming for the effect of "lowering the barrier to understanding the implementation". The logic of the Service class tends to be complicated (when it comes to heavy functionality), so I try to keep it as simple as possible so that anyone can understand it immediately.

Determine the class name

I think you like the naming, but since it is a class that symbolizes the function, it is unified with [verb]([object]) Service. Having a naming convention has the advantage of making it easier to infer functions.

Make public methods perform only

I think there are many people who like it (many people call it call), but basically, only one public method is used, and the others cannot be called. This is because the Service class is a class that symbolizes a single function, and the functions that can be realized by using it are limited to a single one. There are quite a lot of people saying this single public method, but I also adopted it because there was no hesitation at the time of design and the implementation speed increased dramatically.

Only private methods are called by public methods

This is also adopted because it improves the visibility + 1 The responsibility for each method becomes lighter and the guard clause becomes easier to use. I think this is where you have different tastes.

Single approach to post-processing etc.

As I wrote in Follow Service above, I think about the response to the user first as long as it is used for business. As an approach in such a case, there is a method of "performing only necessary processing during one response", and subsequent processing etc. is stored in the queue as a job and processed by the job server etc. At that time, if the process of calling the job is scattered in various places such as Controller and Model, the visibility of the entire project will be poor.

Therefore, you can improve the overall outlook by using a naming convention such as ʻAfterHogeJob for post-processing jobs of HogeService` and calling only in the Service class.

Elimination of controller bloat

Needless to say.

class FollowsController < ApplicationController
  def create
    target_user = user.find(params[:target_user_id])
    service = FollowService.new(current_user, target_user)
    service.perform

    redirect_to user_path(target_user)
  end
end

Elimination of model bloat

Needless to say. Bring everything written in the Model to the Service class, and the model will be only data association, validation, and other methods related to a single model. As a result, there are no more logic-rich methods in the model, and no bloated, hard-to-see models.

Afterword

When I write it again in this way, I think that I am writing in a way that blames the Service class in various ways. I think it is arguable that the Service class is not necessary, but since such a story of design is just one of the methods, it would be nice if it could be used appropriately according to our business.

Recommended Posts

Recommendation of Service class in Ruby on Rails
Ruby on Rails Japanese-English support i18n
Class in Ruby
[Ruby on Rails] Introduction of initial data
[Rails] Addition of Ruby On Rails comment function
Let's summarize "MVC" of Ruby on Rails
part of the syntax of ruby ​​on rails
Ruby on Rails in Visual Studio Codespaces
[Ruby on Rails] Japanese notation of errors
Explanation of Ruby on rails for beginners ①
[Ruby on rails] Implementation of like function
Beginners create portfolio in Ruby on Rails
Erase N + 1 in acts_as_tree of tree structure Gem of Ruby on Rails
Implementation of Ruby on Rails login function (Session)
[Ruby on Rails] Until the introduction of RSpec
Rails new in Ruby on Rails ~ Memorandum until deployment 2
Ruby on Rails ~ Basics of MVC and Router ~
Ruby on Rails Elementary
[Ruby on Rails] A memorandum of layout templates
Ruby on Rails basics
Rails new in Ruby on Rails ~ Memorandum until deployment 1
(Ruby on Rails6) Creating data in a table
Ruby On Rails Association
[Ruby on Rails] How to install Bootstrap in Rails
A series of flow of table creation → record creation, deletion → table deletion in Ruby on Rails
[Ruby on Rails] Individual display of error messages
Ruby on Rails <2021> Implementation of simple login function (form_with)
[Ruby on Rails] Asynchronous communication of posting function, ajax
[Ruby on Rails] How to write enum in Japanese
[Ruby on Rails Tutorial] Error in the test in Chapter 3
Return the execution result of Service class in ServiceResponse class
Implementation of Ruby on Rails login function (devise edition)
Docker the development environment of Ruby on Rails project
[Ruby on Rails] Implementation of tagging function/tag filtering function
[Ruby On Rails] How to reset DB in Heroku
[Ruby / Rails] Set a unique (unique) value in the class
[Ruby on Rails] Post image preview function in refile
Explanation of Ruby on rails for beginners ⑥ ~ Creation of validation ~
Explanation of Ruby on rails for beginners ② ~ Creating links ~
Arrange posts in order of likes on Rails (ranking)
Try using the query attribute of Ruby on Rails
Explanation of Ruby on rails for beginners ⑦ ~ Flash implementation ~
Ruby on rails learning record -2020.10.03
Ruby on rails learning record -2020.10.04
Write class inheritance in Ruby
[Ruby on Rails] Debug (binding.pry)
Ruby on rails learning record -2020.10.05
Ruby on rails learning record -2020.10.09
Ruby on Rails config configuration
Ruby on Rails basic learning ①
[Ruby on Rails] about has_secure_password
Ruby on rails learning record-2020.10.07 ②
Commentary on partial! --Ruby on Rails
Ruby on rails learning record-2020.10.07 ①
Cancel Ruby on Rails migration
Ruby on rails learning record -2020.10.06
Judgment of fractions in Ruby
Ruby on Rails validation summary
Ruby on Rails Basic Memorandum
Difficulties in building a Ruby on Rails environment (Windows 10) (SQLite3)
Definitely useful! Debug code for development in Ruby on Rails