DEV Community

Cover image for You're slicing your architecture wrong!
Basti Ortiz
Basti Ortiz

Posted on

You're slicing your architecture wrong!

For the longest time, the "separation of concerns" has been the ultimate guiding principle of software engineering. We thus structure our codebases accordingly:

  • Grouping related files by responsibility;
  • Partitioning logic by technical layers;
  • And sometimes even isolating by programming language.1

Popular architectural patterns such as the Model-View-Controller (MVC) have applied and codified this principle to the point of dogma. Universities, bootcamps, and courses everywhere teach and perpetuate the notion that the MVC architecture is the be-all and end-all of architectural patterns.

To be fair, the MVC architecture is indeed often the correct mental model for fathoming any CRUD-heavy application (i.e., virtually 99.9% of web applications2) because it formalizes the flow of data from the source (i.e., the "model") to the interface (i.e., the "view") as well as the mutation and actuation on said data (i.e., the "controller"). It is quite literally the essence of CRUD.

But, is MVC also the correct framework for structuring codebases?

Does MVC supposedly3 being the correct mental model necessarily mean we should structure our directories as such?

In this article, we'll explore a better way to structure a codebase using a vertically sliced architecture instead of the classic models/, views/, and controllers/ layout.

Horizontally Sliced Architectures

The MVC architecture is an example of a horizontally sliced architecture. In a horizontally sliced architecture, the separation of concerns is sliced according to technical layers.

A diagram of a horizontally sliced three-layer architecture featuring the MVC architecture

Note that the layers needn't be exclusively "models", "views", and "controllers". In a full-stack web application, you might find yourself slicing by "components", "endpoints", and "databases" instead.

models/
├── feature-a.ts
├── feature-b.ts
└── feature-c.ts
controllers/
├── feature-a.ts
├── feature-b.ts
└── feature-c.ts
views/
├── feature-a.tsx
├── feature-b.tsx
└── feature-c.tsx
Enter fullscreen mode Exit fullscreen mode

