The Shell Library patterns with Nx and Monorepo Architectures

1 Like

So what do you think, fellow Angular developers? Have you used any of these patterns or something like them?

1 Like

Thank you for a great comparison of that both approaches.

What’s also for to mention that NX recommends keeping application as thin as it’s only possible. With their approach, we can quite easily realize that requirement what is not so easy with the Manfred approach. As we prepare our structure we strongly keep that requirement in mind what leads us to more problems even with such simple components as navigation.

Whereas that navigation mostly depends on information about the logged-in user, which are collected inside of Auth data-access lib. With the Manfred approach and keeping navigation component inside of the App we are a force to make our App depends on data-access lib what we strongly want to avoid but the other way to make single navigation to the entire App is to duplicate its usage in every shell library what leads us to violate the DRY (Don’t Repeat Yourself). On the other hand, we like the approach with the shell per entry point for each Bounded Context. Now we are thinking about creating some kind of hybrid of both approaches.

Do you try to extract as much code as possible from your App, does it make sense for you? If yes, where you keep components like navigation and its dependencies on the informations about the user?

1 Like

Thanks, Stefan, for your kind words and your interesting question.

When I’m designing my workspaces , I do try to refactor my code continuously. These refactor often imply to move code to the correct layer depending on the responsibility that it carries.

For me, the focus of keeping my libraries following a single responsibility and enforcing the restrictions of its layer is what allows me to keep my code clean and maintainable.

The same goes for your app; you have first to determine what is your app responsibility and be consequent with that decision.

Let’s say, for example, that we decide that our app responsibility is only to handle the platform-specific configurations (which I believe is a smart choice).

Then we could have a feature-shell library (Nrwl shell library) to orchestrate the top-level routes and other forRoot configurations (not platform-specific). Then we could use several Composite shell libraries or a Manfred Steyer shell library per Bounded Context to manage your bounded-context level routes.

Our feature-libraries modules also manage its internal feature-level routing, and they consume data from the data-access layer. It is their responsibility to allow or not internal navigation, depending on auth and other factors.

Of course, you should always choose what adjusts the better to your project.

I hope this help don’t hesitate to ask if you have any other question and thanks again for your interest.

2 Likes

I would extract layout components to a feature library. We did already come up with a more specialized shell library approach in the article: composite shell libraries. Feel free to customize to your needs. The additional logic that’s put into the app project in the Steyer shell library pattern compared to the feature shell library pattern by Nrwl is picking which bounded contexts to include in the app by importing their shell libraries instead of the feature shell library having the logic of defining the use cases available. If you have just one app, this is fine. If you have multiple apps, they would share the same routes and use cases when using a feature shell library. That’s very rare.

Thank your for your feedback and questions.

2 Likes

Hi there! Great article!

When I was reading your article I thought about Instagram, an example in which the web application has less functionality than the mobile application. I have some doubts about how you would solve the following scenario with your architecture.

Suppose the functionalities of each application were as follows:

  • In the web application you can access your photos and a configuration section where you can edit your profile.
  • In the mobile application you can access your photos, a configuration section and a chat.

In addition, let’s assume that the detail view of a photo varies slightly depending on the platform (in mobile there are more options).

Putting your architecture into practice I would create the following structure, for example:

apps/instagram-mobile
apps/instagram-web

libs/media/feature-browse-pics
libs/media/feature-pic-details
libs/media/mobile-feature-pic-details
libs/media/shell-media-web (entry point for: feature-browse-pics y feature-pic-details)
libs/media/shell-media-mobile (entry point for: feature-browse-pics y mobile-feature-pic-details)

libs/account/feature-change-pwd
libs/account/feature-user-details
libs/account/shell-media-web (entry point for: feature-change-pwd y feature-user-details)
libs/account/shell-media-mobile (entry point for: feature-change-pwd y feature-user-details)

libs/chat/feature-search-friend
libs/chat/feature-chat-view
libs/chat/shell-media-mobile (entry point for: feature-search-friend y feature-chat-view)

Until then all the feature-modules match pages of the application.

Well, each application should have a different navigation bar:

  • The web application will have two buttons: one to access the features of the “media”'s bound context and the other to access the features of the “account”'s bound context.
  • The mobile application will have 3 buttons: the previous two and an extra one to access the functions of the “chat”'s bound context.

Where would you put these navigation bars and how would you define them? That is, you would have a feature-module that was, for example, feature-navigation and inside a
component called, for example, navbar? In most cases in the navigation bar we would find the logic to do sign-in or sign-out, so let’s suppose for this example that the navbar is a
smart component that consumes the slice of the state that handles the authentication. In the end this navbar is a kind of glue between the app and the bounded contexts, so I never know where to place it.

