Skip to content

Instantly share code, notes, and snippets.

@blaix
Created June 12, 2013 11:04
Show Gist options
  • Save blaix/5764401 to your computer and use it in GitHub Desktop.
Save blaix/5764401 to your computer and use it in GitHub Desktop.
Martin Fowler on Service Objects via the Ruby Rogues Parley mailing list

On Tue, Mar 12, 2013 at 1:26 PM, Martin Fowler martinfowlercom@gmail.com wrote:

The term pops up in some different places, so it's hard to know what it means without some context. In PoEAA I use the pattern Service Layer to represent a domain-oriented layer of behaviors that provide an API for the domain layer. This may or may not sit on top of a Domain Model. In DDD Eric Evans uses the term Service Object to refer to objects that represent processes (as opposed to Entities and Values). DDD Service Objects are often useful to factor out behavior that would otherwise bloat Entities, it's also a useful step to patterns like Strategy and Command.

It sounds like the DDD sense is the sense I'm encountering most often. I really need to read that book.

The conceptual problem I run into in a lot of codebases is that rather than representing a process, the "service objects" represent "a thing that does the process". Which sounds like a nitpicky difference, but it seems to have a real impact on how people use and evolve them.

An example I've been using is the process of adding a new user to some system. A typical service object someone might create is UserAdder. The sole method usually has a name which reiterates the name of the class:

class UserAdder
  def add_user(...)
    # perform user addition
  end
end

As the system evolves, user addition becomes more complicated. For instance, it becomes more of a workflow: a user might be prompted for some additional information before finishing the sign-up. Or they might need to be approved by an admin.

It's also common to discover various bits of useful metadata about the addition that we want to preserve. Perhaps we want to save when the addition process was started and finished, so we can track how long the process is taking on average. Or where they were originally referred from. Or who approved the addition.

When the code to add a user looks like this:

UserAdder.new.add_user(...)

...these additional concerns are usually tackled in fairly awkward ways. E.g.

  • More extremely granular "service objects" are added: UserExtraInfoCollector, UserApprover.
  • Lots of additional fields are added to the users table: addition_state, addition_started_at, addition_finished_at, approver_id, and so on. These are added to a table which is often already quite large.

If, on the other hand, we use a class to model an individual instance of the process, and name it accordingly, things tend to developer rather differently.

class UserAddition
  def complete
    # ...
  end
end

Here, UserAddition is used in the sense: "we processed 15 user additions this morning".

In this case, extra steps in the user addition process intuitively become new methods on the object that represents the process:

class UserAddition
  def begin
    # ...
  end
  
  def collect_extra_info
    # ...
  end
  
  def approve
    # ...
  end
  
  def complete
    # ...
  end
end

When we discover that we want to save metadata about user additions, we make the class into an ActiveRecord and add the appropriate columns to a new user_additions table. When the name is UserAddition, these modifications just fall into place naturally.

It seems like a lot to say about a little name change, but when I work through it with pairing clients they seem to appreciate the clarity it brings to how they model their business logic.

@luqmansen
Copy link

Thanks for sharing this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment