Extract a service object in any framework

Extracting a service object is a natural step in any kind of framework-dependent application. In this blog post, I’m showing you an example from Nanoc, a blogging framework.

The framework calls you

The difference between a library and a framework is that you call the library, while the framework calls you.

This slight difference may cause problems in applications being too dependent on the framework. Another potential problem is when your app lives inside the framework code.

The ideal situation seems to be when your code is separated from the framework code.

The “Extract a service object” refactoring is a way of dealing with the situation. In short, you want to separate your code from the framework code.

A typical example is a Rails controller action. An action is a typical framework building block. It’s responsible for several things, including all the HTTP-related features like rendering html/json or redirecting. Everything else is probably your application code and there are gains in extracting it into a new class.

Before

We’re using the nanoc tool for blogging in our Arkency blog. It serves us very well, so far. One place, where we extended it was a custom Nanoc command.

The command is called “create-post” and it’s just a convenience function to automate the file creation with a proper URL generation.

Here is the code:

require 'stringex' usage 'create-post [options] title' aliases :create_post , :cp summary 'create a new blog post' description 'Creates new blog post with standard template.' flag :h , :help , 'show help for this command' do | value , cmd | puts cmd . help exit 0 end run do | opts , args , cmd | unless title = args . first puts cmd . help exit 0 end date = Time . now path = "./content/posts/ #{ date . strftime ( '%Y-%m-%d' ) } - #{ title . to_url } .md" template = << TEMPLATE # #{ title } TEMPLATE unless File . exist? ( path ) File . open ( path , 'w' ) { | f | f . write ( template ) } puts "Created post: #{ path } " else puts "Post already exists: #{ path } " exit 1 end puts "URL: http://blog.arkency.com/ #{ date . year } / #{ date . month } / #{ title . to_url } " end

It was serving us well for over 3 years without any change. I’m extracting it to a service object, mostly as an example to show how it would work.

After

require 'stringex' usage 'create-post [options] title' aliases :create_post , :cp summary 'create a new blog post' description 'Creates new blog post with standard template.' flag :h , :help , 'show help for this command' do | value , cmd | puts cmd . help exit 0 end run do | opts , args , cmd | unless title = args . first puts cmd . help exit 0 end CreateNewPostFromTemplate . new ( title , Time . now ). call end class CreateNewPostFromTemplate def initialize ( title , date ) @title = title @date = date end def call unless File . exist? ( path ) File . open ( path , 'w' ) { | f | f . write ( template ( @title , @date )) } puts "Created post: #{ path } " else puts "Post already exists: #{ path } " exit 1 end puts "URL: #{ likely_url_on_production } " end private def path "./content/posts/ #{ @date . strftime ( '%Y-%m-%d' ) } - #{ @title . to_url } .md" end def likely_url_on_production "http://blog.arkency.com/ #{ @date . year } / #{ @date . month } / #{ @title . to_url } " end def template ( title , date ) << TEMPLATE # #{ title } TEMPLATE end end

I’ve created a new class and passed the arguments into it. While doing it, I’ve also extracted some small methods to hide implementation details. Thanks to that the main algorith is a bit more clear.

There’s more we could do at some point, like isolating from the file system. However, for this refactoring exercise, this effect is enough. It took me about 10 minutes to do this refactoring. I don’t need to further changes now, it’s OK to do it in small steps.

It’s worth to consider this techniqe whenever you use any framework, be it Rails, Sinatra, nanoc or anything else that calls you. Isolate early.

If you’re interested in such refactorings, you may consider looking at the book I wrote: Fearless Refactoring: Rails Controllers. This book consists of 3 parts:

the refactoring recipes,

the bigger examples,



the “theory” chapter

Thanks to that you not only learn how to apply a refactoring but also know what are the future building blocks. The building blocks include service objects, repositories, form objects and adapters.