maestros

Structure by Type vs Feature

20 November, 2021 • 7 min read

Making decisions as a software engineer when structuring an application can get quite tricky sometimes.

Especially when there are so many different tools and frameworks out there with their own opinions on how an application's structure should be.

It's time to make our codebase folder structure tell its story no matter what framework or language we are using.

In this post, we will examine different approaches and practices regarding folder structure so that the next time we are going to introduce a new directory or file, there will be a rationale behind our decision.

Structure by Type

The first one to examine is structure by Type. An application following this pattern should normally look as follows:

app/
├── controllers/
│   └── auth.js
├── models/
│   └── auth.js
├── validators/
│   └── auth.js
├── index.js
└── package.json

What do we have here ?

At first, we can't tell for sure whether this is a frontend or backend application.

But what we can say is that it has an auth feature implemented consisting of its model, a controller, and a validator.

If we were to write down a list of pros and cons for this approach, we would end up with the following:

  • Ensures a more flat folder structure;
  • It can get quite tricky to find out which files are used by which features;
  • Lack of modularity; Each directory contains items that usually aren't relative to each other;
  • Might lead to wrong abstractions;
  • It might not be easy for new team members to find their way into the codebase.

Structure by Feature

Next up, let's try to structure the previous example following the by-Feature approach.

app/
├── auth/
│   ├── controller.js
│   ├── model.js
│   └── validator.js
├── index.js
└── package.json

This time, we managed to place the auth feature in a more prominent position of the high-level directory structure, making it a first-class citizen in our application.

The basic characteristics of this pattern are:

  • The folder structure itself communicates the application features;
  • Sometimes, a lot of judgment needs to get applied on where to draw the boundaries and determine how to organize the feature exactly;
  • There is the risk to end up with too much nesting; In such cases, a refactor and extraction of a sub-feature might be required.

Which is the right one to pick ?

Unfortunately, there is no right or wrong here.

I would say it is a matter of preference, nonetheless one of them scales better.

But to consider that we are working in a scalable codebase, let's see which are the criteria we should look for:

  • Does our structure enable our team to develop features in parallel ?
  • Is refactor or moving things around and dropping legacy code pretty straightforward tasks ?
  • Can we write and maintain our tests in a not painful way ?

Based on what I've seen, I can tell you that structure by feature performs better when it comes to scaling.

But what if I told you there is a third option ?

Apart from the aforementioned approaches, there is also the Hybrid structure.

Let's pick up another example and see how it looks like:

app/
├── screens/
│   └── Login/
│       ├── hooks
│       ├── store
│       ├── LoginForm.tsx
│       └── index.tsx
├── graphql
├── index.ts
└── package.json

This time, there is a mix of type and feature directories in our folder structure.

  • screens and hooks are the type ones;
  • while Login, store and graphql are the features (or sub-features).

If I were to define what is a Hybrid structure:

It is based on top of the structure by Feature approach, but it enhances code navigation by grouping items of the same Type within a feature directory.

Why does Hybrid enhances code navigation ?

Because the folder structure tells a story !

And what's the story behind the last example ?

  • Most probably, it is a frontend application (because we have screens);
  • It has a Login screen, so we have an application for authenticated users;
  • Login screen includes a form component with its store (so in there we expect to find the login flow implementation);
  • This application consumes a graphql endpoint.

Which one is my favorite ?

Countless have been the hours of me trying to decide what structure to choose and why. I have tried all of them over the past years, both in small and large applications.

Down the line, I have come up to the conclusion that the Hybrid structure is the right one for me.

How does Hybrid apply in common use-cases ?

We've already seen a frontend application setup with Hybrid structure; the one with the Login screen.

To give you some further examples, let's take a closer look into another two common use-cases. A monorepo and a gateway package.

Monorepo

What is a Monorepo ?

A monorepo is a codebase that holds multiple packages, and usually, these packages have a relationship between them.

Here is an example of such a codebase:

monorepo/
├── apps/
│   ├── dashboard-gateway
│   ├── dashboard-types
│   ├── dashboard-ui
│   ├── shop-e2e
│   ├── shop-gateway
│   ├── shop-types
│   └── shop-ui
├── libs/
│   └── design-system/
│       ├── components
│       └── theme
├── package.json
└── README.md

Ok, what is the story here ?

  • It is readily apparent, that this monorepo includes two applications, the dashboard and the shop;
  • Each application consists of a gateway, the types and the ui parts;
  • The shop additionally has an e2e test suite as well;
  • Last but no least, this monorepo has a design-system library which we can safely assume that it implements shareable components for the other two applications.

Gateway

What is a Gateway ?

A gateway, can also be referred to as middleware or backend-for-frontend. It usually is a thin layer that connects a frontend application with some internal or even third-party services.

Here is an example of a package like this:

gateway/
├── graphql/
│   ├── login/
│   │   ├── resolver.js
│   │   └── schema.gql
│   └── index.js
├── libs/
│   └── api.client.js
├── services/
│   ├── login.js
│   └── login.test.js
├── package.json
├── README.md
└── server.js

Now, it's time to read the story behind these folders.

  • First of all, based on the presence of the graphql directory, it is evident that to consume this gateway we'll eventually do it via graphql;
  • Then, opening the libs folder it is clear that this gateway connects with at least one api;
  • Lastly, the way services folder is structured and the fact that we can distinct some test files in there, it makes us think that one could find there the main flows that this application implements; and in this case this is the login flow.

Important takeaways

  1. There is no right or wrong approach; It is expected to try all different options and refactor on-the-go if needed;

  2. Each application has its own business domain and specific needs, so it is not always so clear how to split the features from the very beginning. However, the rule of thumb is to try and avoid premature optimizations as much as possible, and wait to gain some knowledge before we start introducing new features;

  3. Don't overthink it. Just don't. As React docs wisely state, don't spend more than five minutes on choosing the file structure;

  4. High-level directory structure should not tell the framework. It is great to use magic CLIs that can quickly scaffold an application and enable us to rapidly start development, but these tools are quite opinionated sometimes. The folder structure is not about the tools or frameworks, it's about the intent. At the end of the day our application should tell what's the story beneath.

Finale

If you made it at this point, thank you so much for reading this article.

I know that some topics should be further explained than just some bullets, but then this post would be as long as writing a book.

Nevertheless, I hope that you got an idea of the statements I tried to make behind these paragraphs and now that you read this you are already thinking how and why you are structuring your apps in the way you do, and what you could probably do different to get their folder structure to the next level.

Thanks for reading ! 🤘