Seven Days of Design Patterns | Part 2 - Organizers

April 29, 2019

I recently rolled off of a large monolithic application, written in Rails, that has been running for twelve years! As I sat down to write about our next pattern, Organizers, I realized that most often I have used them to solve problems in these large applications where there is often more code re-use as well as complexity. Let’s take a look at a real world use case. For this exercise we will be using the Interactor gem.

Let’s get started.

class RegisterUser
  include Interactor::Organizer

  organize CreateUser, RecordUserData, WelcomeNewUser
end

Can you believe this class used to be 200+ lines of code with nested conditionals, explicit rollbacks, and even API calls to third party services? I bet you can, but that’s not the point. Just look at that clean and minimal code! Using and testing this class is exceptionally easy.

class RegistrationsController < ApplicationController
  def create
    result = RegisterUser.call(user_params: user_params)

    if result.success?
      redirect_to dashboard_path
    else
      @user = result.user
      render :new
    end
  end

  private

  def user_params
    params.require(:user).permit!
  end
end

Here is a link to the Github code where you can see how these interactors and organizers are built and tested: github.

Ok ok, so you you recognized that perhaps this code is a little… over-engineered. If this was the only time in your codebase these objects were used, I’d likely agree. However, let’s imagine the business introduced two more requirements. First, they want to introduce a marketing effort where affiliates can put up a registration form for our application. This form needs to register new users and send a welcome email from the affiliate.

I wonder if we can reuse our interactors here…Let’s start with the organizer.

class RegisterAffiliateUser
  include Interactor::Organizer

  organize CreateUser, RecordUserData, WelcomeAffiliateUser
end

That wasn’t too hard right? Now let’s modify CreateUser to meet the requirement.

class CreateUser
  include Interactor

  def call
    user = User.new(user_params)

    if user.save
      context.user = user
    else
      context.fail!(message: "Could not save user")
    end
  end

  private

  def user_params
    context.slice(:email, :password, :name, :affiliate_id)
  end
end

The marketing team isn’t quite done though. If a user registers for our application in this way, then the affiliate needs to receive $5.00 from us as a referral reward.

class RegisterAffiliateUser
  include Interactor::Organizer

  organize CreateUser, RewardAffiliate, RecordUserData, WelcomeAffiliateUser
end

Do you see now? We have created small interactors that execute specific tasks. These are small enough that we can compose them in Organizers that encapsulate complex processes in our domain. This is the true power of the Organizer pattern when used in conjunction with the Interactor/Service pattern.

Comments

comments powered by Disqus