The madness of layered architecture
Bookmarking for Developers & Co with www.bookmarks.dev. Use our Add to Bookmarks.dev bookmarklet to your bookmarks toolbar for a seamless experience. Share your favorites with the community and they will be published on Github - Star
I once visited a team that had fifteen layers in their code. That is: If you wanted to display some data in the database in a web page, that data passed through 15 classes in the application. What did these layers do? Oh, nothing much. They just copied data from one object to the next. Or sometimes the “access object layer” would perform a check that objects were valid. Or perhaps the check would be done in the “boundary object layer”. It varied, depending on which part of the application you looked.
Puzzled (and somewhat annoyed), I asked the team why they had constructed their application this way. The answer was simple enough: They had been told so by the expensive consultant who had been hired to advice on the organization’s architecture.
I asked the team what rationale the consultant had given. They just shrugged. Who knows?
Today, I often visit teams who have three to five layers in their code. When asked why, the response is usually the same: This is the advice they have been given. From a book, a video or a conference talk. And the rationale remains elusive or muddled at best.
Why do we construct layered applications?
There’s an ancient saying in the field of computing: Any problem in computer science can be solved by adding a layer of indirection.
Famously, this is the guiding principle behind our modern network stack. In web services SOAP performs method calls on top of HTTP. HTTP sends requests and receives responses on top of TCP. TCP streams data in two directions on top of IP. IP routes packets of bits through a network on top of physical protocols like Ethernet. Ethernet broadcasts packets of bits with a destination address to all computers on a bus.
Each layer performs a function that lets the higher layer abstract away the complexities of for example resending lost packets or routing packets through a globally interconnected network.
The analogy is used to argue for layers in enterprise application architecture.
But enterprise applications are not like network protocols. Every layer in most enterprise application operates at the same level of abstraction.
To pick on a popular example: John Papa’s video on Single Page Applications uses the following layers on the server side (and a separate set on the client side):
EntityFramework. So for example the
AttendanceRepository property in
CodeCamperUnitOfWork returns a
AttendanceRepository to the
AttendanceController, which calls
GetBySessionId() method in
AttendanceRepository layer, which finally calls
DbSet.Where(ps => ps.SessionId == sessionId) on
EntityFramework. And then there’s the RepositoryFactories layers. Whee!
And what does it all do? It filters an entity based on a parameter. Wat?!
(A hint that this is going off the rails is that discussion in the video presentation starts with the bottom and builds up to the controllers instead of outside in)
In a similar Java application, I have seen – and feel free to skip these tedious details – the
SpeakersService.findByConference, which calls
SpeakersManager.findByConference, which calls
SpeakersRepository.findByConference, which constructs a horrific JPAQL query which nobody can understand. JPA returns an
@Entity which is mapped to the database, and the
Repository, or perhaps the
Controller, or perhaps two or three of these, will transform from Speaker-class to another.
Why is this a problem?
The cost of code: A reasonable conjecture would be that the cost of developing and maintaining an application grows with the size of the application. Adding code without value is waste.
Single responsibility principle: In the above example, the
SpeakerService will often contain all functionality associated with speakers. So if adding a speaker requires you to select a conference from a drop-down list, the SpeakerService will often have a findAllConferences method, so that
SpeakersController doesn’t need to also have a dependency on
ConferenceService. However, this makes the classes into functionality magnets. The symptom is low coherence: the methods of one class can be divided into distinct sets that are never used at the same time.
Dumb services: “Service” is a horrible name for a class – a service is a more or less coherent collection of functions. A more meaningful name would be a “repository” for a service that stores and retrieves objects, a
Query is a service that selects objects based on a criteria (actually it’s a command, not a service), a
Gateway is a service that communicates with another system, a
ReportGenerator is a service that creates a report. Of course, the fact that a controller may have references to a repository, a report generator and a gateway should be quite normal if the controller fetches data from the database to generate a report for another system.
Multiple points of extension: If you have a controller that calls a service that calls a manager that calls a repository and you want to add some validation that the object you are saving is consistent, where would you add it? How much would you be willing to bet that the other developers on the team would give the same answer? How much would you be willing to bet that you would give the same answer in a few months?
Catering to the least common denominator: In the conference application we have been playing with,
DaysController creates and returns the days available for a conference. The functionality needed for
DaysController is dead simple. On the other hand
TalksController has a lot more functionality. Even though these controllers have vastly different needs, they both get the same (boring) set of classes: A
Repository. There is no reason the
DaysController couldn’t use
EntityFramework directly, other than the desire for consistency.
Most applications have a few functional verticals that contain the meat of the application and a lot of small supporting verticals. Treating them the same only creates more work and more maintenance effort.
So how can you fix it?
The first thing you must do is to build your application from the outside in. If your job is to return a set of objects, with .NET
EntityFramework you can access the
DbSet directly – just inject
IDbSet in your controller. With Java JPA, you probably want a
Repository with a finder method to hide the JPQL madness. No service, manager, worker, or whatever is needed.
The second thing you must do is to grow your architecture. When you realize that there’s more responsibilities in your controller than deciding what to do with a user request, you must extract new classes. You may for example need a
PdfScheduleGenerator to create a printable schedule for your conference. If you’re using .NET entity framework, you many want to create some LINQ extension methods on e.g.
IEnumerable (which is extended by
The third and most important thing you must do is to give your classes names that reflect their responsibilities. A service should not just be a place to dump a lot of methods.
Every problem in computer science can be solved by adding a layer of indirection, but most problems in software engineering can be solved by removing a misplaced layer.
Let’s build leaner applications!