So, what's wrong with this project structure?

  • To add a new feature, you must jump between multiple directories—namely models/feature/*, controllers/feature/*, and views/feature/*. Compounded over time, that's a lot of context-switching, mental overhead, and navigational ceremony!
  • It's often not immediately apparent which modules are used by a particular feature. As everything is horizontally sliced, each module can theoretically import any module from the layer below. This makes it difficult to track and assess the impact of code changes.
  • Consequently, modules across layers within the scope of a single feature tend to exhibit high coupling and low-to-medium cohesion.
  • "Uh... which files go where again?" – Probably you for the past 10 minutes, thinking about where to put new files for a feature.

Vertically Sliced Architectures

Now let's turn MVC on its side—literally! In a vertically sliced architecture, the separation of concerns is sliced according to features. The core idea being: each feature module must encapsulate only its own end-to-end logic and nothing else.

A diagram of a vertically sliced three-layer architecture where a feature module is superimposed on all layers of the tech stack

In practice, here is what a feature-driven project structure could look like:

features/
├── feature-a/
│   ├── model.ts
│   ├── controller.ts
│   └── view.tsx
├── feature-b/
│   ├── model.ts
│   ├── controller.ts
│   └── view.tsx
└── feature-c/
    ├── model.ts
    ├── controller.ts
    └── view.tsx
Enter fullscreen mode Exit fullscreen mode

Shared logic across multiple features (e.g., database models, common utilities, etc.) may of course be refactored into shared modules elsewhere. What's important is that the feature-specific logic is self-contained and end-to-end.

Interestingly, though, the MVC patterns have not totally disappeared even in a vertically sliced architecture—as evidenced by the internal model.ts, controller.ts, and view.tsx files within a particular feature module. The slicing is different, yet the MVC-style separation of concerns remains in spirit.

So, what makes this better?

  • All the code related to a particular feature can be found within a single directory. No more back-and-forth between distant directories!4
  • The collocation of end-to-end logic lessens the cognitive load when developing features or debugging behaviors.5
  • By construction, a feature-driven architecture naturally leads to highly independent feature modules that exhibit low coupling (between vertical slices) and high cohesion (within vertical slices).

Example: React + Next.js

Nowadays, most full-stack web frameworks provide routing as a first-class feature. In Next.js, the src/app/ directory contains the entry points for each route. Specifically, the page.tsx file exports the component that should be rendered onto the page for that particular route.

Now let's imagine the page.tsx file as an orchestrator that only imports feature modules from src/features/. A natural conclusion is that a vertically sliced project structure entails partitioning the application logic as self-contained "feature components" that can be imported by any route—or any entry point in general.

src/
├── app/
│   ├── dashboard/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   └── page.tsx
├── features/
│   ├── create-order/
│   │   ├── index.tsx
│   │   ├── context.ts
│   │   ├── hooks.ts
│   │   └── actions.ts
│   └── login-form/
│       ├── index.tsx
│       ├── context.ts
│       ├── hooks.ts
│       └── actions.ts
├── database/
│   ├── index.ts
│   └── schema.ts
└── components/
    ├── card.tsx
    ├── button.tsx
    └── input.tsx
Enter fullscreen mode Exit fullscreen mode

Indeed, feature modules like create-order and login-form may contain feature-specific components, contexts, hooks, and server actions.

  • A feature-specific component may, for example, import from the shared src/components/ directory for common cards, buttons, and inputs.
  • Meanwhile, a feature-specific server action may invoke database queries from the shared src/database/ directory.

The key is to know when to extract shared logic (e.g., src/database/ and src/components/) and when to keep bespoke feature-specific logic isolated from the rest of the application.

Conclusion

At its core, the call to action is simple: simply turn MVC on its side.

Vertically sliced architectures set us up to write highly independent components that exhibit low coupling (between vertical slices) and high cohesion (within vertical slices). The result is a project structure that is considerably easier to read, understand, maintain, and extend.

Further Reading


  1. For example, in web development, it is quite common to have a dedicated css/ directory for stylesheets and a js/ directory for scripts. 

  2. Citation needed. 😅 

  3. For the sake of this article, we assume this to be generally true: that MVC is indeed the correct mental model for fathoming CRUD-heavy applications. 

  4. Admittedly, back-and-forth between sub-directories is nevertheless inevitable. However, the navigation is at least contained within a single core directory now instead of jumping between distant directories, which is a win in my book. 

  5. I can only back this up with my own personal experience as well as anecdotal evidence from my friends. 

Top comments (24)

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

I've long been a proponent of this vertical slicing approach. When do you ever need to look at another feature's controller, view or model when working on a particular feature? The mental load is significantly greater, and onboarding new developers is much harder.

Great article!

Collapse
 
hilleer profile image
Daniel Hillmann

And if you do, it may be a hint you are on a questionable track/path

Collapse
 
scottfred profile image
@ScottFred

The Angular Team recently merged their Angular style Guide and now recommend a pattern similar to this where they recommend a feature based file organization. github.com/angular/angular/pull/60...

Collapse
 
xwero profile image
david duymelinck • Edited

You call it features I see modules, but whatever the name it has the same goal, breaking the code down in more manageable pieces.

Collapse
 
angelomandato profile image
Angelo Mandato

I also have maintained businesses with this concept of separate applications, or also referred to as modules, or better yet organizing independent applications that can work independently on their own. Amazon called this decoupled architecture. For business minded folks, avoiding the monolithic app at all levels is critical to being capable of easily pivoting as well as putting a cost on your assets/intellectual property.

Collapse
 
rockshandy profile image
Phil Eberhardt

I've been using this method myself for a bit and seemed to naturally happen when I moved from Backbone and Marionette many years ago to React. I never really thought about it as vertical slicing though, nice article.

Collapse
 
somedood profile image
Basti Ortiz

That's something I've realized within myself and my own circle of colleagues, too! It's like we all somehow independently converged on this pattern of vertically slicing our features. Given the independent validation, I think that makes it even more appealing.

Collapse
 
mike_c22a5f928ddac6144ec5 profile image
Mike

Embracing a vertically sliced architecture turns sprawling MVC layers into self-contained feature modules, reducing context-switching and boosting maintainability.

Collapse
 
nevodavid profile image
Nevo David

pretty cool seeing folks push for more feature-driven setups - tbh i've always gotten lost digging through layers. you ever feel like habits from old patterns get in the way of trying new stuff?

Collapse
 
nevodavid profile image
Nevo David

Yeah this nails the stuff I’ve struggled with in big projects. Keeping each feature in its own spot just keeps me sane.

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

love seeing thishonestly, searching all over for files always messes me up way more than it should. you ever wonder if most old habits in programming actually slow us down more than help?

Collapse
 
kwnaidoo profile image
Kevin Naidoo

Nice! HMVC is a solid way to organise large projects. I use Django for this, it has a concept of "projects" and "apps" which makes it easier when the framework supports it.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.