What is a factory and why FactoryLoader? 22 Mar 2008
This statement and question were posted to the “http://www.gr-ruby.org”:http://www.gr-ruby.org mailing list as it pertained to the release of FactoryLoader. This post should answer the question “what is a factory?” and also provide more insight into what FactoryLoader’s role is.

> I've read over your website, but I still have a lagging question which
> completely blinds my ability to see the usefulness of it: What is a factory?

A factory is “a mechanism for encapsulating complex creation logic” [0].

Here’s an example. Let’s say that you start writing an application that is a project management tool for a business. To start with they need the ability to create Projects. This is no big deal, you have a ProjectsController#create action and it makes your Project by doing something similar to the below:

def create
  project = Project.new params[:project]
  if project.save
    flash[:notice] = "successfully created the project"
    render :action => "create"
  else
    flash[:error] = "failed to create the project"
    render :action => "new"
  end
end  

Now a few weeks later the customer says that in order to create a project it needs to be assigned a project manager off the bat (the currently logged in user). This is not a very big deal since you have a “current_user” already, you just update your controller to look like:

def create
  project = Project.new params[:project]
  project.manager = current_user
  if project.save
    flash[:notice] = "successfully created the project"
    render :action => "create"
  else
    flash[:error] = "failed to create the project"
    render :action => "new"
  end
end

A few more weeks go by and your customer says that projects are tied to company budgets and when they are created they need to be automatically assigned to the correct budget based on the type of project. So we update to ProjectsController#create looks like the below:

def create
  project = Project.new params[:project]
  project.manager = current_user
  project.budget = Budget.find_by_project_type(project.type)
  if project.save
    flash[:notice] = "successfully created the project"
  else
    flash[:error] = "failed to create the project"
    render :action => "new"
  end
end  

Uh oh. There are important business rules being applied in the controller. The controller’s responsibility is to map an incoming request with an outgoing response and to know high level what the application needs to be doing. It should not know the intimate details of Project creation. Since we’re implementing the business rules on the controller we can’t re-use them for anything, unless we make a POST request to ProjectsController#create. This is quite limiting.

Another option to make them more reusable would be to tuck this away in the Project model itself. Perhaps we add a before_save callback which finds the appropriate budget. Even then we’ve only moved the budget logic out of the controller and there is still requirement for a Project to have a manager. If we go this route we’ll be separating two important pieces of how a Project gets constructed from each other. We would have the ProjectsController#create action setting the manager and then a before_save callback on the Project model finding a budget, both of which are required for a Project to be constructed.

One thought to remedy this would be put everything in the Project model, maybe in a before_save callback or overriding the constructor. But we quickly hit a roadblock because our Project model doesn’t know about the current user, that is application state and only the model knows about that. We could do something stupid and make the current_user global to the application, but that is a bad decision with pretty bad repercussions.

It’d be nice if we had a single object responsible for constructing a Project with these business rules. This is where a ProjectFactory comes into play. The ProjectFactory looks like:

class ProjectFactory
  def create(project_attrs, manager)
    project = Project.new project_attrs
    project.budget = Budget.find_by_project_type(project.type)
    project.save!
    project
  end
end

And the ProjectsController#create action looks like:

def create
  project = ProjectFactory.create params[:project], current_user
  flash[:notice] = "successfully created the project"
rescue 
  flash[:error] = "failed to create the project"
  render :action => "new"    
end

This is pretty straightforward. I just moved some of the lines from the action into the factory. More importantly then just moving line of code is that the act of constructing a valid project (given the customer’s requirements) isn’t the responsibility of the Project. The act of constructing a valid project is a process by itself which is important to the customer, so we isolate it in a ProjectFactory.

This is valuable because:

  • the business logic used to construct a project is in one spot. Should the customer change, add or remove requirements you only have to go to one spot to implement them. Granted if the method signature to this changes you have to go update the spots calling the create method, but you the actual logic for constructing the project isn’t spread throughout multiple files or classes.
  • it promotes reusability (the whole DRY thing), since my ProjectFactory is much more reusable then my ProjectsController.
  • it promotes single responsibility
  • it provides clearer meaning to the code base because we aren’t muddying up the controller or the Project model with creation logic
  • it makes for easier testing and easier to understand tests

The technical implementation of this pattern is mentioned in the GoF book [1]. The value of the pattern as it applies to Domain Driven Design is mentioned in the DDD book by Eric Evans.

Hopefully that helps clarify what a factory is, by seeing how it is used and some of the value it provides. This relates to the FactoryLoader because sometimes the progression of creation logic is over time.

For the first few weeks or months of a project you are hardcoding things like “Project.create” in multiple spots. When the customer starts introducing requirements for constructing (or updating) a Project you have to go update all of those spots with the same code (violating DRY) or go find all of those spots and refactor them to use a ProjectFactory, and then make a ProjectFactory. It doesn’t happen all the time, but when it does happen it can be time consuming, frustrating and painful to do.

What FactoryLoader would do in our example is it give us a ProjectFactory upfront w/o having to write any code. If we used FactoryLoader in the above example our ProjectsController#create action would look like:

def create
  project = ProjectFactory.create params[:project]
  if project.save
    flash[:notice] = "successfully created the project"
    render :action => "create"
  else
    flash[:error] = "failed to create the project"
    render :action => "new"
  end
end  

Where the ProjectFactory is created for you by FactoryLoader (you didn’t create the class, FactoryLoader did dynamically). There’s only one line that differs between the example that uses the FactoryLoader and the one that doesn’t.

Now when the customer starts adding business rules for creating a Project you can create your own ProjectFactory class and implement the customer’s business rules. FactoryLoader will see that you have provided your own ProjectFactory so it won’t dynamically create one. Since all of the spots where you create a Project are already using a ProjectFactory they get the changes for free. Overall you get to touch less code for the changes and you get to spend less time refactoring if any refactoring is needed.

The goal of FactoryLoader is to allow developers the ability to scale to these customer requirements with less pain. The above examples have been quite trivial, hopefully they are clear enough and meaningful enough to express why I wrote FactoryLoader,


Zach Dennis http://www.continuousthinking.com

  • 0 - Domain Driven Design by Eric Evans
  • 1 - Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson and Vlissides

blog comments powered by Disqus