How to implement Clean Architecture in Node.js (and why it's important)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so someone on my discord asked me if i could explain hexagonal architecture to them and honestly i haven't used it myself there's another name for it it's called ports and adapters basically there's that architecture there is onion layer architecture and then there's clean architecture they are all basically the same thing so if you learn one you can kind of translate them to another but the main benefits of these architectures is to create a layer that kind of wraps your business logic and protects it from changes from external sources so for an example let's say you create an application that's using my sql two years down the road you realize that there's something in your application that needs to use a different type of database like or danmodb what you can do is if you applied this clean architecture at the start of your you know development process it's very easy to swap out these external dependencies right you can easily swap it from writing to my sql to writing to mongodb and it really just takes you to change like one line of code to kind of make that swap or maybe like one or two versus if you have all of these external dependencies kind of written directly into your business logic your code can get very messy very quick and you're very coupled to these third-party dependencies right it makes it very hard to change out your code in the future so if this is something that you want to kind of learn about you're interested in hearing about be sure to stick around but before we dive into this topic be sure to click that like button because it helps my channel grow also be sure to click the subscribe and bell icon if you want more information like this in the future that should hopefully help you become a better web developer or software engineer so let's just go ahead and start talking about clean architecture because that is something that we actually are that i actually use at work and i've been using it for almost three years now so i have a decent understanding about it i don't know if i can fully kind of talk about it because most of these architecture things are really abstract and they don't really give you concrete examples for example if you were to read through the clean architecture book written by bob martin or uncle bob whatever his name is it's very abstract and it's kind of hard to grasp what's going on so let's just kind of look at some code and make this kind of a concrete example so i have a little express app here and again i'm not going to run this code because i'm just doing pseudocode and kind of walking you through the higher level designs of how stuff can change but a lot of people when they're building out an express application they'll just do something like this where they have like an endpoint called slash users and they'll call like a route here so if i look at this create user's route let's kind of look through and kind of figure out is this good code or is this bad code and how does this not work with clean architecture right so off the bat there's a couple things in this code that is kind of a red flag that could potentially be a huge issue down the road if you decide to switch out to a different framework or a third party library so the main thing we need to kind of talk about is this key term called domain level knowledge or business logic so there is a diagram that kind of explains clean architecture here we look here you can see there's like this circle right and basically you're supposed to put your frameworks or your web uis or your libraries in this blue ring and basically that blue ring needs to talk to gateways and those gateways need to kind of invoke use cases and those use cases talk to entities so all this is really abstract and confusing to be honest with you all but but in a general sense what you want to do is this yellow ring this is where you kind of have all the logic related to your your business entities right so like for an example if you have a system that you can create users in you might want to have a user entity and inside the entity you have certain rules in place to make sure that a user cannot do anything in your app until they've paid right so that user entity could potentially be like an es6 class or something with methods on it and you need to kind of define the rules of a user right so for example if you don't want users to be able to create new to-do list items if you build like a to-do list app you might want to add a role in there that says can user create to do and that is going to be this like yellow this yellow circle of business rules right so there's certain criteria that the business needs to meet or your entities need to meet before they can execute business rules and if you go back up a level you have this orange ring called use cases right we also call them interactors basically this is where the core of your business lives right you have your application business logic in the orange ring and then in the yellow ring you have your business rules again this is really abstract that might not make sense but the main take of this whole like onion architecture hexagonal architecture clean architecture is to kind of decouple your business logic from your external sources right so anything that's defined as like a database call you really need to have like an interface defined so you can pass in a real implementation so we're going to talk about interfaces in a bit but anything that's in this blue circle should not bleed into your business logic right so for an example if you have a business function and you see express related code in that business function you're breaking this paradigm of clean architecture because now you're coupling really tightly to your you know express logic so let's go back to the code this is probably really confusing so the first issue with this function if this is supposed to be like a business logic the first issue is that we have rec and rez right in this code right we are pulling out information from the express library which goes against this whole idea of like you shouldn't be coupled to your third-party dependencies or your third-party frameworks so what you can potentially do is you want to kind of isolate that layer so that you don't have this stuff going on so what you could do is i could rename this to get user interactor and again there's a lot of different ways you can kind of do this so i'll do interactor and we want to take out rec and rez from this interactor because again this is supposed to contain all the business rules this is if i go back to that diagram this is that orange ring here right this is the use cases or the interactor as you can call it so let's go ahead and try to get rid of this and really just figure out what does this business function really need well it just needs a username and a password right that's all it needs to be able to do its job so we can find a way to abstract that out then we are kind of making a more cleaner architecture and cleaner components so let's kind of go back to the index and let's show you how you could do that so instead of directly calling create user in this case i guess it would be create user interactor you could potentially just do recres here and i could just do some logic to basically call that with rec.body.username actually what i'm going to do is i'm going to say const username and password is equal direct.body and then i can pass those in username and password i think that's how i did it yeah so this is a step in the right direction of what hexagonal or clean architecture is all about it's basically kind of protect your business logic from your external dependencies so now this doesn't know about express at all right there's no express logic in here other than at the bottom which we're gonna have to fix as well so right now this is trying to call res.json but we don't pass in res anymore right because we're trying to protect this business function from knowing about express so let's just go ahead and return i don't know like a user object again this is all pseudo code so none of this will actually work if i try to run it but we can maybe just do something like this this would be like an id of one but i hope this all makes sense so now this doesn't depend on the res object that comes in from express but in order to get this working you got to go back up a level and i would probably need to just do a try catch on this catch any errors that might happen and just say res.status of i know 500 and then we can send back that error all right and then usually the interactors are going to be like async weight so i'm just going to go ahead and say the const new user is equal to weight on that and i'm going to say res.json new user all right so again there might be flaws in this code but this is kind of just exemplifying how you can make your code cleaner by following a clean architecture approach so i hope that makes sense this interactor now knows nothing about express and that is a good thing to do that is how you can kind of apply clean architecture to your code base so now if you look through this code you will see that there are some other issues with it right we have direct connections to the database which is a very very bad approach right now this code is very coupled and it's very dependent on whatever database implementation you might be using so for example up here you have it requiring my sql directly this is bad because now our business logic is tightly coupled to my sql remember the whole point of this architecture is so you can easily swap out or exchange these different modules in the future if you decide that mysql doesn't actually fit your business needs so what you can do is you can make a new file or a new function and i can just call it create user and i like to put the actual type of layer that it resides in so i'll say like persistence so now what we can do is inside of this business function this interactor we can actually import that now again this isn't the actual perfect way to do it i need to kind of talk about dependency inversion and dependency injection in a bit but let's just go ahead and import it right now because it makes a little bit more sense and what we want to do is we want to remove any traces of my sql from this code so let's just go back to this function here i'm going to go ahead and import that i'm going to go ahead and do all this logic again this is just pseudo code it probably has tons of bugs in it but i'm going to basically do that so now this function is the only thing that knows about my sql how to connect to it how to do things with it and we can just call it call like con's new user is equal to a weight on great user persistence maybe passing a username and password or whatever information that you want might want let's just go ahead and just say username and password here all right so this is again a step in the correct direction now our business logic doesn't know anything about mysql which is a great approach okay so again we're applying clean architecture to make our business logic isolated from third-party libraries there is an extra step i need to do but i'll talk about that in a second the last thing you'll see this code's doing is when a user registers we need to send out an ses email so let's just go ahead and create a new method called syn registration email persistence or something and this is just a name convention you don't have to name stuff persistence if you think it's a stupid name but i'm going to go ahead and say send registration email persistence and again this function really just needs to care about how to send the email you don't want to have business logic about like what the email should say so i'm going to go ahead and just extract all this code and put it inside this function go ahead and do this and i think that should be good now i'll go ahead and call send registration email now what we need to actually do is probably send who that's going to so i'll say like uh two and i'll say email or username we'll just assume username is an email and i will say body is welcome to the application and again we'll probably have to wait on it because this is probably gonna be asynchronous function but i hope you understand like what we're getting at i'm going to change out the email and i'll change this to body and then somewhere down here like we'd probably use body we probably want to do subject as well and this could be email all right so this hopefully that kind of works if we actually like test that but let's do subject and say like welcome and of course we could probably just return a new user here instead let's pause for a second let's inspect this code and understand what happened so we now have this business logic that doesn't know anything about how to send an email all it knows is that i have a function i can call and it also doesn't know anything about databases it doesn't know that i'm using sql it doesn't know i'm using mongodb all it knows is that i can call a method that's going to persist a user okay this is how you basically set up clean architecture now there is another step i need to talk about and it's all about how dependencies are set up so right now this business logic still depends on functions that are tightly coupled to my sql right so now this whole thing is still coupled to my sql it might not look like it but in a sense you're requiring these functions that are coupled to my sql so it's very hard to test and it's very hard to switch these out later on so one approach that we do to kind of reduce the coupling is your interactor should basically depend on interfaces right so inside of the this should be kind of like an interface that it calls and it just needs to pass whatever data to the interface and the logic should happen behind the scene so how do we solve this and how do we make this more decoupled well basically you need to have this depend on interfaces right so instead of calling the actual implementation here it needs to call an interface and you need to pass in the correct parameters or arguments that the interface needs so one way you can achieve this is by using inversion of control and a specific way to implement inversion of control is by using dependency injection so inversion control is basically doing what i'm saying instead of this thing requiring and depending these directly you can have these passed in and dependency injection is one approach to kind of implementing inversion of control so what does it mean to have it passed in well by using dependency injection we could actually just pass in some methods here and these methods could basically do the exact same thing right so these are going to be like interfaces in a sense but now this business logic doesn't know how these things are implemented so whatever is calling this business logic can swap out the implementation details on the fly if you want to or you can use like a framework that does you know the built-in dependency injection or something like that but right now this is called like function injection but there's also like construction injection if you're doing es6 classes you can in the constructor you can kind of inject whatever dependencies that you want you could also do like setter injection where you have like a class that has setter functions you can call those setter functions to basically set these things but i personally like using functions and i like to pass in the dependencies inside of the arguments here so that you might say well then how do i actually pass in these methods so let's go back to the create user interactor where we call in the index here and now what you do at a higher level you need to basically import these two things so let's just go ahead and import those real quick i could do um [Music] which ones do i want i want to create user persistence is going to be require i'll require that one and then i'm also going to require syn registration email persistence and do that all right so now there's only like a single place that i'm kind of setting up the actual implementations honestly if you're using typescript this thing would depend on interfaces here and not actual implementations and then you'd pull in the actual implementations here which would kind of extend that interface and you would basically just pass them in like so right so i would just pass those in alright so now we actually kind of fully did clean architecture we have a business function that doesn't depend on mysql all it depends on is an interface that knows how to connect to a database and then also it knows how to send out an email again it doesn't care how the implementation is done you can use sendgrid you can use ses from amazon the business logic doesn't care all it knows is that i have a method i can call and when i call that method an email is sent out somewhere so this is now clean this actually abides by clean architecture this thing doesn't depend on any methods that are kind of hard coded into third-party libraries and all of these are passed in using dependency injection more specifically it's function injection but i hope this all makes sense there's different ways that you can kind of achieve the whole dependency inversion dependency injection by using like a framework that kind of does that for you i know if you've touched angular before like they have built-in ways to do this dependency injection approach which is great and i think there's a couple of node frameworks that kind of do the whole dependency injection for you so if you do want to go down this this road of like clean architecture or hexagonal architecture i definitely recommend that you check out a framework or library that can kind of do this for you so honestly that was like a really high level quick overview i don't know how much more like in depth you can kind of get into this stuff you can kind of the the main issues i have with reading this clean architecture book or talking about hexagonal architecture is that these people talk about really abstract concepts and they can just ramble on forever about these concepts and not actually show you how to implement them right so what i'm kind of gearing towards in this video is just show you how you can implement it and not get you all caught up in the weeds of like oh this is an abstract this is an interface this is a port this is an adapter again the main goal is just isolate your business rules from your third-party dependencies and i didn't really talk about entities here i know if i go back to that graph there's like a yellow entity but typically what you do is you probably have some type of const user entity like i say like user entity that might take in a username and a password so use your imagination if we made another file called user entity that could be an es6 class that has some methods on it and i could say user dot validate or something that's what we mean when we say business entities you have these like these things these either esx classes or methods or functions that you can basically create or construct these entity objects and there's logic that's built into these entities to make sure that certain things are met so for example let's say you actually did have a user entity let's just go ahead and pretend like we have one all right so let's pretend this is like an es6 class this has a constructor and this might have like a this dot [Music] this dot username is equal to username and this dot password is equal to password right so let's just pretend that we have this and it also has a validate method which will basically throw new and error if something is invalid what you can actually do inside of this is you can say if the password length is less than six i could just throw like a uh invalid invalid password error all right so this again this is like pseudocode but this is the gist of what an entity is you have like these properties that you store on this like data object and these and these entities have methods that you can do to change username change the password to validate that everything is proper before you actually start saving that into the database right so here i could potentially just save the user into the database if you wanted to there's more caveats about clean architecture that i could kind of touch on but basically this is how we do this at my job we've been doing this for like three plus years now we have a ton of different entities that contain all the business rules and the business logic of how do you like validate certain things meet certain criterias and it can get really complex really quick so it's good to structure your code to make sure that all this is set up properly with entities you can validate those entities and then you need to persist them somewhere and then at this case we send out an email so yeah that's about all i wanted to talk about with clean architecture i hope this was a good overview i know i went kind of fast and some of this stuff can be really confusing but if you don't feel like you've learned much leave a comment below maybe i can kind of dive more in the detail about this video like always be sure to click the like button if you enjoy this video because it helps my channel grow and also be sure to click that subscribe and bell icon if you're new to this channel and you want to get better at software engineering or web development because i'll have other videos like this in the future that should hopefully help with that have a good day happy coding
Info
Channel: Web Dev Cody
Views: 94,665
Rating: undefined out of 5
Keywords: web development, programming, coding, code, learn to code, tutorial, software engineering, node, node.js, clean architecture, clean code, how to keep you code clean, dependency injection, inversion of control, dependency inversion
Id: VmY22KuRDbk
Channel Id: undefined
Length: 19min 31sec (1171 seconds)
Published: Mon Feb 21 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.