All software is made up of components that handle different parts of the system. These components are usually split into different groups, sometimes called “tiers” or “layers”. Layering can be applied to any software system, regardless of programming language, framework or paradigm.
The concept of “layering” is really just an extension of the Single-responsibility principle (SRP). SRP states that each class (or module, or component, or “thing”) must only have one purpose.
In web applications, controllers figure out how to process incoming web requests, models deal with databases, views deal with UI/response formatting, middleware deals with request processing, and instrumentation deals with logging and monitoring.
There’s plenty more that can be listed, however, these layers are only meant to deal with the underlying computer systems. Every web application will have these things, but what about code that actually makes your app unique? That is, the code that implements the business logic (BL) of your software. Doesn’t it deserve its own layer?
The answer is, yes, it does. Most people have a hard time with this at first. They don’t realize BL needs to be layered, so it ends up couch surfing somewhere else, usually in the controller or model.
Since each layer already reached its 1-responsibility quota, the only option left is to house your BL separately. This is commonly called the service layer.
By splitting your BL into its own layer, you’re giving it some breathing room. You’re allowing it to operate without bumping into other code that it has no business with.
Typically, the service layer will be composed of ordinary classes and functions that make use of models and other classes in order to achieve the desired business outcome.
The benefit of having BL in its own layer means it can be invoked independently.
Now, all controllers have to do is call the appropriate service, get the result, and format it into a response acceptable for clients.
This also enables easy re-use. Services can all each other in order to do the same task, consistently and reliable, regardless of where that service was called from.
Services can also be invoked by things other than incoming web traffic. You could build a separate interface that can call services through other means and for other purposes (e.g. SSHing into a web server to do a one-off task, or an admin interface that sits outside of your public-facing API).
Creating a service layer is essential for building complex software systems and will allow you to ship new features quickly and reliably.