#Layered architecture for medium-sized projects

1 messages · Page 1 of 1 (latest)

cloud path
#

Hi, I need to create a well-structured layered architecture for medium-sized projects. I chose this approach because the projects I'm gonna build will not be large, instead just medium-sized applications. DDD is an overkill for them, especially when I want to create MVPs quickly and in an easy way and I think trying to use DDD for example would do more harm than good. I want to find conventions and advice that could help me create a solid and straightforward structure for projects. I won't change DB in the furure, I won't change my ORM which is PrismaORM.

Therefore I've got some questions concerning the correct implementation of layered architecture:

  • while some actions would require some logic should I create one service per use case for example: CreateOrderAndEmptyCartService
  • what about queries, as they do not contain business logic should all of them be in one service?
  • I've seen repository pattern in layered architecture, is it suitable to my use cases where I won't change DB, ORM and I'm using Prisma? I tried to create a repositoy for User for example, but I've got an impression that I just made an additional class which is a wrapper to for example findFirst method (and other) which with only Prisma takes just one line of code. More than that, when I want to have types inferred correctly, I can not dynamically pass include: { account: true } for example and I have no other idea than giving all possible includes in the repository method and then not to use most of the join results when I do not need them which seems kinda lame.
  • should I make different services for different roles for example Admin and User in a case when Admin has to receive different responseDto than User for the same query?

Last but not least, do you have any example of medium-sized applications to share so I could see how it should be done?

Regards

livid depot
#

My suggestion is, don't sweat these details too much. Just remember that Nest modules are mini-onions i.e. they can hold all the layers from controller to repostory (if you use them). So, start creating feature modules. As you get deeper into your creation, you'll start to notice when a module might be getting too big or realize one feature is actually more than one feature, and you can at that point break up the module and start making parent/ child or sibling modules. Your app will automatically scale as it should, naturally.

As for the repository pattern, I'd consider it a coding feature. If you don't need it, don't use it.

I guess the better rule of thumb is, if you try to determine too much of what might happen in the future, you'll probably be doing a lot of work for nothing. So, don't work for a too distant future. Just start rolling with Nest's modules as they were intended. That will get you pretty far and your app will be organized too. 🙂

cloud path
# livid depot My suggestion is, don't sweat these details too much. Just remember that Nest mo...

Thanks for the answer. If you're saying that I should use only the things that I need, would it be wise to use the repository pattern only in the places when I need to perform some more complex queries ? Let me clarify what I mean: in +- 70% of cases the only thing that I need from my ORM is to find a unique record in db with its basic data so using prisma the code would look like this

const user = await this.prisma.user.findUnique({ where: {id: userId} });

However in some cases I need some more information about the use like for example roles, address and cart details so the code would be

const user = await this.prisma.user.findUnique({
where: { id: userId },
{ include: {cart: { include: {cartProducts: true} },
roles: true,
address: true} });

If I used prisma directly in my services for simple queries and call repository only for more complicated queries like the second one would it be ok, or is it too confusing that in one place I use repository and in other ORM directly?

livid depot
#

@cloud path - Where are you getting the assumption that the repository pattern should be used for "more complicated queries"? That isn't a reason for using it. I'd say, use the query as it is. If you start noticing patterns where you might be able to DRY up some of your code with a repository, then go for it at that point. From my understanding, (as I don't use Prisma) it is a large abstraction itself and if you try to abstract those details away into repositories, your just going to be heading for extensive trouble.

fiery coral
#

I'm with Scott on this one, I'm finishing an ecommerce project, and I didn't find a need for repository.

cloud path
# livid depot <@680365486294302720> - Where are you getting the assumption that the repository...

Actually I read this: "Understanding the abstraction of database queries is quite simple. Imagine having to write a very long database query. Now imagine having to rewrite that database query out every time you want to use it. Well, most people would just encapsulate a long database query in some sort of re-usable function. And that's just it, that is one of the purposes of the repository pattern, to create re-usable, abstracted, and encapsulated database utilities functions." in this article: https://book.restfulnode.com/part-2/chapter-4/2-layered-n-tier-architecture-the-unpopular-proven-way . Maybe I just misunderstood what the author meant in this post.

On another website I found an example where there is a repository pattern using Prisma https://www.tomray.dev/nestjs-prisma . I have to aggree with You that it seems to be another layer of abstraction on the top of another abstraction which is prisma.

livid depot
#

@cloud path

Now imagine having to rewrite that database query out every time you want to use it.
Isn't that exactly what I said?
use the query as it is. If you start noticing patterns where you might be able to DRY up some of your code with a repository, then go for it at that point.

cloud path
#

A quick follow-up of the discussion. I read about use-cases pattern and I think it can be a great solution for me as in the requirements of the app that I'm going to build, there will be many operations that will be performed by users with different roles and each role will have a slightly different logic of for example getting access to a resource, so I would like to have reusable code to some crucial business logic that could be surroanded by logic specific to a given role. I'm thinking about creating services which could orchestrate the flow of use-cases.

  • For example, I could have EmployeesService which may have a method assignEmployeeToProject().
  • This method would require checking if the user which calls this method has access to this employee, let's say we want to 1. check if the employee belongs to the same organization as the user who calls the mehod (let's assume he's the admin of the organization) and probably check more conditions 2. assign employee to the project, 3. send notification
  • In the assignEmployeeToProject() I would call such 3 use cases: checkIfEmployeeCanBeAssignedToProject.execute(), assignEmployeeToProject.execute() and sendNotificationToEmployee.execute()

Would it be a great way to go? I see 2 main benefits: reusability of code and screaming-architectute. Also each of such use-cases would follow the single-responsibility principle and by exposing only execute() method of the use-case class I could hide implementation details. If it may be a great solution, should I use use-cases only for more complicated operation which require business logic and do not apply them to for simple get queries? Should I have one module with all use cases on a given resource for example UserUseCasesModule, or maybe I should divide such modules further according to the role of use cases: UserOnboardingUseCasesModule, UserAccountmanagementUseCasesModule?

livid depot
#

I think you are over thinking things. @cloud path If you look around at the definitions people offer about use cases, they can be units of work or whole processes. I'd suggest, don't try to "bend" your application to some theory like you are doing. Just use what the framework offers (modules/ providers/ services/ guards/ pipes/ interceptors, etc.) and you'll start to notice where different patterns can be used to make the code smarter, DRYer, and more SOLID (whereas the framework helps you do this from the start with the different "fundamentals").

Put matching feature code in modules. Break down your business logic code to simpler services and helpers (providers). Build your graph of dependencies via the modules system. Use guards for authentication and authorization. Use pipes and interceptors to do any "cross-cutting" work. etc. etc. etc.

If you follow what Nest alone gives you, you'll be following all kinds of other architectural patterns, without having to give them credit via naming. In other words, please don't do "DoSomethingUseCaseModule". If everyone looking at your app knows Nest, they should know that a "Module" will be some sort of feature of your app and it can entail either business and/ or system running logic in sub-modules/ services/ helpers/ providers, etc. and the module will also hold the matching pieces for the "layers" of your app for that one feature i.e. controllers/ resolvers, guards, interceptors, pipes, services/ providers, etc. etc. If you throw in "UseCase" in Nest Module names, instead of making anything more understandable, it will put questions in the heads of other devs more than help them understand the intentions of trying to follow clean code architecture. I hope you understand.

My best suggestion to you is, stop trying to make a perfect architecture and just start building your app following Nest's way of doing things. As you go, you'll learn more and can improve things, but you'll also be building something too.

cloud path
livid depot
#

@cloud path - Where you using Nest before?