I once worked on a project that was basically a simple My Account application/area for a train ticket retailer.
The backend itself held no data, but whoever built the backend had gone full service layer, with models and adapters to the upstream services that hold the data.
The result was a backend that was a pain in the ass to change, necessitating whole trees of file changes to build features.
So we started inlining everything. We just took it back to the request handlers. We started fetching, mutating and returning the data in the request handlers. Suddenly a change became modifying one function. Every endpoint was unique and didn't depend on anything else. Things became easy.
Halfway through the migration, someone got our effort reviewed by a principal engineer who told me "it wasn't SOLID", and my contract wasn't renewed. It didn't dishearten me.
Software design is meant to make change easier and proudly adding abstractions can be a bad thing.
> So we started inlining everything. We just took it back to the request handlers. We started fetching, mutating and returning the data in the request handlers. Suddenly a change became modifying one function. Every endpoint was unique and didn't depend on anything else. Things became easy.
What is the big benefit you gained from doing that compared to calling, say, a service method call in the controller?
costService.generateCost(newPrice);
Is it really that difficult to go to the service method definition? With inlining at the controller level, in order to unit test generateCost, you'll now have to deal with authentication/authorization/request handling related infrastructure which has nothing to do with cost calculation.
The most complicated code we had was for generating receipts, which used some functions which we kept separate, because it made sense.
Auth was handled by a middleware. And then once we'd stripped out all the layers, 95% of the handlers looked like roughly like this:
result = fetch(...);
/* Maybe more fetches, maps or filters */
response.send(result);
They didn't really _do_ anything. It was all just small tweaks to data someone else owned. The biggest challenge were upstream endpoints changing on us, making sure we were logging and passing things like correlationIds consistently. Moving to fat handlers, we unified those by having those things already set up and passed into the handlers. The focus was on devex, so a junior could easily modify/create an endpoint and not have to think about how to get it right. We made the pit of success as easy to fall into as possible by breaking the rules that weren't serving the project very well.
It was a glorified proxy layer. There were benefits in treating it as such, rather than deluding ourselves into thinking we needed services, repositories, models and such. Just transform data from someone else's endpoints and focus on the frontend.
The backend itself held no data, but whoever built the backend had gone full service layer, with models and adapters to the upstream services that hold the data.
The result was a backend that was a pain in the ass to change, necessitating whole trees of file changes to build features.
So we started inlining everything. We just took it back to the request handlers. We started fetching, mutating and returning the data in the request handlers. Suddenly a change became modifying one function. Every endpoint was unique and didn't depend on anything else. Things became easy.
Halfway through the migration, someone got our effort reviewed by a principal engineer who told me "it wasn't SOLID", and my contract wasn't renewed. It didn't dishearten me.
Software design is meant to make change easier and proudly adding abstractions can be a bad thing.