So every feature/change only needs one engineer.
Primarily, this philosophy encourages you to maintain a strict connection between needed use cases, and the code that enables them. Once that connection is severed, your code expands beyond the required use cases, and you end having to maintain support for phantom/theoretical use cases.
Instead of just implementing ACLs/permissions on existing APIs you have to develop and maintain two APIs, one for the customers (that will get outdated with lot of missing features and be less battle-tested) and one “real API”.
Our public API is fit for the use-cases of our big customers that want deep integrations, and our frontend’s API is able to handle our specific workflows.
We have a bunch of shared backend code, but the endpoints that our API customers use are very specific. Sometimes we build custom endpoints for a single customer which would be useless for our frontend/services. Keeping them separate allows them to gain needed functionality without trying to bolt it on to existing internal APIs, and cluttering it all into one messy blob.
Of course our internal API is a mess in a bunch of other ways but at least we don’t have to deal with the public parts making it even messier
Mobile app: this is addressed in the article.
Naming: Usually most naming for different parts of pages is handled by the design(er), but even if not, once you have an approximate naming convention for parts of the site, naming becomes easy. And the stakes for these names aren't that high, because you are only scoping everything down to a single page, so global consistency and precision in naming is not that imporant. It just needs to be enough for someone to understand what it most likely refers to on this page.
Every API call was so form fit to the UI that nearly every UI tweak needed frontend and backend changes.
It didn’t need to be this way (there was a lack of engineering skill and leadership), but even past the surface level much of the backend code beyond the API interface also became form fit to the specific use case meaning many times seemingly simple changes required near total reimplementation of all of the business logic and other underlying code.
End of the day, showing the same data or exposing the same flows elsewhere in the application required nearly a total reimplementation on the backend. This created an explosion in size of the codebase and complexity.
This whole idea seems to basically be going back to RPC-based APIs and all the drawbacks that led to everyone kind of collectively agreeing REST was better (even if most people only sort of half implement it).
Our approach, staring down a roadmap of projects that basically involved exposing the same functionality over and over with different pictures in front of it was to steer _hard_ the other direction.
We sat down and put together an API specification that laid out the general shape of an API. Not the specific resources, but for any given collection/resource how it should be exposed. We found common “tough” use cases and figured out up front how to expose those RESTfully and documented the patterns to maintain consistency. We put down a framework in code to make compliance with the spec the “easy” path for most cases.
Then we approached the rest of the design from an API perspective, not a frontend or backend perspective. For any given flow/operation/etc, what would a good interface look like for this. (This is very similar to figuring out what the interface for a module/class/etc should look like, given its responsibility, before writing it.) Then we built it.
The end result has been that more and more frequently requests and even entirely new projects require _no_ backend involvement. Often the FE team doesn’t even need to ask any questions—given any endpoint that exposes a collection of resources, they know _exactly_ how to work with it because they’re all the same.
When new business rules or requirements come in, we no longer need to apply them in 5 or more different places across two codebases. And we’ve still maintained a good separation of frontend and backend duties to the point our FE team is busy but kind of bored—they’re only really concerned with the view/display logic at this point.
Company history is largely lost to the mists of time, but the API routes were all prefixed `/v5/`. I can only imagine how much further along the company would be if they’d spent the last decade building on top of a foundation instead of rebuilding the foundation every two years as they have been with the RPC model.
Having to change backend code is not necessarily a bad thing. Just don't structure your code as two seperate codebases built by two different teams.
I'm trying to understand how this is possible. Couldn't the backend logic that contributes this response fragment in one endpoint be extracted and reused to contribute the same response fragment in another endpoint?
Was backend not modularized and reused?
The way I understand your approach, you ended up building and maintaining CRUD resources similarly to how they would exist in a naive public API, but the difference is that you spent extra time to build more meaningful, rich, business-level resources than serving nearly raw database records.
This is probably a good compromise that isn't disagreeing with the article as much as you seem to be implying. The difference between your approach and the article's approach is a small side-step. You either let the front-end fetch your rich business-level resources separately, or you package them into pages and serve them in a nice single package. Or you can build pages as per the article, and extract these rich resources that you display in those pages into separate endpoints for some additional flexibility. Either way, you end up with rich use-case driven resources, packaged differently.