Ruby - Specification Pattern

Subscribe to receive new articles. No spam. Quality content.

Hi guys! I've been digging into ideas of Domain-Driven Design for a while and decided to share one cool pattern that I found in book: "Domain-Driven Design: Tackling Complexity in the Heart of Software" by Eric Evans (The Blue Book). If you haven't read this book yet - do that. Twice. Because it's so hard to get all concepts first time :) In this book Eric Evans describes Specification pattern which can make application more flexible. Today we're going to learn how to implement it.

Wiki:

The specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic.

Here is a quote from book:

Pattern provides ability to explicitly define business rules, avoiding conditional operators.

Sometimes you feel that particular class is not the best place to contain business logic and you can not find the proper place. If we put all logic into one class - we're going to have god-object. Something like ActiveRecord model, which usually bloated by business logic, validations, relations, etc. If we put domain logic into Controllers, it's going to be even worse, because we will have if...else statements all over the place.

Specification pattern allows us to check object if it satisfies business rules or not. Each specification class should respond to is_satisfied_by?(candidate) method and return true or false.

Let's consider simple example. Imagine we have simple blog post:

Post = Struct.new(:title, :tags, :author, :published)
post = Post.new("Specification Pattern", ["ruby", "patterns"], "Sergii", false)

We want to create specification which would check if post has a title. Code could look like this:

module Spec
  module Post
    class WithTitle
      def is_satisfied_by?(post)
        !post.title.empty?
      end
    end
  end
end

Spec::Post::WithTitle.new.is_satisfied_by?(post) # => true

It looks like an over engineering at first sight. Because we've created so much code just to check if post has a title, but you will get the idea when we have more requirements and code.

Eric Evans says that Specification pattern fits for the following goals:

  1. Validation: check if object satisfies business rules and ready for some purpose.
  2. Select an object from collection. (If we follow DDD - we can pass Specification into Repository).
  3. To specify the creation of a new object to fit some need.

Wiki said that pattern allows us to combine rules together by chaining them and using boolean logic. And that's true, because each element of specification should return either true or false.

I couldn't find implementation of this pattern on Ruby, but for me its definition means something like that:

spec = Spec::Composite.new(Spec::Post::WithTitle).and(Spec::Post::WithTags).not(Spec::Post::Published)
spec.is_satisfied_by?(post)

Now you see how explicit definition of Specification is. You can read that as plain english language (if we omit module names, of course). We see that composed specification has the following criteria:

  • Post with title
  • And post with tags
  • Not published

When we composed all criteria into one spec we can ask if post satisfies these rules. Looks cool, right?

We can just imagine how many if..else we would need to describe this specification in code of controller. Also, one of benefits of this approach that these rules don't live inside Post class. Post class is just a data structure. Besides that we can combine specification into composite one or use each particular specification separately. For example:

Spec::Post::WithTags.new.is_satisfied_by?(post)

Other languages use interfaces to implement this pattern. Ruby doesn't support interfaces, so I implemented specification pattern this way:

module Spec
  module Post

    class WithTitle
      def is_satisfied_by?(post)
        !post.title.to_s.empty?
      end
    end

    class WithTags
      def is_satisfied_by?(post)
        !post.tags.to_a.empty?
      end
    end

    class Published
      def is_satisfied_by?(post)
        !!post.published
      end
    end

  end
end

And here is main class which allows us to combine specifications using and and or methods:

module Spec
  class Composite

    def initialize(specs)
      @specs = { truthy: Array(specs), falsy: [] }
    end

    def is_satisfied_by?(candidate)
      truthy_check = ->(spec) { spec.new.is_satisfied_by?(candidate) }
      falsy_check = ->(spec) { !spec.new.is_satisfied_by?(candidate) }

      @specs[:truthy].all?(&truthy_check) && @specs[:falsy].all?(&falsy_check)  
    end

    def and(specs)
      @specs[:truthy] = (@specs[:truthy] + Array(specs)).uniq
      self
    end

    def not(specs)
      @specs[:falsy] = (@specs[:falsy] + Array(specs)).uniq 
      self
    end

  end
end

I feel that this code could be improved, but at this moment it's more like a proof of concept, rather than production-ready code.

Code I've wrote above allows us to combine specifications:

Post = Struct.new(:title, :tags, :author, :published)
post = Post.new("Specification Pattern", ["ruby", "patterns"], "Sergii", false)

spec = Spec::Composite.new(Spec::Post::WithTitle).and(Spec::Post::WithTags).not(Spec::Post::Published)
spec.is_satisfied_by?(post) # => true

I really like this approach. I see the following advantages of using it:

  • all specifications are easy to test
  • code of each specification is easy to understand
  • Post class is just a data object
  • benefits of combining conditions using and and not methods
  • we can use each particular specification separately or combine with other ones

As always I'll appreciate any feedback.

Thanks for reading!

Subscribe to receive new articles. No spam. Quality content.

Comments