Tuesday, February 14, 2012

Service Layers or OOP?

In the last post, I mentioned that my co-workers and I had settled on a design that was working very well for us, but that wasn't very "object-oriented," at least not in the way Bob Martin described it.

We evolved to our approach through a lot of TDD and refactoring and plain old trial and error, but the final touches came when we  watched Gary Bernhardt's Destroy All Software screencasts and saw that he was using pretty much the same techniques, but with some nice naming patterns.

I don't know if there is a widely accepted name for this pattern, so I'm just going to call it the Service Layer Pattern.  It's biggest strengths are it's simplicity and clarity.  In a nutshell I'd describe it by saying that for every operation of your application, you create a "service" class.  You provide the necessary context to these services, in our case as Active Record objects (NOTE: service does NOT mean remote service (ie, REST), it just means a class that performs some function for us).

So far so basic, the real goodness comes when you add the layering.  I find there are a couple ways to look at this.  The more prescriptive is similar to DDD's (Domain Driven Design) "Layered Architecture" which recommends 4 layers: User Interface, Application, Domain, and Infrastructure.  From DDD:
The value of layers is that each specializes in a particular aspect of a computer program.  This specialization allows more cohesive designs of each aspect, and it makes these designs much easier to interpret.  Of course, it is vital to choose layers that isolate the most important cohesive design aspects.
In my Demonstrating the Costs of DI code examples the classes and layers looked like this:
SpeakerController (app)
 > PresentationApi (app)
   > OrdersSpeakers (domain)
   > PresentationSpeakers (domain)
     > Active Record (infrastructure)
   > Speaker (domain)
     > Active Record (infrastructure)
This concept of layering is very useful, but it's important not to think that a given operation will only have one service in each layer.  Another perspective on this that is less prescriptive but also more vague is the Single Responsibility Principle.  The layers emerge because you repeatedly refactor similar concepts into separate objects for each operation your code performs.  It's still useful to label these layers, because it adds some consistency to the code.

Each of these services is an object, but that doesn't make this an object-oriented design.  Quite the opposite, this is just well organized SRP procedural code.  Is this Service Layer approach inferior to the OOP design hinted at by Uncle Bob?  Or are these actually compatible approaches?

The OOP approach wants to leverage polymorphism to act on different types in the same way.  Does that mean that if I have a service, like OrdersParties, that I should move it onto the Party object?  What about the PartyApi class, should I find some way of replacing that with an object on which I could introduce new types?

There is a subtle but important distinction here.  Some algorithms are specific to a given type: User.Inactivate().  What it means to inactivate a user is specific to User.  Contrast that with User.HashPassword().  Hashing a password really has nothing to do with a user, except that a user needs a hashed password.  That is, the algorithm for hashing a password is not specific to the User type.  It could apply to any type, indeed to any string!  Defining it on User couples it to User, preventing it from being used on any string in some other context.

Further, some algorithms are bigger than a single type.  Ordering the speakers on a presentation doesn't just affect one speaker, it affects them all.  Think how awkward it would be for this algorithm to reside on the Speaker object.  Arguably, these methods could be placed on Presentation, but then presentation would have a lot of code that's not directly related to a presentation, but instead to how speakers are listed.  So it doesn't make sense on Speaker, or on Presentation.

Some algorithms are best represented as services, standing on their own, distinctly representing their concepts.  But these services could easily operate on Objects, as opposed to Data Structures.  Allowing them to apply to multiple types without needing to know anything about those specific types.  So I think the Service Layers approach is compatible with the OOP approach.

In the next post I'll take a look at how interfaces fit into this picture.

4 comments:

  1. "I don't know if there is a widely accepted name for this pattern, so I'm just going to call it the Service Layer Pattern."

    Did you try Googling "Service Later"? http://martinfowler.com/eaaCatalog/serviceLayer.html

    Martin Fowler documented that pattern in his 2002 Book "Patterns of Enterprise Application Architecture" http://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420 It's a great book and IMO should be on every programmer's shelf right next to GoF.

    -Jerry
    @JerryKnowsCode

    ReplyDelete
    Replies
    1. Hehe, thanks Jerry. Yeah, I've read poeaa, but I don't think what I'm describing is _exactly_ the same as what Fowler describes. According to Fowler, "a Service Layer defines an application's boundary and its set of available operations from the perspective of interfacing client layers". So in my silly example, PresentationApi is definitely a Service Layer. But OrdersSpeaker is a service, but doesn't fit Fowler's Service Layer definition. And it's this layering of services depending on other services that I was (poorly) trying to call out as possibly being bad OO. I should have called it "Service Layers" or something just to make it that much more distinct.

      But, you did just prompt me to re-read Fowler's Service Layers chapter, and it pretty much directly confirms what I was writing about. So thanks for that! I'd forgotten what he had to say about "Identifying Services and Operations."

      Delete
    2. I'm a little embarrassed to admit that I didn't read your entire blog post this morning. I got as far as "Service Layer" pattern and posted links to Fowler. ;-)

      We've been beating the service-layer drum pretty loudly at Within3 so I'll definitely give this a complete read later, especially with respect to your comparison with the pattern documented by Fowler.

      -Jerry

      Delete
  2. Hi Kevin, cool article!

    I think the advantage of this techniques comes from 'Admitting the ignorances'.

    We admit that we don't know some operations should belong to what objects/classes.

    We admit that we don't know, and we don't care.

    They are just some operations for now, and we don't want to waste time on finding a 'place' for these operations.

    So we just make it a service objects. And that's it.

    We can re-factor them later, so we keep the flexibility.

    We admit the ignorance and make it a stand-alone service object, so we keep the clarity.

    We save a lot of time for now, so we keep the dev speed.

    Cheers!

    Tony

    ReplyDelete