Syndication Icon
Published April 26, 2020 Updated July 31, 2021
Code Snippet
Command Pattern

The Command Pattern is an excellent way to simplify and encapsulate business logic into simple objects that do one thing extremely well. I first started seeing advanced use of the Command Pattern around the time Hanami came onto the scene back when it was known as Lotus. In fact, Hanami builds upon the Command Pattern, devoting an entire architecture section to what they call interactors. Dry RB also played a pivotal role. The goal of this article is to explain the Command Pattern, why it’s important, and how to leverage it.

Advantages

There are several benefits to using the Command Pattern, including:

  • Commands adhere to the Single Responsiblity Principle which is the S in SOLID design.

  • The public interface consists of two methods only:

    • #initialize - Allows an object to be constructed with minimum defaults using Dependency Injection which is the D in SOLID design.

    • #call - Provides a single, actionable, public method for messaging the object.

  • Each command can be swapped out for or used in conjunction with a proc or lambda, which share the same call interface. Additionally, commands can be used in case equality (i.e. #===), functional composition (i.e. << or >>), etc. which provides a myriad of flexible options.

  • The private interface encapsulates the objects injected during construction, uses the Barewords Pattern, and aids in keeping the public interface simple.

  • Commands enable reuse of existing functionality elsewhere in the architecture thus reducing duplication of code and keeping things DRY.

  • The architecture of a complex system is significantly improved when built upon simple objects.

  • Improves removal of objects when no longer needed.

Disadvantages

There are a few disadvantages I often hear complaints about when teaching this pattern or receiving feedback during code reviews, such as:

  • Commands create more objects to trace through when studying the architecture of an existing system.

  • Lots of small objects means lots of files loaded in your source editor.

  • Juggling more objects in your head when working with the implementation can add additional cognitive load.

While the above are concerns, I’d argue the advantages of the Command Pattern far outweigh the disadvantages, especially when working in complex systems which will exhibit all of the above concerns regardless. Do you want to deal with a complex system made of simple objects or larger more complex objects? Having lived with the latter, I much prefer the former.

Guidelines

When using the Command Pattern, it’s important to adhere to the following guidelines:

  • All objects names should be nouns ending in er or or if possible. In some situations this might not be possible, though. For example, let’s say you have a collection of commands which process different configurations like JSON, YAML, XML, etc. In that case, you can gather these loaders into a single namespace: Loaders::JSON, Loaders::YAML, Loaders::XML, etc. without violating this guideline. With the snippet above, the situation is simple, so we have a single Downloader object which specializes in downloading data.

  • The public interface should only consist of #initialize and #call.

  • #initialize should take one (minimum) to three (maximum) arguments.

  • #call should only take zero (minimum) to three (maximum) arguments.

  • The private interface should protect and encapsulate objects injected during construction, which aids in keeping the public interface simple too.

  • Avoid reaching for functionality provided by gems like Interactor for implementing the Command Pattern. Use of these gems introduce more complexity via before, around, and after callbacks as well as other functionality that cause unintended side effects. Instead, you can use any Plain Old Ruby Object (PORO) to implement the Command Pattern without introducing further complexity.

Usage

I’ll walk you through getting started with basic usage and then wrap up with advanced usage.

Basic

For context, here’s the implementation again:

# frozen_string_literal: true

require "net/http"

class Downloader
  def initialize client: Net::HTTP
    @client = client
  end

  def call(from, to) = client.get(from).then { |content| to.write content }

  private

  attr_reader :client
end

We can construct and message the downloader as follows:

require "uri"
require "pathname"

Downloader.new.then do |downloader|
  downloader.call URI("https://git-scm.com/images/logos/downloads/Git-Icon-1788C.png"),
                  Pathname(%(#{ENV["HOME"]}/Downloads/git.png))
end

With the above, as a simple use case, we are able to download the Git Logo to our local machine as git.png.

Now imagine if you need this capability in multiple aspects of your architecture. With a command, you have a simple object that can be reused with ease.

As a bonus, testing is straight forward because we can spy on the objects injected and test for log and download behavior.

# frozen_string_literal: true

require "spec_helper"

RSpec.describe Downloader, :temp_dir do
  subject(:downloader) { described_class.new client: client }

  let(:client) { class_spy Net::HTTP }

  describe "#call" do
    let(:source) { temp_dir.join "input.txt" }
    let(:from) { URI "file://#{source}" }
    let(:to) { temp_dir.join "output.txt" }

    it "downloads content" do
      source.write "Test content."
      allow(client).to receive(:get).and_return(source.read)

      downloader.call from, to

      expect(to.read).to eq("Test content.")
    end
  end
end

💡 Use of the temp_dir RSpec metadata is a Pathname folder provided by Gemsmith which ensures there is a tmp/rspec folder for dealing with temporary files. After running the specs, all temporary folders are automatically cleaned up.

Granted, the above provides basic test coverage. You might also want to consider use cases where input is invalid or corrupt.

Advanced

Argument forwarding is not a feature that should be used lightly but does provide an advantage when it comes to the Command Pattern. Especially when wanting to enhance the syntactic sugar of your object’s public API.

Let’s look at our basic implementation again except, this time, enhanced with argument forwarding:

# frozen_string_literal: true

require "net/http"

class Downloader
  def self.call(from, to, ...) = new(...).call(from, to)

  def initialize client: Net::HTTP
    @client = client
  end

  def call(from, to) = client.get(from).then { |content| to.write content }

  private

  attr_reader :client
end

Notice the introduction of the .call class method. By adding the leading from and to arguments, which will be given to #call, followed by ... to forward the remaining arguments to #new, we can now message Downloader without having to type #new each time:

Downloader.call URI("https://git-scm.com/images/logos/downloads/Git-Icon-1788C.png"),
                Pathname(%(#{ENV["HOME"]}/Downloads/git.png))

Even better, we can swap out our HTTP client — by using the HTTP gem instead of Net::HTTP — all via the power of argument forwarding:

require "http"

Downloader.call URI("https://git-scm.com/images/logos/downloads/Git-Icon-1788C.png"),
                Pathname(%(#{ENV["HOME"]}/Downloads/git.png)),
                client: HTTP

Conclusion

I hope this explanation paints a useful picture of what the Command Pattern is and how you can leverage this pattern in your own code. 🎉