3 Likes

Hi Marco!

I’m happy that you found our article useful.

Thanks for your thorough example and your excellent question!

It is a widespread scenario to have Layout components gluing components and functionalities from different concerns. Usually, these components are used across your app and follow a stable behavior.

Something I have done before to deal with this scenario is to create a feature-layout-library to compose these different Layout components. Here you would usually find a TopBarComponent, SideNavComponent, ContentComponent (this could contain your router-outlet), and the LayoutComponent that organizes the others.

The full path could be something like

libs/core/feature-your-app-name-layout
or
libs/core/your-app-name/feature-layout

In the above path core is the grouping folder that holds our cross-cutting libraries.

As you can see, new patterns start to arise from having this cross-cutting slice in our workspace, and more analysis is needed to discuss the different alternatives and their consequences.

I’ve mostly used the second folder structure for my cross-cutting concerns; that is, they are grouped by application. Also, notice that it is very different to how I manage the rest of my workspace where everything is organized in Bounded Contexts. However, the Layout and root navigation are specific to our application, so it makes sense to have that apart.

The feature-layout-library can be imported then by an Nrwl feature-shell-library (also specific to our application), as explained in the article and combined with the rest of application-wide configurations or directly in the application if you prefer that.

Then your layout components like your navigation bar can perform root-level navigation and import smart components provided by the other bounded contexts, like in your auth example.

That means that if you need a domain-specific functionality, that functionality should be exported from it’s Bounded Context implementation instead of having it implemented in your cross-cutting section.

Disclaimer: this is the way I’ll probably do it today based on my previous experience and expertise. You should always adapt to this kind of situation to your project, team, and understanding. I’ll probably do things differently in the future because we are ever-evolving thinking professionals.

Teaser: A new article is in process, and we will discuss some of the topics that your question brought up. Stay tuned, and don’t doubt to ask any other question and let us know what solution you end up implementing.

2 Likes

Well said, @nacho_vazquez

I would call the grouping folder shared rather than core.

You mention layout components such as top app bar, side navigation drawer and content component for router-outlet (never seen that before, interesting).

I’d design these as one-off presentational components (one per application). I might put them in a ui-layout workspace library. I’d then populate them with navigation items, menu items and other structures in a workspace feature library, for example feature-layout. This would speak to the example that @marcoinarrea proposes since we could have two layout feature libraries: one for web and one for mobile. They’d both share the layout UI library and the composite shell libraries could pick the one appropriate for the platform app project in question.

2 Likes

Thank you very much for your answers @nacho_vazquez and @LayZee.

I found the article very interesting because I am working with a very similar architecture, with the difference that in addition to the Bounded Contexts I always include two main libraries for each application, the nrwl feature-shell-library and a global data-access library. In the global data-access library I load all the services and parts of the NgRx store that need to be loaded from the start of the application. In the feature-shell-library I load all the initial configuration of the application, such as the main layout components you are commenting on and my global data-access library. Finally in my applications I only load the nrwl feature-shell-library. With these two libraries I can isolate the core of each application (so these libraries are always app-specific). My solution to the example I was raising,
taking into account your post and the answers you have given me, it would have the following form:

apps
  instagram-mobile   <-   import libs/instagram-mobile/shell
  instagram-web      <-   import libs/instagram-web/shell

libs
  media (DDD Bounded Context)
    feature-browse-pics
    feature-pic-details
    mobile-feature-pic-details
    web-feature-layout (specific layout for media section that should be imported by shell-media-web and probably uses presentational components from "common/ui/layout")
    mobile-feature-layout (specific layout for media section that should be imported by shell-media-mobile and probably uses presentational components from "common/ui/layout")
    shell-media-web (Composite Shell concept - Nacho Vázquez and LazyZee)
    shell-media-mobile

  account
    feature-change-pwd
    feature-user-datails
    shell-media-web
    shell-media-mobile

  chat
    search-friend
    chat-view
    shell-media-mobile

  instagram-mobile (Something similar to Angular Team Core Lib Concept, trying to decoupling domain logic from de rest of de global configuration and mantain clean apps)
    shell (nrwl feature-shell)  <-   application-wide config, top-level routing and use of layout components.
    data-access                 <-   domain main services and core NgRx config.
    shared
       feature-layout <- app-specific layout that probably depends on "instagram-mobile/data-access"'s services and "common/ui/layout"'s presentational components. In my case
             that app-specific layout should be imported by the instagram-mobile/shell (Nrwl feature-shell lib).
      

  instagram-web
    shell
    data-access
    shared  <- I make a difference between shared and common. I use shared for app-specific shared libs (ui, utils, ...). I put contraints and only web-* libs
               can consume that libs.

