Use stateless services

Augusts Bautra - Apr 30 - - Dev Community

This post is a response (comment, really) to Tim Riley's 2017 presentation on functional architecture, specifically, how to write truly stateless and functional services.

The bad example from him

Image description

This approach allows re-using the service object.
Image description

I believe the ability to do so is a negative, but even if you disagree, I will show how the reuse comes at too high a cost:

  1. The API becomes dispersed, some stuff in init, some in call. Where do I put a new parameter?
  2. Memoisation becomes impossible.
  3. Extract-method refactoring becomes cumbersome to impracticality because we have to pass everything as arguments.
def call(feed)
  # something very long
end

private

def split_out_of_call(feed)
end

def other_split(download_feed, some_loop_arg)
end
Enter fullscreen mode Exit fullscreen mode

The alternative

Let's lean into the functional aspect of a service, and disallow instantiation! This will remove the ability to reuse (an actual win in my book, YMMW), and allow us to use private instances as full black boxes:

class ImportProducts
  def self.call(...)
    new(...).call
  end

  # This is key. *Everything* about the instance is private.
  private  

  attr_reader :download_feed, :products_repo, :feed:

  # yes, even the initializer is private 
  def initialize(download_feed:, products_repo:, feed:)
    @download_feed = download_feed
    @products_repo = products_repo
    @feed = feed
  end

  # no params for #call
  def call
    # ...
  end

  def split_out_of_call
    # #feed implicitly available
  end

  def other_split(some_loop_arg)
    # #download_feed implicitly available
  end 
end
Enter fullscreen mode Exit fullscreen mode

And now in action

ImportProducts.(
  download_feed: download,
  products_repo: repo,
  feed: books_feed
)
Enter fullscreen mode Exit fullscreen mode

This way there is never an instance which might get some @errors populated to be accessed later. Everything needs to be in the #call return value, often a hash, but ideally a Struct.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .