Caching data per call with command pattern

We’ve got a lot of classes following this general command pattern:

class SomeCommand
  import ['subcommand_a', 'subcommand_b']

  def call(args)
    dependency_a.(...)
    # ... processing specific to this command ...
    dependency_b.(...)
    # ...
  end
end

There are a lot of things I like about this pattern. It’s easy to test with dependency injection, I can re-use the subcommands elsewhere cleanly, and because there’s no per-call state in the object, dry-system can (could?) memoise and re-use it.

But sometimes subcommand_a and subcommand_b will both call the same expensive operation (e.g. a remote API request). Ideally I’d like to run this once, and use the result in both places.
I could add an argument, like so:

  def call(id, args)
    cached = expensive_operation(id)
    dependency_a.(..., cached)
    # ... 
    dependency_b.(..., cached)
    # ...
  end

But if there’s a lot of existing arguments, this gets quite verbose. It also breaks abstraction: if A calls B, B calls C, and C’s implementation changes to need expensive_operation, all of them need extra parameters.

I’ve also considered a full-fledged cache in the expensive_operation code, but that raises the spectre of cache expiry. Right now I only keep the expensive data for one call so I haven’t had to worry about that.

How have other people handled this situation? Is there some principle or tool I’m missing that would help?

Check out dry-effects - this is what I’d use for caching within a request cycle. There’s an effect called Cache that you can use easily, just plug it in and you’re done.

1 Like