DEV Community

Cover image for The beauty of modularity: a discipline of looking, not building
Massimo Moffa
Massimo Moffa

Posted on

The beauty of modularity: a discipline of looking, not building

Every developer knows it: divide et impera, simplify and execute. There's nothing more fascinating, in computer science, than the ability to break down a complex task into simpler, more intuitive ones — it's the foundation on which every problem, every program, every line of code is built.

Starting from this observation, I want to try to tell what fascinates me about making things modular and composing them to build something bigger. I try to apply this principle in every project I work on, so I can reuse what I write as often as possible: I can't stand throwaway code, written to run in one specific case and then die. There are contexts where it's unavoidable, but where it can be avoided I think it's worth doing.

What do I mean by "modular"?

When I say something is modular, I don't just mean it's "broken into pieces." I mean it's made of autonomous units that can live and change without dragging the rest along.

For me, a module always has three traits:

  • Clear boundaries: it's clear what's inside and what's outside, and what it's responsible for. This reduces ambiguity and chaos ("where does this responsibility end?").
  • Explicit interface: it talks to the outside through a few declared access points. In software these can be APIs, ports, events, props, CLIs, environment variables, input and output files. In real life they can be procedures, checklists, templates.
  • Substitutability and reuse: if you keep the interface stable, you can change the implementation or reuse the same piece across contexts without rewriting from scratch.

In practice, modularity for me is a strategy to lower the cost of change over time: if tomorrow I have to update a piece, I want to be able to do it without breaking or rewriting everything else.

Modular bricks (interlocking) – close-up

Modular bricks (interlocking) – close-up

Modularity at its best (in my opinion)

When I think about modularity in the digital world, a few examples come to mind that, more than others, manage to embody its spirit: not just "breaking into pieces," but building blocks with clear boundaries, clean interfaces, and a life of their own — pieces you can combine, swap, or reuse without having to start over.

The examples I picked come from very different contexts — infrastructure, frontend, and personal productivity — and that, in my view, is exactly the point: modularity isn't a feature of a language or a framework, it's a way of thinking that shows up at every level of the stack. In each of these cases I like to look at two things:

  • what is being made modular (the process, the component, the unit of content);
  • how it's being made modular, meaning which "boundaries" actually make that piece independent.

Docker

I like to picture Docker containers as hot-swap cards: closed, self-contained objects you plug in or pull out without shutting the system down. Each container carries its own code, runtime, libraries, and configuration, and exposes itself only through a clean interface: ports, volumes, environment variables.

This image, I think, captures Docker's modularity well:

  • Clear boundaries: what happens inside stays inside, which kills off most of the "works on my machine" pain.
  • Substitutability: you can swap a container (even a database one) without touching the rest, because the interface stays the same.
  • Composability and reproducibility: with docker compose you assemble multiple "cards" and get the same system on a laptop, across a team, and in production.

The most interesting part is the mental shift: you don't "fix" the server anymore, you replace components like slotting in a card. The metaphor has its limits, and it's worth exploring them because they clarify what modularity can and can't promise.

But the isolation is less total than the metaphor suggests: containers share the host kernel, and portability is paid for in bloated images, slow builds, and a security surface you still have to watch.

Di Software: Docker, Inc.Screenshot: VulcanSphere - Self-taken; derivative work, Apache License 2.0, https://commons.wikimedia.org/w/index.php?curid=182060522

Di Software: Docker, Inc.Screenshot: VulcanSphere - Self-taken; derivative work, Apache License 2.0, https://commons.wikimedia.org/w/index.php?curid=182060522

Module Federation

If Docker makes infrastructure modular, Module Federation brings the same spirit to the frontend: born with Webpack 5 and now available in Vite too via plugins, it lets multiple applications (even from different teams) expose and consume modules at runtime, instead of compiling one monolithic bundle. A stable shell loads pieces (components, pages, libraries) published elsewhere on the fly and composes them into a single experience.

My mental image is an app built from grafts: each remote is a deployable module with clear boundaries and an explicit interface.

  • Real release boundaries: each remote has its own build, pipeline, and version. Updating a piece doesn't mean redeploying the whole shell.
  • Reuse without duplication: a module lives in one place and is consumed wherever it's needed, cutting copies and drift across projects.
  • Controlled shared dependencies: common libraries (e.g., React, the design system, utilities) are negotiated between host and remote.

What's interesting is that modularity here isn't "free": it shifts decisions and responsibilities onto boundary design, failure handling, and performance. It's exactly that friction that makes you understand when runtime modularity really pays off, and when it just adds complexity.

But the price is sneaky: bugs move from build-time to runtime, version mismatches between host and remote only surface in production, and every network failure becomes a visible failure of the app.

Notion

