Does the "Useful Object" pattern translate to Elixir? Operational vs Inert Dependencies

Long time no see ElixirForum. I’m working at a Rails shop right now, but I still like to tinker with Elixir every now and then. We’ve recently adopted a pattern for writing our objects, dubbed the Useful Object pattern: GitHub - eventide-project/useful-objects

The goal for this pattern is as follows:

An object’s dependencies are initialized by default to a safe , and inert (in terms of side effects) implementation of the dependency’s interface.

An object’s class interface provides a means of constructing an instance of the object, including the initialization of its dependencies to active , operational implementations of the dependencies’ interfaces, eg: an active database connection, an active payment gateway client, etc.

An object may record telemetry about its execution as it is executing. The activation of telemetry instrumentation is optional.

Here’s an example of what one looks like without the added macros:

class Something
  # Exposing some instance variables to the outside world...
  attr_reader :some_value
  attr_reader :some_other_value

  # Setting a dependency that uses its inert form by default, but can be later updated to the operational form...
  attr_writer :some_dependency
  def some_dependency
      @some_dependency ||= SomeDependency::Substitute.build
  end

  # Setting instance variables when you call new...
  def initialize(some_value, some_other_value)
    @some_value = some_value
    @some_other_value = some_other_value
  end

  # Call new, then turn set your substitute dependency to it's operational form
  def self.build(some_object)
    new(some_object.some_value, some_object.some_other_value).tap do |instance|
     instance.some_dependency = SomeDependency.()
    end
  end

  # Shorthand for building w/ operational dependency and actuating immediately
  def self.call(some_object)
    instance = build(some_object)
    instance.()
  end

  def call
    # Execute the object's raison d'etre, making use
    # of the object's attributes and dependencies
  end
end

class SomeDependency

  # Actuating SomeDependency causes some nasty side effects! Bad for testing...
  def call
    do_some_destructive_side_effect
  end

  # Luckily, here's our Substitute that mimics, but does not perform, any destructive side effects
  module Substitute
    def self.build
      SomeDependency.new
    end

    class SomeDependency < ::SomeDependency
      def call
        pretend_to_do_some_destructive_side_effect
      end
    end
  end
end

As you can see, Something starts off by using SomeDependency::Substitute until the build method is called, at which point is starts SomeDependency proper. I’ve found this paradigm pretty useful (heh) for standardize the way I write code as well as controlling side effects during tests.

Does this pattern translate to Elixir code or FP code in general? Are there any alternatives to this pattern to manage side effects during testing?

1 Like

You can do the same in elixir using e.g. default parameters on functions, or Keyword.get(opts, :dependency, DependencySubstitute). Processes can be initialized like your ruby class. Personally I’m not sure though if I’d want such code in production. Seems like a great way to shoot yourself in the foot by not initializing the real dependency with side effects and nobody noticing it.

2 Likes

Sounds similar in spirit to https://wiki.c2.com/?GoodCitizen (though useful objects appears to be more concerned with deps/side effects).

Agree with @LostKobrakai, this seems a good way to shoot yourself in the foot. An alternative you might consider is having a factory function for a set of inert dependencies e.g.

Service.new(Service.inert_dependencies())

vs

Service.new(%{db: db})

That way the caller has to be explicit about whether it should use the in inert dependencies or not.

edit: changed stubbed => inert

1 Like

^^ Thx for the responses! Discovered an article that also touches upon these patterns, worth checking out: https://blog.carbonfive.com/2018/03/19/lightweight-dependency-injection-in-elixir-without-the-tears/

1 Like