Transcript for:
Clean Architecture

all right so someone on my discord kept asking me questions about clean architecture and i kind of made this diagram for him to kind of explain like at a rough level how clean architecture kind of works if you don't know what clean architecture is it is a book written by uncle bob it's a good read it's kind of teaches you more about like how to set up your systems into more manageable maintainable layers i'm not sure if i'm a big fan of uncle bob but he does have some good ideas uh but anyway like the kind of talk about what clean architecture is there's different approaches to clean architecture there's like ports and adapters i think there's called like hexagonal architecture but i think a majority of all of these architectures the goal is to have like your core domain model your business logic which is a term that we kind of use a lot in software business logic or domain and the goal is to kind of reduce the amount of dependencies that your business logic has and i'll kind of walk you through this chart real quick but if you're building out a a system it's usually always good to kind of separate your different types of tech into layers right a layered base architecture is probably always going to be a benefit to you especially as your project gets larger and you have more dependencies and you have more requirements that come in and change your system over time i want to give a little example of how you could do this with like express next or nest js it doesn't really matter but the idea can kind of be applied to any type of back-end and you could still kind of apply this to the front end too it might i might need to change this diagram a little bit right now let's just pretend that we're working on like a next.js application and we wanted to try to apply clean architecture to it right so in next um actually most of the stuff is in express so i'm not going to talk about next let me let me rephrase what i was saying so typically in express you have like a main app.js and inside the app.js you have a bunch of like uh routes right these routes will basically invoke a function so if you want to kind of apply clean architecture the goal is you need to make sure that whatever is inside this get users controller all it needs to do is take the data that came into the request and pass that data to another file here another module called like interactor or a use case i think is what uncle bob calls them and the idea is your domain your business logic should not know about express okay this whole layer here should have no knowledge of express now the main benefit of that is you can take your entire business logic and you can move it from express to nest js you can move it to next js it doesn't matter this is just javascript code and the framework that's handling your http requests is something that doesn't really make that much of a difference right these functions all they do is take in some primitives like strings objects numbers and they crunch on those numbers and they do whatever business logic that you need them to do and then they return another primitive right so the interaction between the controller and the interactor you want to kind of insulate your interactors from any type of next js request objects or insulate it from the response objects this thing should not know about next or express okay so i think i made that point clear so all this controller does is basically do like rec.body maybe extract the things that you need it and then pass that into your interactor now the interactor you can do some validation in here to make sure that the things that were passed in match certain criteria and typically we do that by using something called entities so entities are kind of like classes typically which have a bunch of different properties on them and they have validation rules that are kind of coupled into that data so for example let's say you had a user entity and it has a status of um [Music] registered or something right and the only way to get to that registry or that registered status is if they were paid right if they paid something so the user entity might have two fields it might have like a status of registered and then it might also have a receipts array on it that has a receipt of when and how they paid for their uh status or their service okay so you might have joy validation rules or zod validation rules if using javascript that kind of check whenever you try to change this entity you didn't put it into a bad state that basically breaks your business rules because if you were to take a step back from the code and just think about the business rules someone cannot be registered unless they paid right and so your idea is you take those business rules those higher level abstract business rules and you codify them into an entity that has the validation and links all these different things together and you're going to have a bunch of different entities that have their own validation rules and sometimes you have entity entities that depend on other entities and those have cross validation rules like a user cannot have receipts like let's say you had a receipt entity and i know there's no there's no room here but let's say that receipt entity kind of depended on the user or vice versa and there's some type of validation rules that says that in order to basically have this user entity is being paid you have to make sure that there's a certain receipt here okay so there might be a top level validation run or something that verifies those type of logic rules so it's kind of your job to figure that stuff out and it's kind of hard to wrap your head around exactly how to do this but i think as long as you just make sure that your business logic doesn't leak outside then you should be in a good state and that's something i didn't really talk about either is the whole idea of this layered architecture is you don't want business rules leaking out to your front end or leaking out to your persistence layer right if in the front end or whatever the controller is doing of the view has like some hard coded logic to figure out when an entity is considered registered then you are basically leaking business logic out of this layer which isn't good in order for the front end to know if an entity has been paid you should probably have some type of function here that the front end or the controller can invoke but no is my user fully paid and they can get that information and they can change stuff in the ui based on that so yeah make sure that your business rules do not leak out and make sure that you do not copy and duplicate your business rules in multiple places because it's going to be very hard to update a bunch of different places if you decided to change how your business rules are set up and if you kind of use entities that means that all your business rules are kind of isolated into your entities and all your interactors do are basically acting as the orchestrator of taking your entities taking your data from your database gluing it all together and validating it and sending it back so one thing i didn't mention here is the right side is typically you have to talk to some type of database right so your entities need to call methods and those methods are what calls the database so we're talking about express right here so let's say you're using something like prisma that means that there should be no prisma logic here nothing inside the business layer needs to know about prisma you don't need to know about sqlize you don't need to know about you don't need to know about a sequel there should be no logic about the database inside your business rules because what happens is as your business logics become more complex you may need to store stuff in different types of databases and your business rules shouldn't care about where that data is coming from right they shouldn't care if it's coming from sql elasticsearch all they need to know is how do i get that data just give me a method i can fetch that data and then i can do what i need with it in my interactor so make sure you don't have any like db logic prisma etc here let me make sure this is red so it's kind of more apparent what i'm trying to say so your if your db logic doesn't exist in your business logic or layer where does it exist well it exists over here so this is another layer this is like your persistence layer sometimes you can do something called like a repository pattern where these are called like repositories and these are the things that talk to the database it doesn't really matter about the naming convention just make sure that you have a line that separates all your business logic from your database logic okay now the benefit of this is now that git user interactor only depends on a git user's call you can easily swap this out so let's say this was like sql and then you get a business requirement and you realize that okay maybe sql wasn't the best choice maybe in this little example with receipts we need to store receipts or users in a different type of database and fetch it from there right so you can just point this thing to a completely different database and your business rules didn't know about it okay so that is the main benefit of what we're trying to get at with this whole layered architecture clean architecture um there are some other things i didn't mention so maybe i'll mention them real quick is that this thing this interactor file should not require these uh persistence methods okay so there's this idea of abstract code should not require more concrete code so for an example in your get users interactor if you have a require statement or an import statement where you're importing git users technically you're doing it wrong because you don't want to have to go through and refactor every place that you're calling gate users what you want to be able to do is you want to be able to swap that out using either dependency injection or some type of like ioc container where you can just at one place in your code base swap out what this method is so let me kind of show an example of that real quick okay so let's say you had a method called get users um and this is like an interface so let me just go ahead and put like an i i'll just say like interface hopefully this if you use typescript or javascript this will make more sense so you have like this interface here and the git user's interactor depends on the interface it doesn't actually depend on the hard coded logic now the benefit of that is that you can have different get user implementations like i say like a sql get user dot js and i can also say like get user.js now if as long as both of these like implement this interface what you can do is let me just move this over here real quick are you guys super confused already i hope not so let me just go ahead i don't know if i even have the lines correctly there's like a special way you're supposed to put lines like if it depends on something then uh you're supposed to be pointing one away honestly i'm just kind of like hacking that stuff and trying to get my point across so if i do the lines the wrong way like don't get mad at me i think there's some of them supposed to be solid some of them are supposed to be dotted like who cares you got you're just trying to i'm just trying to teach a point right so if not teaching it correctly like i apologize all right so the idea is like this thing is depending on an interface and those interfaces can be kind of implemented with different um real concrete code okay so what this allows us to do is if you were to pass in the actual persistence implementation so if i had like another let me do this i'm gonna go ahead and pretend like we have another module here inside the controller we have like another let me just okay i'm gonna do this with dependency injection using like functional injections so let's say this is a method and this method should not import this interface right we should not import these these implementations so how do you achieve that in javascript or typescript well one way is you basically pass in as either a functional argument or a constructor for using classes or something you basically pass in the implementation [Music] at runtime right so although i did put this over here like don't get confused this is not part of the framework um layer this is just an example of like this controller is going to basically be the one who imports this okay so the controller imports it and disregard this stuff down here so the controller is the thing that chooses what implementation the interactor is going to use so you might have an import in the get user's controller it says import the gate user and that's going to get a function that also uses the same interface of a git user's interface and this get user's interface they must should call it user from db interface because i just now noticed that both of these are like kind of similar anyway all right so if this thing imports the actual implementation of the interface and we can pass it in like this just as long as this get users interactor takes a command line argument or not a command line argument a function argument that says it takes in a get user's interface you've kind of given this interactor the ability to do its normal logic but it doesn't need to know about what database store it's pointing to now i don't know if i just butchered this whole explanation i probably just made this really confusing but the benefit of all this and i'll give you a real live use case we had a project where we have a bunch of different endpoints and a lot of them have to send out emails and all of these things are built using clean architecture so when they need to send an email they all get a single thing here called like send email and typically this thing is like set up an object factory i don't want to get too too into the weeds and confuse you all but there's an object factory where you say get my send email function implementation and that would return a function that kind of determined how to send the email okay so we did this like you know seven or eight different places in our code base where we have an api endpoint that when it does something on the data it needs to send out email so we had to like kind of redesign how emails work in our system we had to push them off from the queues and have those cues be processed with some type of like parallel processing to make this more efficient we only had to change the code in one place right although like we had to change a lot of like underlying system code really we had to come into one file and change the implementation of send email so i just had to come in and say send email async that's it get that deployed everything was now sending emails to a different code path to the sqs queue that sqsq was being processed and sending out the emails so the benefit is again the business logic doesn't care about where the email's going it just cares that if i send this you know a subject a body in a destination that something knows how to send it somewhere and i don't care what that something is just pass me something that meets that interface there's one last topic that i think is important to kind of mention um although i don't really do this on my project because sometimes you can just add extra duplication and complexity but there's something called a data transfer object where the thing that you return to the the user like the front end you don't want this to kind of bleed over you don't want your database information um let me rephrase this as your system grows you may have needs to change what's stored in your database right but as you're changing properties or fields in your database you don't want that to bleed all the way through your code base and get back to the ui and start breaking stuff because you changed a field so there's something called a data transfer object which i believe you can put here and so instead of this thing returning just like i don't know the full user object you actually pass it through a data transfer object which will kind of map i'll put like a dto here and that'll take in the information that was in the database and transfer in such a way that keeps the ui uniform so the api response that you get back is going to be always the same thing no matter how much stuff that you change behind the scenes and i believe you can do the exact same thing between your business logic and your your interfaces here right so let's say you want to insulate your business logic from how the database is changing you can add dtos here as well that basically prevent um you know database changes from bleeding over into your business logic now i don't do this on our projects because i do think it's just additional complexity if you have control over your front end and your back and in your database it's really easy to go through everything and just refactor if you decide to change a field in your database and then also like often you don't want to change fields in your database because now your code's not backwards compatible and it's harder to deploy it if you're doing like a blue green deployment but i just want to kind of talk on that subject i think you can also have dtos coming in so you'd have like this thing it's sitting in a dto this thing would be like an interface or you can just keep this stuff primitive so you don't have to worry about details i don't want to get down that discussion because i don't use it on my project at work because i think it just adds extra complexity but i know there's a lot of people who probably write in java and do this stuff because they want it to be proper but at some point you have to like realize are we just over engineering everything are we adding too much additional stuff what is the benefits that we're getting from adding all this stuff but i think this is the sweet spot of like having your stuff be in layers isolate your persistence from your business isolate your business from your frameworks and then also allow you know us to easily switch out what we pass and use in our business logic by just basically using dependency injection or something else where we can dynamically change the implementation of these methods at runtime anyway i think that was a lot of stuff i don't know if i did a good job explaining that but hopefully that was a good overview of why we do this and how you do this i might actually have another video where i do this in a next.js application and kind of walk you through this process so if you're kind of interested in seeing that be sure to subscribe press the bell icon and wait for that video don't know when it's going to be posted but stick around also join my discord if you want to kind of talk to me directly or ask my community of people who are learning how to code we got a cool community growing over there and i think you'd be interested in tagging along have a good day and happy coding