jonathanPretre / clean-architecture-nestjs

430 stars 133 forks source link

This code is poor advice #4

Open smolinari opened 1 year ago

smolinari commented 1 year ago

It maybe a fair example in how to reduce coupling in general, but Nest's module system was built to afford code reuse and to facilitate an easier reasoning about how the application should work. Nest does this by considering a "domain" as a module... it's a feature. Infrastructure would be in a "common" or "core" folder holding things that are outside the business domain and can be cross-cutting. And each module should entail all the layers of the onion/ clean architecture. Usecases are services in Nest. So, in this todo example, you should have only two folders.

/common /todo

The todo module (folder)would have:

/controllers /guards /interfaces /services todo.module.ts

Imagine you also have more features in your application. How about a blog and an ecommerce feature. Yes, they could also be their own apps, right? But for now, we are a very small team and thus, we work in a monolith.

So you'd have

/blog /common /ecommerce /todo

And if you had followed Nest's methodology, you'd be able to pull out the blog module and use it in any other Nest application with only little modification. Or pull it out into its own application too, fairly simply.

With the advice given in your article/ in this repo, it would be impossible to pull out any feature to be used on its own. Zero code reuse basically denying anyone Nest's modularization advantages. And, it also makes it a lot harder to figure out where the code to fix a problem or needing an enhancement lies.

If this is just an exercise to show a reordering of Nest code to try to follow Clean Architecture more to the letter, ok. But, it's not what you should do to work with Nest. Not at all.

Scott

jonathanPretre commented 2 months ago

Hello,

I appreciate your insights, but I have a different perspective. I aim to strictly follow Clean Architecture principles to maintain flexibility, ensuring I'm not locked into a specific framework.

Your approach seems to diverge from Clean Architecture, which emphasizes independence from frameworks. By adhering to these principles, I've successfully used my template in production without issues.

I'm concerned that following a framework-centric approach might lead to circular dependencies as the application grows, especially with multiple features.

smolinari commented 2 months ago

I can appreciate your point of view. Yet, once you "buy in" to a framework, you are locked in to its methodologies no matter what or how you use it. You are using it. And fighting the standards set by the framework thinking you are complying with someone else's ideas of a "proper" architecture only complicates your code base.

On circular dependencies. Yes, you can run into circular deps even with clean architecture and even without Nest. But, that isn't a problem of the framework or the architectural pattern, but much more about how you model your business logic into its workable components. As clean architecture prescribes, everything should be outside in and in that vein, business logic should be top down. By that I mean, business logic dependencies should always be in the form of parent to child. Never child to parent. If siblings need to communicate, then you need a bus system too.

I think I know where you are getting the "avoid framework lock-in" idea from. It' from Uncle Bob's definition:

Each of these architectures produce systems that are:

Independent of Frameworks. The architecture does not depend on the existence of some library of feature laden software. This allows you to use such frameworks as tools, rather than having to cram your system into their limited constraints.

I've never really understood this completely. For one, if you use a framework, as I mentioned above, you are still "locked-in". For instance, as soon as you use a @Injectable from Nest, there is no way to simply swap out the framework for some other. It's now intertwined completely with your code. You are locked-in.

Also, Uncle Bob's point makes little sense, especially when the framework offers the exact flexibility to allow for the layering he prescribes, as Nest does. If you look at his diagram of what Clear Architecture is about, Nest offers all of the layers:

  1. Entities are entities in Nest too.
  2. Use Cases are considered Services.
  3. Controllers are controllers. Gateways are gateways. Presenters are templates or templating systems (doing MVC with Nest).
  4. Everything else outside are just other modules or external libraries to Nest.

The goal of Nest however, is to put these layers into a module system, so that your code can be reusable. Each module can hold any number of the layers as you feel need to be included, so the module can be reused. If you break up the Nest module/layering concept, then you cause two main issues.

  1. You add a ton of cognitive load to newcomers of the code and to future you, who has to figure out what you were thinking, when you placed a piece of logic somewhere that isn't quite so logical. Basically someone with Nest experience will be saying WTF quite often and you know about the WTFs per minute code quality standard, right?

image

  1. As I've already mentioned, you defeat any chance of reusable modules i.e. making some of your code a library so you don't need to repeat yourself in new projects or being able to offer your code to others in a module. You basically no longer have proper Nest modules.

So, your thinking of being "clean architecture compliant" is not really pragmatic in terms of Nest, as Nest offers clean architecture out of the box...... and a lot more.... 😁

Scott

jonathanPretre commented 2 months ago

On circular dependencies. Yes, you can run into circular deps even with clean architecture and even without Nest. But, that isn't a problem of the framework or the architectural pattern, but much more about how you model your business logic into its workable components. As clean architecture prescribes, everything should be outside in and in that vein, business logic should be top down. By that I mean, business logic dependencies should always be in the form of parent to child. Never child to parent. If siblings need to communicate, then you need a bus system too.

I am sorry but if you face some circular dependency. It is totally because of your architecture. It is impossible to get this kind of issue with clean architecture.

I've never really understood this completely. For one, if you use a framework, as I mentioned above, you are still "locked-in". For instance, as soon as you use a @Injectable from Nest, there is no way to simply swap out the framework for some other. It's now intertwined completely with your code. You are locked-in.

@injectable it is not only used by nest. It is not even a feature from nest. Annotations are implemented in typescript and some famous packages are using it like inversify

I repeat, I understand your point of view, but I don't share the same. For me nest is just a framework who deserve my vision. For you, nest is your god framework to architect your code.

I recommend you to create a repo to share your vision and probably some people will find in your way a better way than mine.

smolinari commented 2 months ago

It is impossible to get this kind of issue with clean architecture.

This is untrue. But, if you believe it, that is fine.

@Injectable it is not only used by nest. It is not even a feature from nest. Annotations are implemented in typescript and some famous packages are using it like inversify

You are sidestepping the point. Just because other packages/ libraries/ frameworks use a particular convention, it doesn't mean you can swap them out simply. In fact, it is the opposite. The framework has you locked-in through the use of that convention (and others) to get things done for that particular framework.

For you, nest is your god framework to architect your code.

It's not a god framework for me, but it is a great tool. I'm using a lot of other tools. Nest is just one of many in my tool box. The difference between you and I is, I use Nest to its fullest potential, whereas you feel you must build a frame of work around it, thinking you can avoid being locked in by Nest. That is wasteful effort and all I'm trying to do is get you to understand why it is wasteful. But, if you don't want to understand, that is fine too. Take time away from doing Nest the right way and do it your way. It will work too. That's because Nest is that flexible. πŸ˜ƒ

I recommend you to create a repo to share your vision and probably some people will find in your way a better way than mine.

I wrote an article on this actually. https://dev.to/smolinari/nestjs-and-project-structure-what-to-do-1223

I've been a moderator on the Nest Discord server for years and I've seen many come up and ask about how to do clean architecture in Nest. Or how to generally structure their project and the answer is always, "follow the module concept Nest offers". It IS clean architecture. 😁