DEV Community

Cover image for Decomposing a project using Nx - Part 2
Chris Trześniewski for This Dot

Posted on • Edited on • Originally published at labs.thisdot.co

Decomposing a project using Nx - Part 2

Large projects come with a set of challenges that we need to remember in order to keep our codebases clean and maintainable. In the previous article, we talked about the horizontal decomposition strategy, and how it can help us manage our application code better. In this article, I would like to focus on the second strategy for splitting the application code - vertical decomposition.

Vertical decomposition

The more the application grows, the more it becomes important to create, and keep boundaries between certain sections of the application codebase. This is where the concept of vertical decomposition comes in. In most large-scale applications, we should be able to distinguish certain areas that concern different parts of the business value or different parts of user interaction. Let's use the slightly expanded version of the application used in the previous article. In addition to the liking and disliking functionality for photos, we can now see and edit the user's profile. You can find the relevant code on my GitHub repository.

As in most cases, the interaction with the user profile here can be considered as a completely separate part of the application. This gives us the clue that this part of the codebase can also be separate. The distinction between modules that concern different scopes of the application is what I call a vertical decomposition. This creates a second axis on which we can split the codebase to minimize the concern that each part of the application needs to be aware of. We can imagine that, if the example application were to grow, we could create separate modules for them. E.g:

  • photos - photos related features
  • user - user profile feature
  • chat - chatting between users feature

In the aforementioned example, we can see 3 possible parts of the application that don't have very strong dependencies between each other. Separating them upfront will ensure that we don't end up with too many entangled features. This requires more conceptual work in the beginning, but definitely pays off as the application grows, becomes more complex, and requires additional features to be implemented.

Using Nx to implement those boundaries

With Nx, and the CLI it comes with, I do recommend creating separate libraries within the monorepo to emphasize the boundaries between modules of the application. In the previous article, I introduced the concept of tags used by Nx to enforce boundaries between different types of libraries. We can use this same set of tools to create the vertical decomposition as well. It is a good practice to create a common prefix for tags that concern the same axis of decomposition. In the case of vertical splitting, I suggest using e.g. scope or domain prefixes. By applying this prefix to the modules defined above, we can create the following tags:

  • scope:photos
  • scope:user
  • scope:chat

or

  • domain:photos
  • domain:user
  • domain:chat

Similarly to the horizontal type: tags we can not assign the tags defined above to the libraries we've created for specific submodules of the application:

  "projects": {
    // scope:photo
    "photo-api-model": { "tags": ["type:api-model", "scope:photo"] },
    "photo-data-access": { "tags": ["type:data-access", "scope:photo"] },
    "photo-feature-list": { "tags": ["type:feature", "scope:photo"] },
    "photo-model": { "tags": ["type:model", "scope:photo"] },
    "photo-ui": { "tags": ["type:ui", "scope:photo"] },
    "photo-api": { "tags": ["type:be", "scope:photo"] },
    "photo-fe": { "tags": ["type:app", "scope:photo"] },

    // scope:user 
    "user-feature-profile": { "tags": ["type:feature", "scope:user"] },
    "user-ui": { "tags": ["type:ui", "scope:user"] }
  }
Enter fullscreen mode Exit fullscreen mode

nx.json

And also the boundaries between scopes can be enforced using ESLint or TSLint rules.

      "rules": {
        "@nrwl/nx/enforce-module-boundaries": [
          "error",
          {
            "enforceBuildableLibDependency": true,
            "allow": [],
            "depConstraints": [
              /* type related boundaries */
              {
                "sourceTag": "scope:photo",
                "onlyDependOnLibsWithTags": ["scope:photo"]
              },
              {
                "sourceTag": "scope:user",
                "onlyDependOnLibsWithTags": ["scope:user"]
              }
            ]
          }
        ]
Enter fullscreen mode Exit fullscreen mode

.eslintrc.json

I recommend limiting access to only the same scope as a starting point, and enabling access to a different scope only when it is actually necessary. This way we are forced to stop and consider the connection we are about to create, and therefore we can take some time to decide whether that's the best approach. It can lead us to finding and extracting a separate scope that can be used by both current scopes.

To verify that the boundaries between libraries are not violated, the following command can be run:

nx run-many --target=lint --all
Enter fullscreen mode Exit fullscreen mode

Of course, the CI process should be set up to make sure that as the codebase evolves the constraints are still met.

Conclusion

As I have shown in the above sections, the vertical decomposition can greatly benefit the maintainability of the application code. It is especially useful when working with large codebases as they are the ones that probably contain multiple scopes/domains that can be extracted and separated. However, I encourage you to try this approach even on a smaller project as it will be much easier to grasp on a smaller scale. With Nx tools, it is very easy to set up the boundaries between application scopes, and makes sure that those constraints are kept as the application grows.

If you want to read more about the architecture in an Nx monorepo, I recommend the following articles:

In case you have any questions, you can always tweet or DM me @ktrz. I'm always happy to help!


This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit thisdotlabs.com.

This Dot Media is focused on creating an inclusive and educational web for all. We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit thisdot.co.

Top comments (0)