common (cross-cutting assets)
    ui (libs with only presentational components)
      custom-material
      layout (app-agnostic layout components) ..components for lists, media objects, etc.
    
    data-access
      auth (Lib for handling auth behaviour, including NgRx Auth Partial State. This lib should be imported in instagram-mobile/data-access and instagram-web/data-access).

I would very much like to know what you think. I do not want to extend the discussion because it would be totally beyond the scope of this post, but I would be very interested to know what strategies
you continue to share the global environments and styles between the libraries of your workspace, in those intermediate parts I see that there are still many approaches and I would love to be able to
discuss them in future posts :slight_smile:

2 Likes

The composite shell library pattern is meant to be an alternative to a Nrwl feature shell library, replacing them entirely.

I understand what you’re trying to do. When we use several shell libraries per application, we still need a place to put application-wide configuration and initialization. I would usually separate it in multiple shared/common libraries like you do and import them from an application-specific CoreModule which could of course also be in a separate library if we’re being very strict in trying to minimize logic in application projects.

Make sure to read this part of the series

3 Likes

Hi Nacho,

I’ve just read through your article, it’s really good, raises some great ideas about application architecture. I have read both the Nrwl book and Manfred’s a while ago, but I’m now going to re-read them.
I do like the approah Manfred sets out more than the Nrwl approach, for one main reason. I think his approach is easier to sell to organisations/managers etc. I think that some managers (I know the current client I’m working) would not be so keen to be tide into working with a third party product like NX. Not that it is a bad product, it’s not, the main problem companies may have in legal reasons, licencing etc. So being able to take pure Angular CLI approach that Manfred’s allows could be more appling.

I do think that now Angular is more of an enterprise level framework understanding how to architect larger, more complex applications is an important part of being an Angular developer. So articles like this and the books you’ve mentioned are more and more important. We need to start having more conversations about architecture than just focusing on new features.

Anyway, great post, a lot to think about here.

Stephen

1 Like

Hello there, Stephen!

Lars and I are thrilled that you enjoyed our article.

I can’t agree more, we need more front-end architecture discussions, but luckily there are people out there thinking like us.

This is a link to the new Angular Architecture Podcast (previously Angularlicius)
https://angulararchitecture.com/podcast

The host is Matt Vaughn you can also find he’s a book about clean Architecture on http://leanpub.com/angular-architecture-the-unofficial-guide/c/cUr2nOPHals0

I genuinely recommend the MVP architectural patterns series by Lars
http://leanpub.com/angular-architecture-the-unofficial-guide/c/cUr2nOPHals0

I understand that your client can hesitate to use new tools, but Nx is a free, open-source tool just like Angular.

However, it is possible to bring some of the features that Nx provides to Angular without too many efforts. Also, I believe that Manfred Steyer uses Nx in his book and talks, that said, everything written by Nx, Manfred, or us is applicable with just Angular.

Once more, thanks for your kind words and stay tuned; we are always working on new articles.

Hi Stephen,

We use the Nx CLI in this article series, but Nx is just a toolchain, helping you scaffold and use a workspace. The Angular CLI can be used in a similar way, if you do some of the scaffolding manually.

Here’s an example of an Nx-style Angular workspace scaffolded using Angular CLI:

Instructions in the readme aren’t complete. @nacho_vazquez and/or I may write about using the Angular CLI for a workspace rather than the Nx CLI at some point. Is this something, you’d be interested in?

About the buy-in, Nx is a free, open source toolchain. I don’t see how it’s different than other development tools we use. You can even just use some of the features, for example use Nx schematics with Angular CLI. In a worst-case scenario where the Nx project was abandoned, you could just stop using their schematics.

All the shell library patterns discussed in this article and the tiny application project techniques in the second article of the series can be used regardless of us using Angular CLI, Nx CLI, or a custom toolchain. They are ways of structuring our codebase. They are tactics to have success with strategies. Our CLI and other parts of our toolchain are the details, supportive tools, nothing more. The patterns we discuss can stand on their own, without the Nx or Angular CLI.

2 Likes

Hey @nacho_vazquez

I’ve just bought the Angular Architecture book from Matt Vaughn, haven’t read it yet. I would like to use NX, but it is hard to ask a client to use a new tool. I do think it is a great tool.

Anyway great article, I’m looking forward to seeing the next one.

Stephen

1 Like

Hey,

I think a non NX version of this would be useful in case some people can’t use NX but still want to structure there apps using a good approach.

Stephen

2 Likes