2024-07-20

How to implement Dependency Injection Pattern in Ruby On Rails?

https://dev.to/vladhilko/how-to-implement-dependency-injection-pattern-in-ruby-on-rails-28d6

class LoggerService
  def self.call(params)
    puts params
  end
end

class Service
  def call
    LoggerService.call('Something happened')
  end
end

Service.new.call

このハードコードアプローチの問題点:

  • SOLIDの依存関係逆転の原則に反する
  • クラスの内部メソッドを更新する必要があるため、コードを変更するのはかなり難しく、何かを壊してしまう危険性がある
  • 内部でハードコードされたサービスをたくさん使い始めると、読んで理解するのが難しくなる。 例えば、ハードコードされたサービスは複数の名前空間(Namespace1::Namespace2::LoggerServiceなど)の下にある可能性があるので、本当に「認知的負荷」が増えます)。

Plain Ruby Dependency Injection

記事では dry gem を使った例もあるがやらない

class Service
  attr_reader :logger_service

  def initialize(logger_service:)
    @logger_service = logger_service
  end

  def call
    logger_service.call('Something happened')
  end
end

# Now you can inject any logger service you want into the Service class
service = Service.new(logger_service: LoggerService)
service.call

依存関係の逆転の説明

依存関係の逆転の原則は、オブジェクト指向設計のSOLID5原則の1つである。

  • 高レベルモジュール(クラスやコンポーネントなど)は、低レベルモジュールに依存してはならない。
  • 抽象化は詳細に依存すべきではない。 詳細(具体的な実装)は抽象に依存する必要があります。

提供されたコード例のコンテキストでは、Service クラスは高レベルモジュールと見なされ、LoggerService クラスは低レベルモジュールと見なされます。 依存性注入を使用することで、Service クラスは LoggerService クラスの特定の実装に依存しなくなり、代わりに抽象化(つまり、params 引数を受け入れる呼び出しメソッドを持つオブジェクト)に依存します。

これは依存性の逆転の原則に従います。なぜなら、高レベルの Service クラスは、低レベルのモジュール(LoggerService クラス)ではなく、抽象化(logger_service オブジェクト)に依存するからです。 また、詳細(LoggerService クラスの具体的な実装)が抽象(logger_service オブジェクト)に依存するため、この原則に従います。

全体として、依存性注入を使用し、依存性の逆転の原則に従うことで、ソフトウェアの設計とアーキテクチャを改善することができます。