DEV Community

Cover image for Angular architecture matters: Monorepo
ng-conf
ng-conf

Posted on

Angular architecture matters: Monorepo

Sergio Ausin | ng-conf | Feb 2019

a meme. the image is Chuck Norris giving a thumbs up, the text reads

Whenever you start a new project, one of the first decisions we made is the structure of our code base. This is important because we do not want to have spaghetti code, features breaking when code changes... A few years ago, I worked in a project that comprises several apps with shared libraries. At first, everything was fine: apps were very small, few requirements; but every small app converts into a big one. The problem appeared when the first critical bug was reported. The typical steps to solve any bug were:

  • Identify the cause
  • Patch the affected code
  • Build a new version of the library. We used semantic versioning
  • Upload it to the private NPM repository
  • Update every app using the library
  • Verify that everything is working as intended (launch e2e tests, unit tests, etc.)
  • Finally, we released a new version of the apps

Obviously, if something went wrong we have to repeat some of these steps. This process may be a pain in the ass when you have to update several applications. On the other hand, you also have to be aware of the applications that are using the affected library. I wasted a lot of time upgrading libraries, so I decided to look for a better way to deal with this problem: the monorepository. The main advantages of this approach are:

  • Same library version for every app
  • Ease of maintenance: when you update a shared library, you update it for all apps.
  • No conflicts between versions

This approach is followed by Google and Facebook, among others. So, if you did not know it, you should give it a try! To help us with this task, the guys from Nrwl have created NX.

NX by Nrwl

NX is an extension for the @angular/cli that implements the monorepo-style development. It provides a set of code generators, libraries, linters…

For example, we can create an application with layered architecture: logic, user interface, etc. The picture below shows an example of layered architecture:

Image of a monorepo folder setup.

Monorepo folder structure

In the libs folder, there is a “three level” directory tree.

  • First level: the name of the apps. Inside this directory, there are the libs that our app will use
  • Second level: the use case/page directory
  • Third level: the “layer” directory. There are some typical libraries that I use: core for services, user-interface for components and a routing library which is lazy loaded by the app.

This architecture follows these rules:

  • A core library must not import a user interface library
  • A core library must not import a routes library
  • A user interface library must not import a routes library
  • A shared library must not import a non-shared library
  • A use case library must not import another use case library
  • A library must not import an app

To be consistent and make sure that we are following correctly the layered architecture, NX provides us a lint rule: “nx-enforce-module-boundaries”. To make it works, we have to set up some tags in every library. Those tags are set up in the nx.json file:



{
"npmScope": "ngconf",
"implicitDependencies": {
"angular.json": "",
"package.json": "
",
"tsconfig.json": "",
"tslint.json": "
",
"nx.json": "*"
},
"projects": {
"web-app": {
"tags": []
},
"web-app-e2e": {
"tags": []
},
"web-app-home-core": {
"tags": [
"scope:home",
"type:core"
]
},
"web-app-home-user-interface": {
"tags": [
"scope:home",
"type:user-interface"
]
},
"web-app-home-routes": {
"tags": [
"scope:home",
"type:routes"
]
},
"web-app-workshops-core": {
"tags": [
"scope:workshops",
"type:core"
]
}
}
}

Enter fullscreen mode Exit fullscreen mode



nx.json hosted by GitHub

For this example, I have used the tags:

  • Scope: for the use case
  • Type: for the layer

Once we have set up correctly our nx.json file, we have to add the “nx-enforce-module-bundaries” rule to our tslint.json file.



{
...
"rules": {
... // other tslint rules
"nx-enforce-module-boundaries": [
true,
{
"allow": [],
"depConstraints": [
{
"sourceTag": "scope:shared",
"onlyDependOnLibsWithTags": [
"scope:shared"
]
},
{
"sourceTag": "scope:home",
"onlyDependOnLibsWithTags": [
"scope:home",
"scope:shared"
]
},
{
"sourceTag": "type:core",
"onlyDependOnLibsWithTags": [
"type:core"
]
},
{
"sourceTag": "type: user-interface",
"onlyDependOnLibsWithTags": [
"type:core"
]
}
]
}
]
}
}

Enter fullscreen mode Exit fullscreen mode



tslint.json hosted by GitHub

With this simple configuration we achieve the following goals:

  • Home libraries can import home and shared libs
  • Core libraries can only import core libraries
  • user-interface libraries can only import core libraries

If you try to import a user-interface library from a core lib you will see the following error message:

A project tagged with “type:core” can only depend on libs tagged with “type:core”

To sum up

NX enforces quality and consistency when we develop our apps. It gives us some good practices that we should follow when we are developing our apps!


EnterpriseNG is coming November 4th & 5th, 2021.

Come hear top community speakers, experts, leaders, and the Angular team present for 2 stacked days on everything you need to make the most of Angular in your enterprise applications.
Topics will be focused on the following four areas:
• Monorepos
• Micro frontends
• Performance & Scalability
• Maintainability & Quality
Learn more here >> https://enterprise.ng-conf.org/

Top comments (0)