On Tue, Mar 12, 2013 at 1:26 PM, Martin Fowler firstname.lastname@example.org 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:
...these additional concerns are usually tackled in fairly awkward ways. E.g.
- More extremely granular "service objects" are added:
- Lots of additional fields are added to the users table:
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.