One last shift in level: from infrastructure to frontend, now we move up to the user-facing tool. Notion is probably the example I live closest to, and it's literally the tool I'm using to write this post. The thing that strikes me most is that the same environment I'm using to put these lines together is also the one I use to run my company, build procedures, collect ideas, and organize projects: one place, many different uses, all built from the same little bricks.

My mental image is a set of composable bricks: blocks, pages, databases, and views all belong to the same family and offer clean contact surfaces to the other pieces.

  • Clear boundaries for each block: every element (paragraph, toggle, table, embed) is its own unit — movable, duplicable, nestable, without breaking the context it lives in.
  • Reuse and composition: a database can show up on multiple pages as different views, a page can be linked, embedded, or turned into a template, and the same schema works for both a blog post and a company procedure.
  • Same tool, different contexts: from blog posts to CRM, from meeting notes to a lab HACCP, the content changes but the mental model doesn't, and that lowers the cost of switching between activities.

The interesting point is exactly this: modularity doesn't show up in the single block, it shows up in the fact that the same "language" supports wildly different uses without ever feeling forced. It's proof that, when the boundaries are well thought out, a few base pieces are enough to cover hugely different scenarios.

But the same freedom that makes it powerful is also its Achilles' heel: without strong conventions you end up reorganizing the workspace more than actually using it, and the "right" module always seems to be one step further away.

