Practical .NET

The Service Layer in Software Development Doesn't Exist

While the concept of "service classes" probably has some value when it comes to organizing the objects you're dealing with, the idea of a "service layer" has no value at all when it comes to building applications.

It's worth remember that vocabulary matters: If you're not using the right words, how will anyone know that you're down with what's happening now? (With that sentence alone, I may have demonstrated that I'm not keeping up with "what's happening now").

As an example of dead terminology (use this and be branded as "out of touch"), we don't have "data access layers" anymore -- instead, we implement the repository pattern. And I get that: There's a typical interface for repository classes and a whole ton of best practices for implementing them (and not all of it is contradictory). There are significant differences in concept, technology, and implementation between the repository pattern and the various ideas grouped under the "data access layer" rubric.

But here's my point: A repository class isn't a "layer" -- it's just a class that I call.

More dead terminology: We don't have "middle tier business objects" anymore. Instead, we have "the service layer." One definition I ran across recently was that a service layer was that part of the application that sat between the UI and the repositories, which is, the last time I checked, everything in the whole freaking application except the UI and the repositories ... not, I would suggest, a particularly useful definition.

I think that the idea of a "service layer" is about as useful these days as "data access layer." In fact, I think the whole idea of "layers" misses the point entirely, but I'll get to that.

Classifying Objects
I'm not suggesting that we shouldn't be classifying the classes we create. The fallout from applying the SOLID principles is that we end up with a ton of classes and objects (the same thing happened when we applied third normal form to database design: an explosion of tables). Just trying to think about all these classes has forced us into developing various classification schemes to try and segment this overwhelming number of classes into a manageable number of categories.

This gives us more terminology: We talk about entity classes, for example, which groups together all the property-rich objects that, typically, represent what we're updating in our long-term storage. We talk about Data Transfer Objects, which are property-rich classes that bring together whatever bizarre collection of data is needed for some purpose (the Model object passed to a View in ASP.NET MVC is a another example). We talk about factory classes that are method-rich classes that create and configure objects for you and repository classes, also method rich, that manage things we need to store and retrieve from storage.

On that basis, I can see having "service classes": classes that remove complexity from the code that they are called from. Given that definition, these classes exist at many places through our applications and system: A Repository class might call a service class that's responsible for managing cached data; An Action method in an ASP.NET MVC Controller might call a service class that handles changing the ship date on a salesorder. But calling them a layer -- as if they existed in one place within your application -- is just dumb.

The Importance of Service Classes: A Case Study
Just because service classes don't form a distinct layer doesn't mean they don't matter: Removing complexity is important and critical to creating simple objects. I won't extol the virtues of simplicity here (that's been done by Max Kanat-Alexander of Bugzilla and, now, Google) so you'll just have to take it for granted: Reducing complexity by having lots of simple objects is a good thing.

In fact, designing applications using the Dependency Inversion Principle (DIP) automatically leads you to creating service objects. When writing code, you should be thinking "What interface could I call that would make it easy to write this bit of code?" Once you've defined the methods and properties of the interface that would make your current code easier, you can go ahead and write the code that calls the interface's members. Then it's just a matter of defining a class with that interface and putting some code in its members. You have created a service class without even thinking about what "layer" you're in.

As an example, I was recently working with a client on an ASP.NET MVC project. Initially, I encouraged my client to write their Action methods to call "service classes" that would, in turn, call our repository objects. At the time, the primary role of the service classes was to manage transactions that crossed multiple repositories and, secondarily, to assemble the entities the repositories handed back into DTOs (it's always a good idea to build up your DTOs from common data structures.

Late in the design process I suggested that, since most of my client's customers were institutions, a Web API backend might make a lot of sense. Setting up a bunch of Web Services would allow those customers with a lot of transactions to bypass the UI we were building and integrate their systems directly with our backend. Of course, if we did that, we'd want our UI to also make use of that backend.

To handle that new design, we had to make just three changes:

  • Create copies of the service classes and move the copies into a new namespace
  • Take most of the functionality out of the original service classes (leaving their interfaces unchanged) but redesign them to call the Web Services
  • Design the Web Services to call our original service classes in their new namespace

If you want to call these layers, we now had two service "layers" that had to be crossed in any transaction: One at the UI and one at the Web API service.

Moving up to the Next Level
But service classes not only exist at multiple points in any application, they also exist at different levels of the architecture.

For example, I wasn't clear on how scalable this application would need to be. If the application needed to be highly scalable (and it wasn't clear it needed to be), a Web API that accepts and processes requests might not be the right design. It might make more sense for the Web API interface to simply accept the requests and write them out to a queue or some event-driven processing system. To implement that we would need to:

  • Make a copy of the service classes at the Web API
  • Gut the service classes at the Web API to have them just write to a queue or event-management system
  • Create a new application that would read requests from the queue/accept events and then call the original service classes to process the requests

The message formats we'd established for calling the Web API methods would probably even work as the message formats on the queue or in the events.

I'm not suggesting there wouldn't be more to do: We'd be switching from synchronous to asynchronous processing, which would almost certainly require the clients and our UI to be rewritten. But there are real benefits to having items wait temporarily on the queue. It could, for example, help deal with the problem of requests arriving in the wrong order. My service objects could process the queue looking for requests based on the time they were sent (not the time the arrived) or the process that adds items to the queue might insert items into the queue based on their sent time. It could also help when multiple copies of the same request show up: The process that adds to the queue or event system could check for duplicate requests already present and discard duplicates.

Which means that this queue management process is moving complexity out of both the client and the services -- the definition of a service class. Should that queue management software be considered part of the infrastructure or part of the "service layer"? And do we now have a service layer in three places: at the UI (responsible for calling the Web API), at the Web API (managing the queue), and in the classes that process the requests on the queue (calling the repository classes and managing transactions)?

Or, perhaps, we don't really care what layer a class is in. Maybe the whole idea of "layers" (as in "data access layer") is as dead as the dodo. Maybe what we have is lots of classes calling lots of other classes in a way that keeps complexity at any single point in the application low. In my opinion, if you're doing that then you're doing the right thing and the taxonomy you use to organize our classes in our heads is secondary, at best.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube