Railsアプリをサービスオブジェクトでリファクタリングする
サービスオブジェクトは、単一のアクションを実行するRubyオブジェクトです。ドメインまたはビジネスロジックのプロセスをカプセル化します。架空のライブラリアプリケーションで本のインスタンスを作成する必要があると想像してください。プレーンなRailsアプリでは、次のようにします。
class BookController < ApplicationController
def create
Book.new(*args)
end
end
これは単純なことには問題ありません。ただし、アプリが大きくなるにつれて、アプリを囲む多くの定型文になってしまう可能性があります:
class BookController < ApplicationController
def create
default_args = { genre: find_genre(), author: find_author() }
Book.new(attrs.merge(default_args))
end
private
def find_genre
// ...
end
def find_author
// ...
end
end
サービスオブジェクトを使用すると、この動作を別のクラスに抽象化できます。その後、コードは再び単純になります:
class BookController < ApplicationController
def
BookCreator.create_book
end
end
Railsは、MVC(モデル、コントローラー、ビュー、ヘルパーなど)の組織構造をネイティブにサポートするように設計されています。この構造は、単純なアプリケーションに適しています。ただし、アプリケーションが大きくなるにつれて、モデルとコントローラー全体にドメイン/ビジネスロジックが散らばっているのが見える場合があります。このようなロジックは、コントローラーにもモデルにも属していないため、コードの再利用と保守が困難になります。 Railsサービスオブジェクトは、ビジネスロジックをコントローラーやモデルから分離するのに役立つパターンであり、モデルを単純なデータレイヤーにし、コントローラーをAPIへのエントリポイントにすることができます。
ビジネスロジックをカプセル化するサービスを導入すると、次のような多くのメリットが得られます。
-
LeanRailsコントローラー -コントローラーは、要求を理解し、要求パラメーター、セッション、およびCookieを、動作するサービスオブジェクトに渡される引数に変換することのみを担当します。次に、コントローラーはサービスの応答に従ってリダイレクトまたはレンダリングします。大規模なアプリケーションでも、サービスオブジェクトを使用するコントローラーアクションは通常、10行以内のコードです。
-
テスト可能なコントローラー -コントローラーは無駄がなく、サービスの共同作業者として機能するため、特定のアクションが発生したときにコントローラー内の特定のメソッドが呼び出されたかどうかしか確認できないため、テストが非常に簡単になります。
-
ビジネスプロセスを個別にテストする機能 -サービスは、環境から分離された小さなRubyオブジェクトであるため、テストが簡単で高速です。すべての共同作業者を簡単にスタブし、サービス内で特定の手順が実行されているかどうかのみを確認できます。
-
再利用可能なサービス -サービスオブジェクトは、コントローラー、他のサービスオブジェクト、キューに入れられたジョブなどから呼び出すことができます。
-
フレームワークとビジネスドメインの分離 -Railsコントローラーはサービスのみを認識し、それらを使用してドメインオブジェクトと対話します。この結合の減少により、特にモノリスからマイクロサービスに移行する場合に、スケーラビリティが容易になります。最小限の変更で、サービスを簡単に抽出して新しいサービスに移動できます。
まず、架空のライブラリ管理アプリケーション用のapp/servicesという新しいフォルダに新しいBookCreatorを作成しましょう。
$ mkdir app/services && touch app/services/book_creator.rb
次に、すべてのロジックを新しいRubyクラス内にダンプしましょう:
# app/services/book_creator.rb
class BookCreator
def initialize(title:, description:, author_id:, genre_id:)
@title = title
@description = description
@author_id = author_id
@genre_id = genre_id
end
def create_book
Boook.create!(
title: @title
description: @description
author_id: @author_id
genre_id: @genre_id
)
rescue ActiveRecord::RecordNotUnique => e
# handle duplicate entry
end
end
end
次に、コントローラー内またはアプリケーション内の任意の場所でサービスオブジェクトを呼び出すことができます。
class BookController < ApplicationController
def create
BookCreator.new(title: params[:title], description: params[:description], author_id: params[:author_id], genre_id: params[:genre_id]).create_book
end
end
BookCreator.new(arguments).create
を簡略化できます BookCreator
をインスタンス化するクラスメソッドを追加してチェーンします create
を呼び出します 私たちのための方法:
# app/services/book_creator.rb
class BookCreator
def initialize(title:, description:, author_id:, genre_id:)
@title = title
@description = description
@author_id = author_id
@genre_id = genre_id
end
def call(*args)
new(*args).create_book
end
private
def create_book
Boook.create!(
title: @title
description: @description
author_id: @author_id
genre_id: @genre_id
)
rescue ActiveRecord::RecordNotUnique => e
# handle duplicate entry
end
end
end
コントローラでは、ブックの作成者を次のように呼び出すことができます。
class BookController < ApplicationController
def create
BookCreator.call(
title: params[:title],
description: params[:description],
author_id: params[:author_id],
genre_id: params[:genre_id])
end
end
コードをDRY(Do n't Repeat Yourself)のままにして、この動作を他のサービスオブジェクトで再利用するために、call
を抽象化できます。 ベースのApplicationService
へのメソッド すべてのサービスオブジェクトが継承するクラス:
class ApplicationService
self.call(*args)
new(*args).call
end
end
このコードを使用して、BookCreator
をリファクタリングできます。 ApplicationService
から継承します :
# app/services/book_creator.rb
class BookCreator < ApplicationService
def initialize(title:, description:, author_id:, genre_id:)
@title = title
@description = description
@author_id = author_id
@genre_id = genre_id
end
def call
create_book
end
private
def create_book
# ...
end
end
BusinessProcessGemを使用したサービスオブジェクトの作成
BusinessProcess gemを使用すると、基本アプリケーションサービスクラスを作成したり、initialize
を定義したりする必要はありません。 gemにはこれらすべての構成が組み込まれているためです。サービスオブジェクトは、BusinessProcess::Base
から継承する必要があります 。
gemファイルに次を追加します:
gem 'business_process'
次に、bundle
を実行します 端末のコマンド
class BookCreator < BusinessProcess::Base
# Specify requirements
needs :title
needs :description
needs :author_id
needs :genre_id
# Specify process (action)
def call
create_book
end
private
def create_book
# ...
end
end
1つのパブリックメソッド
サービスオブジェクトは、1つのビジネスアクションを実行し、それを適切に実行することになっているため、それを実行するための単一のパブリックメソッドのみを公開する必要があります。他のメソッドはプライベートで、パブリックメソッドによって呼び出される必要があります。名前がすべてのサービスオブジェクトで一貫している限り、パブリックメソッドに任意の名前を付けることができます。この例では、call
という名前を付けています。 。他の従来の名前はperform
です およびexecute
。
サービスオブジェクトの名前は、それが何をするかを示す必要があります。 「or」と「er」で終わる単語でサービスオブジェクトに名前を付ける一般的な方法があります。たとえば、サービスオブジェクトのジョブが本を作成することである場合は、BookCreatorという名前を付け、本を読むことがジョブである場合は、BookReaderという名前を付けます。
構文糖衣パターンのような抽象化やBusinessProcessのような宝石を使用して、サービスオブジェクトの呼び出しの表記を短くします。このアプローチを使用すると、BookCreator.new(*args).call
を簡素化できます。 またはBookCreator.new.call(*args)
BookCreator.call(*args),
に 短くて読みやすいです。
特に大規模なアプリケーションでサービスオブジェクトを導入するということは、1つのサービスオブジェクトから数十のサービスオブジェクトに成長することを意味します。コード編成を改善するには、一般的なサービスオブジェクトを名前空間にグループ化することをお勧めします。たとえば、図書館のアプリケーションでは、すべての本関連のサービスをグループ化し、すべての著者関連のサービスを別の名前空間にグループ化します。フォルダ構造は次のようになります:
services
├── application_service.rb
└── book
├── book_creator.rb
└── book_reader.rb
サービスオブジェクトは次のようになります:
# services/book/book_creator.rb
module Book
class BookCreator < ApplicationService
...
end
end
# services/twitter_manager/book_reader.rb
module Book
class BookReader < ApplicationService
...
end
end
これで、呼び出しはBook::BookCreator.call(*args) and Book::BookReader.call(*args)
になります。 。
複数のことを行うサービスオブジェクトを持つことは、サービスオブジェクトの「ビジネスアクション」の考え方に反します。従来、複数のアクションを実行する汎用サービスオブジェクトを使用することはお勧めしません。サービスオブジェクト間でコードを共有する場合は、ベースモジュールまたはヘルパーモジュールを作成し、ミックスインを使用してサービスオブジェクトに含めます。
例外をレスキューし、カスタム例外を発生させます
サービスオブジェクトの目的は、サードパーティのサービスやライブラリ間の相互作用やRails ActiveRecordによるデータベース操作など、実装の詳細をその中にカプセル化することです。 ActiveRecordとの対話中にActiveRecord::RecordNotUniqueなどのエラーが発生した場合、サービスは例外を適切にレスキューする必要があります。エラーがコールスタックに伝播することを許可しないでください。レスキューブロック内で処理できない場合は、そのサービスオブジェクトに固有のカスタム定義の例外を発生させます。
サービスオブジェクトパターンは、アプリケーションに新しい機能を追加するときに、アプリケーションの全体的な設計を大幅に改善できます。これにより、コードベースがより表現力豊かになり、保守が容易になり、テストの負担が軽減されます。
-
Rails5でのAngularの使用
あなたは前にその話を聞いたことがあります。分散型で完全に機能するバックエンドAPIと、通常のツールセットで作成されたフロントエンドで実行されているアプリケーションがすでにあります。 次に、Angularに移動します。または、AngularをRailsプロジェクトと統合する方法を探しているだけかもしれません。これは、この方法を好むためです。私たちはあなたを責めません。 このようなアプローチを使用すると、両方の世界を活用して、たとえばRailsとAngularのどちらの機能を使用してフォーマットするかを決定できます。 構築するもの 心配する必要はありません。このチュートリアルは、この目的のた
-
これらのアプリを使用してカメラでオブジェクトを識別
カメラ付きのスマートフォンは、写真をクリックするための従来のコンポーネントではなくなりました。 AR ステッカーを使って画像を面白くするなど、もっと魅力的なことを行うために使用されます .カメラを使用して顔を認識し、ジェスチャーで写真をクリックすることができます。同様に、スマートフォンのカメラでオブジェクトを識別できるようになりました。これは、カメラでクリックした被写体に関する情報を見つけるのに役立つ便利なテクニックの 1 つです。カメラでオブジェクトを識別するアプリについて説明しましょう: Google レンズ Google レンズは、この技術をサポートするすべての Android デバイ