Gravity of monoliths in feature-centered frontend projects
By Ivan Vokhmin

Gravity of monoliths in feature-centered frontend projects

During more than 10 years I had some pleasure of working with different projects with various codebases and code quality. From (micro)service oriented to big and old monoliths, that are required to be breaking apart into array of services (packages) to reduce cognitive complexity. Basically, most companies I worked with had "that huge thing" in one form or another. Either as main money maker or a separate case solver that can nobody can get rid of. It seems not to be a specific frontend/full stack web apps problem, that software becomes too big and hard to manage. But the problems there tend to escalate faster. I think mobile development (flutter, swift, java, kotlin etc.) tend to drift in same monolithic architecture direction. In this article I will explore the reasons why monolithic architectures tend to form and prosper, and why sometimes it is so hard to make things right even before they collapse under their own weight.

What is a monolithic architecture

Monolithic architecture, a traditional approach in system design, actually contains all components of an application into a single codebase. This unified structure simplifies development and deployment processes, offering ease of management and tight integration. (1) However, monoliths tend to accumulate complexity, making them less adaptable to changing requirements, and harder to maintain.

Formation of monolithic architecture

Microservices are pretty easy. All you need is a small API-exposing main function that maybe leverages some framework like express. Add dockerfile, service definition / IaC, drop pipelines and you are ready to go. Or even less than that if you leverage cloud functions like AWS lambda or azure/GCP function, where you don't care much about runtime and scaling (hello AWS SAM). But every time you run something like create-next-app for next hello-world webpage, a strange noise emits from notebook fans as countless modules are installed - it is the sound of pavement work for new monolith being created.

Now it's time to add new features into web app, and full-stack web frameworks (like next.js, nuxt.js or angular universal) are supercharged with built-in server that allows the app to do not only frontend, but backend-relevant tasks. And, sometimes you may start to notice patterns in how features are implemented.

For example, you may implement backend processing tasks in framework API handlers (like next API), instead of creating a separate service. When a deadline looms and features are expected to be shipped fast, it seems to be the good choice for following reasons:

  • Data types for both requester (web browser client) and processor (server API) are stored in one place - it is easy to create/test data transfers
  • You don't need service definitions/pipelines, as everything is in the project already
  • You can re-use code from the project, creating additional tight coupling

Or, you want to build a small component library, that may have its own tests and storybook. You may think of creating a separate npm package for it (or a separate monorepo folder), but this degrades dev experience as you need to mount components in main project properly (or wait for package to be released). Also it decreases feature shipment speed. So, with some pressure from business the delivery speed will be restored by merging component library with main project.

What is the common ground for those 2 cases? With feature delivery speed as key requirements, it is logical to implement all use cases/problem solutions right in full-stack project. Because modern fill-stack web apps can solve problems in multiple domains that were separated some time ago.

Everybody is happy. Features are shipped fast, project is thriving. Until one day...


Image from article (2), author Thomas Vilhena

With more and more features shipped fast, the size of the project and its complexity skyrockets. Suddenly, a new business requirement was dropped. Initially estimated in 2 weeks to implement, it took a month first, and then 2 months. Nobody is happy anymore, as the project has become too bulky and complex to extend and maintain. Business now realized that tech debt needs to be tackled.

Why "gravity" pulls monoliths back together

By the time the problem becomes obvious to everybody, it maybe already a little too late. While there are many guides on how to split up monolithic codebase, usually commitment the key success factor. In most of the cases, business is not ready to commit more than 2-3 weeks into solving monolith complexity problem. A quick "patch" solution will be preferred, when some parts are reaped apart into standalone services or separate applications. Not that it will decrease complexity too much, it will actually decrease the delivery speed, because the main monolith will continue to grow later, as more and more features will be smashed on top after maintenance time is spent. And now you will have a set of services around it, too. Good luck if developers who know context are still around... If not, new hires will be unable to do complex changes. And the monolith is complete, surrounded with some infrastructure and service-like leftovers from previous split attempts.

So, before you may end up in that situation:

  • Secure proper commitment once problem is identified
  • Don't allow solution to become too complex if personal turnover is high

And, more importantly:

  • Monoliths don't grow if there is constant commitment to understand and tackle technical debt (like complexity)


Sources

(1) https://www.geeksforgeeks.org/monolithic-architecture-system-design/

(2) https://thomasvilhena.com/2023/04/the-burden-of-complexity

要查看或添加评论,请登录

Ivan Vokhmin的更多文章

社区洞察

其他会员也浏览了