Notion desktop (https://www.notion.com/help/notion-for-desktop)

Notion desktop (https://www.notion.com/help/notion-for-desktop)

Modularity in "real" life

You might not expect it, but modularity isn't a computer science invention: the physical and organizational world is full of remarkable examples, often so woven into our daily lives that they go almost unnoticed. Here are a few that, in my opinion, tell the same story as Docker containers and Notion blocks, just off-screen.

  • ISO shipping containers: same box, same dimensions, same hooks for decades. The contents change (fruit, cars, clothes) but not how they're moved, and around that single interface an entire global logistics system has been rebuilt.
  • LEGO bricks: a handful of piece types, an interface (the studs on top and bottom) standardized for decades, and a composition range that goes from a spaceship to the Eiffel Tower. The same piece from 1980 still snaps onto a set released yesterday.
  • IKEA furniture (e.g., Kallax): same cubes, same dimensions, same holes. The interface here is literally physical: the standard 32 mm hole and the cube's dimensions, constant across models and accessories. The same cube becomes a bookshelf, a room divider, a TV stand, or a vinyl holder depending on context, without the piece itself really changing.
  • Automotive platforms (e.g., Volkswagen's MQB): the same chassis and the same electrical architecture support the Golf, Audi A3, Skoda Octavia, and SEAT Leon. The module is the mechanical base, the interface is how the bodywork and engines attach to it.
  • The mother sauces of classical cooking: five bases (béchamel, velouté, espagnole, tomato, hollandaise) from which hundreds of derivative sauces come. Change one ingredient "at the interface" and you get a different dish, without starting from scratch each time.

The common thread is always the same: clear boundaries, a stable interface, and pieces you can swap without bringing down the rest. Once you start seeing it that way, modularity stops being a code question and becomes a way of looking at the world.

ISO shipping containers in a port

ISO shipping containers in a port

How do you discover a module?

The thing I like most, when I'm working on a system or a company, is that a module isn't invented — it's discovered. It's already there, inside the problem, hidden under noise, repetitions, and habits. What we call "designing a module" is actually an act of observation: it means noticing a boundary that has been there for a long time and finally giving it a name.

I like to think of it as sculpture: the marble block already contains the statue, the sculptor doesn't create — they remove what's in excess until the shape comes out on its own. In the same way, in a system or a process there are already natural fault lines — my job is just to recognize them before forcing arbitrary divisions.

To spot them, I usually rely on a few recurring signals:

  • Repetition: if I'm writing or doing the same thing for the third time with small variations, there's a module underneath asking to come out. My hands are already using it, it just needs to be made explicit.
  • Things that change together: what changes for the same reasons and on the same occasions tends to belong together; what changes for different reasons wants to be separate. It's the golden rule for figuring out where to cut.
  • The pain of change: if changing one small thing forces me to touch ten unrelated places, the system is telling me the boundary was drawn in the wrong spot — or hasn't been drawn at all yet.

Seen this way, finding a module looks more like archaeology than architecture: you dig, you brush, you wait for the shape to come out. And the best moment is when you realize that module had been there all along, and you just had the patience to find it.

This has happened to me concretely a couple of times. I work for a company that does environmental monitoring, and one of the things I've built is Square, our platform: the grids used to host charts, maps, and rankings written case by case, and at some point the repetition was so obvious that I extracted standard widgets — the same ones that today live indifferently inside grids, in reports, or in other contexts. Same story for a GIS-like page: the information shown on the map found its natural form as composable layers, toggleable and stackable as needed, without rewriting the map every time.

My attempt at building a "module"

I tried to apply this same logic outside of code too, with Plop, the dissolvable cube I made with some of my colleagues: the cube always stays the same, what changes is what you put inside (flavors, active ingredients), and even the liquid base you dissolve it into can vary — usually water, but milk works too if you want. Once the module is discovered, every new variant costs much less than inventing a product from scratch.

Beware the false module!

Having said all this, the opposite risk is just around the corner: once you get comfortable with modularity, the temptation is to see modules everywhere — and that's not always a good thing. The truth is that not everything has to be a module, and forcing it often makes the system worse, not better.

The two signals I watch most carefully are these:

  • The module used only once: if I've just extracted a "reusable" piece but the second use case doesn't exist and I struggle to really picture it, I'm probably paying the cost of abstraction (interface, configuration, documentation) without the benefit. It's the classic module born from a hypothesis, not from observation — almost always better to leave it inline until repetition shows up on its own.
  • The module that does too much: when the interface bloats with parameters, the docs start filling with "if… then…" clauses, and the internal logic gets hard to explain in a single sentence, I'm usually not looking at a complex module, but at multiple overlapping modules asking to be separated. It's the same signal as "the pain of change," but seen from the inside: if changing one behavior forces me to understand all the rest, the boundaries are in the wrong place.

The rule I try to keep in mind is simple: modularity has to reduce cognitive cost, not add to it. When I feel a module is making my life harder instead of simpler, it's usually not an implementation problem — it's a sign that that module, the way I drew it, didn't really exist.

What I take home

In the end, modularity for me is mostly this: a strategy to reduce cognitive cost and the cost of change over time. It's not there to make a system look more elegant — it's there to make it cost less, in attention today and effort tomorrow.

But what really stays with me, after all of this, isn't a technique — it's a habit of looking. Once you've seen the ISO container behind global logistics, the 32 mm hole behind the Kallax, and the five mother sauces behind an entire menu, you stop looking at the world as a single block and start looking for the fault lines — not to break everything, but to figure out where you can actually change something without having to rebuild the rest.

In that sense, modularity is less a property of things and more a way of reading complexity: digging until you see the boundaries that were already there, giving them a name, and then having the discipline to leave alone the pieces that, modules, weren't really. It's the price — and the gift — of looking at the world in blocks.

References

  1. Divide et impera (wikipedia): https://en.wikipedia.org/wiki/Divide_and_impera
  2. Modularity (wikipedia): https://en.wikipedia.org/wiki/Modularity
  3. Coupling and cohesion (wikipedia): https://en.wikipedia.org/wiki/Coupling_(computer_programming)
  4. Separation of concerns (wikipedia): https://en.wikipedia.org/wiki/Separation_of_concerns
  5. Interface (computing) (wikipedia): https://en.wikipedia.org/wiki/Interface_(computing)
  6. Docker overview: https://docs.docker.com/get-started/docker-overview/
  7. Docker Compose overview: https://docs.docker.com/compose/
  8. Containerization (wikipedia): https://en.wikipedia.org/wiki/Containerization_(computing)
  9. Linux kernel (wikipedia): https://en.wikipedia.org/wiki/Linux_kernel
  10. Module Federation (Webpack docs): https://webpack.js.org/concepts/module-federation/
  11. Webpack 5 release (Webpack blog): https://webpack.js.org/blog/2020-10-10-webpack-5-release/
  12. Micro-frontends (martinfowler.com): https://martinfowler.com/articles/micro-frontends.html
  13. Notion (product): https://www.notion.so/product
  14. Notion API (as an example of explicit interfaces): https://developers.notion.com/
  15. ISO container / Intermodal container (wikipedia): https://en.wikipedia.org/wiki/Intermodal_container
  16. LEGO brick (wikipedia): https://en.wikipedia.org/wiki/Lego
  17. IKEA KALLAX series: https://www.ikea.com/us/en/cat/kallax-series-27534/
  18. 32 mm system (wikipedia): https://en.wikipedia.org/wiki/32_mm_cabinetmaking_system
  19. Volkswagen Group MQB platform (wikipedia): https://en.wikipedia.org/wiki/Volkswagen_Group_MQB_platform
  20. Mother sauces (wikipedia): https://en.wikipedia.org/wiki/Mother_sauce
  21. Iodo (Plop): https://iodo.tech
  22. Sense Square (Square): https://sensesquare.eu

Top comments (1)

Collapse
 
bryan_maclee profile image
Bryan MacLee

It's a good article. Well formed, and makes the point that devs have settled on modularity as one of the pillars of advancing programming.

My question is. What are some concrete examples of your version of modularity.

The abstract nature of the article makes it very hard to disagree with the points made. But, also makes it hard to agree with any specific point.

This isn't meant to be criticism. I am genuinely curios about any novel approaches that you might have to "modularity"