Clean Architecture with ASP.NET Core 8 | .NET Conf 2023

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] let's get going all right I want to show some coat so first let's talk about what is clean architecture and you'll find various definitions there there's a whole book on the topic uh it's very closely related to onion architecture hexagonal architecture or ports and adapters I think uh ports and adapters probably has the the clearest name out of the bunch hexagonal is a fun one because it's named that because somebody drew a hexagon when they were drawing the architectural diagrams whatever shape you draw that's what your architecture will be known as um but the point of it is in all these cases is that we want to have a domain Centric approach to organizing our application and its dependencies and so what do I mean by domain Centric what else could it possibly be besides domain Centric uh well it turns out that uh in the before times a whole lot of applications were written with the database as the center of the universe and there are a lot of trade-offs that happen when you design your software that that way uh trade-offs that often cause a lot of pain when you want to try and do things like find seams in your software so you can easily test it or or change some of its dependencies U and so having this dependency on infrastructure be minimized is the key feature of this type of architecture and there are trade-offs for that right it's not necessarily the the best architecture ever um but it is a choice that you should be aware of uh and it's a good fit for a lot of different options so when would you use clean architecture well uh if you are a team that is comfortable with and and practicing something like domain driven design and you really want the focus of your software to be on that domain model that you're creating and not necessarily on all the infrastructure concerns and all the cool extra uh dependencies and services that you're working with then clean architecture really helps the team follow that that guidance make sure that everything does stay domain Centric right you can lean on the compiler to ensure that nobody on intentional on purpose intentionally or by accident uh is is bringing in dependencies where they shouldn't um and of course another place where this shines is where you have complex business logic where you really want to have a lot of automated testing of that logic to make sure that everything is working uh and so you know between these two things they they go together very nicely because uh domain driven design is ideal for things that have a lot of domain complexity uh and clean architecture is a way to guard against having those infrastructure concerns creep into your domain and make it more difficult for you to change it or test it uh as as demands uh change and and your understanding of the problem grows all right so you might choose clean architecture because you want the architecture to help enforce your policies rather than having to have disciplined developers who always do the right thing all right so if if you're are using C because you like that it has a strong uh type system and that the compiler will tell you when you try and pass a string into an INT uh parameter um and and you get an immediate feedback that says hey you can't do that because of that typing those kinds of constraints are the same type of thing that you get when you follow clean architecture as part of your your architectural approach because what's going to happen is if someone tries to bring in a dependency on something that they shouldn't uh in the domain model for instance the compiler will tell them no sorry you can't do that uh that's not how that works right the the dependency won't work that way right so historically um there's there's been various reasons why you might want to break up a solution into multiple projects or logic multiple logical layers um and the traditional one that many of us are familiar with is is known as end tier uh and this is kind of what I grew up with early in my career uh and it was certainly an improvement over just having spaghetti code with all of these concerns mixed into the same files uh but it does suffer from the fact that there are these transitive dependencies now where the database is dependent on by the data access layer but the business layer depends on that and the UI depends on that and so at the end of the day everything ends up being dependent on the database and if you want to try and write unit tests for your business logic that don't involve the database it can be difficult to do so depending on some other factors but in general it's not as easy as it could be all right so clean architecture uh to use a similar shaped diagram uh looks like this so the user interface just depends on your domain model your business layer uh and your infrastructure which includes your data access also depends on the business layer right so it's following the dependency inversion principle by using abstractions by using inter faes and so by placing those abstractions inside your domain model it makes it so that the infrastructure layer can Implement those abstractions and depend in that direction and what that means is that the business logic can't possibly depend on your infrastructure layer can't possibly depend on the UI project now if you put everything in one project and they're all just in separate folders you can absolutely ship successful software but there will not be any guarantees from the compiler to tell you that the things that are in the domain folder are not having a dependency on the things that are in the infrastructure folder right if you have such folders and so clean architecture gives you that constraint gives you those guard rails to make sure that you're not accidentally introducing dependencies in places where you don't want them all right so another way to look at this more of a hexagonal architecture approach uh is to say that at the center of your application you have this core project with your domain model and we'll talk more about what's in there in a second um and importantly various abstractions a lot of n tier architecture stuff didn't use any abstractions didn't need any abstractions everything just Ned up whatever it needed or used static uh references to it um but it turns out abstractions have a lot of value uh and clean architecture and domain driven design both benefit greatly by the proper use of certain abstractions um with that Central project having no dependencies on exterior things your your UI project your infrastructure project even your tests all those things can depend inward uh and and you don't have anything pointing outward from that core project all right so I have a template that's uh available for you to use um I'll show you how to use it here real quick it's it takes two lines of code um so if you run out here to uh nit and you grab this bit of code right here to to install this preview um of the net 8 version of this then you can come into Powershell and I've actually got preview two uh just released like an hour ago and if you hit enter it'll install it and I've already installed it so it's there already uh but then if you want to uh create it you just do this and say net new clean Arch uh DN and give it whatever name you want usually as as two values a company name do project name right and if you do that uh it's going to go ahead and create it you can just CD into it and see what's there and you can do a you know doet build and everything should work uh and just like that you have this solution template uh one of the things that's nice about this is that you don't have to produce all those different projects with all the right dependencies and the right directions right it's not huge it's only about s projects and that includes three test projects um but not having to do it yourself saves you a bunch of TDM I I know I have to set up projects fairly frequently uh and it's it's really painful to have to create this this web project and then this class library and then this other class library and then these test projects right so this saves you some of that um and that's that's really all there is to it right we're going to look more at what that template gave you in just a second um but that's all you need to do to get started with it all right and if you're using visual studio Visual Studio code doesn't matter either way it's just those two lines uh in in p shell or or your terminal works fine on a Mac right it's. net core uh and and you're good to go all right so now let's talk about the rules of clean architecture and so the first rule is that you want to model all of your business rules and your your entities that you're going to track the state of inside of the core project right that's your domain model if you're following domain driven design and that's the thing that you want to concentrate all of your complexity in and not let any business logic leak out of there um or other types of logic like data access logic right you don't want that scatter all over the application either right you want to be able to have a a central place where all that logic lives because it makes it easy to test and it makes it independent from infrastructure concerns then the second rule is that all the dependencies need to flow toward the core project right nothing goes from core toward your UI toward your infrastructure toward external dependencies right everything has to flow toward the core uh and then that gives you this this third rule which is kind of a you know follows on from number two which is that your inner projects are going to Define interfaces that your outer projects then Implement and that's how we're going to use the dependency inversion principle to support uh this this basic setup where uh we have code that depends inward instead of the other way around okay so there's usually about three projects involved you can have four uh and we've talked a little bit about this core project so let's say what goes into your core project inside of a clean architecture solution where where do you put stuff when you have you know these different buckets in which you want to place things and so I've mentioned interfaces and so most of your interfaces are going to live in the core project and that's going to make them available for any other project to access uh or or even the core project itself right so you'll have a bunch of interfaces in there for various things that you need to do um and when you're thinking about interfaces if you haven't used them a ton um the thing about interfaces that they should focus on what needs to happen the implementation of the interface should focus on how right so I might have an interface that says I send email well there's a bunch of different ways I could send email I could send it with SMTP locally I could send it with a third party service like send Grid or MailChimp or or something else um and so how I send it should not be part of the interface right I should be able to implement that interface in a bunch of different ways to do that how all right so the other things you're going to have in here are going to be your actual domain model types uh of things that are going to get persisted generally and that's going to include your Aggregates your entities and your value objects entities is just anything that has an identity so all those things that you store your database that have an ID field those are entities value objects are things that you compare just based on their properties so a date time is example of a value object uh you can store a date time in an entity right and so frequently your value objects will be properties of your entities and then an aggregate is just a way to group together several entities together that have some relationship where they're part of the whole uh you can think of like uh a purchase order with line items or an order with detail rows um something usually like has a master detail relationship and so that hierarchy can be grouped together as an Aggregate and then it get it gets persisted as a unit so when you grab it from the database you get the whole thing and when you store it you store the whole thing um and that allows the aggregate to enforce certain rules or invariant uh as part of the persistence uh operation so you can't just go grab an individual uh line item on a purchase order and update its amount without the purchase order header knowing that hey now it might be over the limit um you want to make sure the aggregate is involved in that operation you may also have some domain Services these are mostly an outlier uh this is where you put logic that can't go in any of your entities or Aggregates or or value objects um and so usually this is stuff that has to orchestrate between uh different Aggregates or entities but you may have domain exceptions um these are domain specific things that might go wrong that you know could go wrong and you want to save future use some headaches by instead of just letting a null reference exception Bubble Up um you're going to create a custom exception that says you know customer doesn't exist exception or something like um and then when you see that exception in a stack Trace you know exactly what the problem is you don't have to go wonder wonder what was null um so it can save you a lot of trouble you might you may also use domain events and domain event handlers uh domain events are things that are interesting inside of your domain that when they happen something else maybe ought to happen right or somebody wants to know about this if you're looking at some of the new stuff coming with net8 with metrics and traces and and things like that for observability domain events can also tie into that really nicely so if you want to know every time somebody you know adds an item to the cart you could have a domain event for that and in addition to other domain Behavior you might also want to hook into that for you know having some type of a trace or or mechanism to see a counter for that um domain events are a really nice way to decouple things so that if you have a big workflow where you know when someone checks out of their cart you have to check inventory process their credit card create an order send them an email send somebody else an email send something to the warehouse right there's this whole laundry list of things that have to kick off um with domain events what you can do is just update the the order that you're creating and save it fire off a domain event that says New Order created and you're done right all those other follow-on things and and the three more that happen you know next in the next few months as additional features um all get added as handlers right so they they don't uh have to get added to the existing function they can be added as additional classes and additional uh Behavior that's outside of the state of the order that's changing that's triggering all of that all of that behavior all right you may also have specifications here specifications are an excellent and underused pattern for defining queries uh and so if you have a problem when you look at your code and there is just link statements everywhere uh where you're trying to to fetch a certain you know type of object or make sure it includes these rows or make sure it has this filter on it uh and and it's in your services and it's in your views and it's in your controllers and it's in your endpoints um everywhere there's link right specifications help you pull that stuff back into your domain model where it's centralized it's testable they're named and they're easy to reuse right so you don't end up with different Blobs of of link code in different places uh you might have validators here you might have enms in here you might have custom guard Clauses that you've defined to make sure that certain edge cases or invariants never happen um all that stuff could be in here and with that let's look at a quick demo of it so we're going to go to this sample project right here and we're going to jump up to this core project and uh look at just some of the folders in here right so there are two Aggregates in this sample and this sample is in my clean architecture uh repo um there are contributors and there are projects uh and and in this example you know a project is uh something with a bunch of to-do items on it and contributors are people that work on items right so think of it like a simple task tracking system all right and so right away when you look at this because you can see it's it's all about contributors and projects you get some idea of what this is like this is not a banking application this isn't an insurance application it's not an e-commerce application it's got contributors and projects and I just told what those mean so you you can see kind of what the design is from there um and then inside of those they may have certain specifications that say how to fetch them right there's a get get a contributor by ID as a specification there's for projects they have children like to-do items so they're hierarchy that's why they're an Aggregate and they have a spec a few specifications where you can say give me all the incomplete items or give me a project with its items um so it just looks like this and so essentially these specifications are very simple but they they have a name and you can reuse them uh as far as the entities themselves they're just standard uh entities but one thing they have is a lot of the the business logic is encapsulated inside of them so instead of having a lot of logic inside your controller your endpoint or some service in between right you are able to put most of your logic inside the entities where you can test it uh and this is just a sample app so there's not a ton of logic here um but but that's the general ideas is that's where it would go if you had more complexity uh there's domain events so when you add an item to a project it has a domain event that's registered here and then you could have domain event handlers I don't have one for that Handler just a second but we're going to see in a minute this uh item completed email notification Handler and so the thing about handlers that's nice is that they can use dependency injection um and so in here I can pass in a service for sending email that it can then use to to send the email now because that that service is just an abstraction I don't actually know how it's going to send an email I just know that's what it wants to do but I I didn't have to do it inside of the place where where it was triggered which in this case was uh right here inside this Mark complete method right so Mark complete is able to run without having to have any dependencies um and still trigger things that need dependencies in order to happen so that's one of the beauties of using domain events all right so that's the core project um if there's questions you know I'll catch them at the end you can always find me on Twitter so let's let's jump back and talk about the next thing which is command query responsibility segregation which is an optional design that you can include in your architecture if you want it has various benefits that I don't have time to get into right now and you can achieve this most easily in a separate project that sits between the user interface and your domain model and this is sometimes called use cases or sometimes a set of app Services all right and so the things that belong in here are going to be uh these sorts of things commands and handlers queries and query handlers various dto that that support these commands and queries uh and behaviors and we'll see what behaviors are in just a second all right so let's look at use cases and in here again same project we're going to look at the uh use cases project right here and here's projects and contributors are the top level things and under there there's various operations or or things that you can do with those domain types um so one of the things we could do is is Mark a to-do item as complete that's a command to do that command I just need to know the project ID and the item that you want to Mark complete and I have a Handler here that's going to accept a command it's a it's a command Handler and it's going to return back some result that's going to say whether or not the command was successful um inside of here it's going to use a specification to go and find the appropriate item or return not found uh and same thing that's that's the project that it's looking up but then uh here is the actual item on the project when it's done it's going to call that Mark complete uh method on the entity itself um and then save the changes when it saves the changes that's when domain events get fired that's when those get handled and when it's all done it's going to return success um so that's an example of how you can easily support cqrs uh some folks might prefer to have a root level folder on here that's like commands and queries so it's really obvious that you're doing cqrs uh I find that it's sufficient to just lead with your actual domain types here uh and then it's pretty obvious in here which ones are queries and which ones aren't like get with all items that is a query right it it's using an iquery type uh and it has a query Handler that that is doing it so you can see from the files themselves whether not their commands queries um but you don't necessarily need to use a folder structure for that if you don't want to all right so now let's jump back here and talk about infrastructure infrastructure is where the actual how things happen happens in your application and so it's going to have things for persistence uh domain driven design uses a pattern called the repository for persistence you're probably familiar with it um it can use any type of implementation for that Entity framework or Dapper Uh custom ado.net whatever you like uh and then by using this abstraction you can easily decorate it with things like caching uh to make it so that you don't have to build caching inside of your your actual services or other places in your code you can just create a caching object that wraps around your repository and adds caching when and where needed uh in the specification pattern that I'm using has a Boolean on it that you can just say enable caching right so any specification that should support caching can just turn that on and if you have a cash repository then it just it gets caching right it's super easy you don't have to change it anywhere um it's going to have API clients for when you call thirdparty apis it's going to have various ways to access the file system or to send emails or SMS maybe to access the system clock uh and it could have other services and even some of its own interfaces but the only interfaces that should go in here are ones that have dependencies on infrastructure so if the uh interface itself returns something like an Azure blob storage item right well we don't want to have an Azure blob storage item in an interface in core because that would tie us to Azure but it's certainly fine to have that as an interface in inside of infrastructure um but it's only going to be used internally here all right so let's look at that uh real quick so in the infrastructure project there's not a whole lot here right I've got a way to send email by faking it I've got a way to send email for real this one's just using local SMTP uh and then under data there's interesting stuff here um there's my EF repository not a whole lot going on here because it's inheriting from a thirdparty uh package and then the appdb context which just has my DB sets for the things I'm using uh and then a little bit of code in here under save changes where it's going to dispatch domain events right and so that's how that magic kind of works in this particular design um inside here the the queries allow you to you know write queries however you want so um you don't have to use repositories for your querying and that's a problem many teams run into is they think oh we're using repository we have to use it for everything and then they bang your head against the wall for hours trying to figure out the proper link query to do some crazy SQL right when you want to do a query you're not returning back your domain model you're just returning back some query results and how you get those results is just whatever the most efficient way is and oftentimes that's just going to be SQL right so just write the query that you need get back the results you need uh in whatever dto uh format you need not necessarily a domain type because you're not doing any domain Logic on these things typically you're just presenting them to the user uh so in the cases where you're just presenting data to the user don't feel like you have to jump through all the Hoops of using a repository to do it all right so that's infrastructure uh in the web project all the web stuff goes there I'm going to go super fast because I'm running out of time and I want to show more code but if it has to do with asp.net core probably goes inside the web project also that's where your composition route is going to be that means that's where you're going to wire all your services up to their interfaces uh you might have other services and interfaces here too same rules apply as infrastructure the only reason you're going to have services and interfaces here is if they have uh elements in them that are tied to web right so if you have an interface that returns back a view model uh it has to live here because that's where the view models are defined um for example all right so if we look at our web project in this example I'm actually using a project called Fast endpoints and fast endpoints is nice because it leverages minimal apis so it has all the speed of minimal apis but instead of you having to have like some crazy long program CS full of Lambda expressions for all your queries or sorry for all your endpoints um each endpoint is just one uh file so if I want to create a project this is the endpoint it inherits from end point of request comma response so it's using the reaper pattern that's request endpoint response R Reaper uh and so these are just using mediator to send off commands that's because I'm using that use cases project if I weren't using that project I would just be interacting directly with the domain model here uh and I would just cut that project out of the mix um but in here these are all very simple these are all just do something with mediator get back a result and return it um so that's create you know delete looks you know much the same take a command send it and take the result return it uh so the point point here is that your endpoints should be super thin uh because they're harder to test than most other things um and so this way you don't have a lot of logic inside of your web layer most of that logic should be inside of your domain layer um let's see there's not a whole lot else here one one thing about this pattern uh if you if you structure it correctly uh is that you can chain this so that all the dto that work with it are are grouped with it inside visual studio and that's just because of the naming convention if you put create dot in front of these types they will they will chain with create so here's the request uh I like to put the route on here too so I don't have that magic string floating around and I can access that from my tests uh here's the response that comes back with the uh the item that was uh created project that was created and if you set up a validator this is using um fluent validation uh this stuff is automatically hooked up with fast end points so just by it existing here it's going to get called as as part of the process all right so shared kernel is something you might share between different solutions it's a DDD term holds common types between stuff uh ideally distributed as a Nate package uh I have all this stuff here in mine uh you should have your own for your company and your teams uh you can grab the one that I use uh which is this our Dallas uh shared kernel that's another n package out there so the sample I've been showing you uses this so like the the base entity type is here um it it goes off of this has domain events base which just has this collection of domain events here that that you can use uh and register and clear and then the dispatcher that's used that I showed you in save changes um is in here as well right and as well as everything else that I use like the the value object type that I use is in here um that that handles how value objects work all right last thing we want to do is just show a quick demo so in app. HTTP if we run the app make sure it's running um just send a request here that's working all right so we can go find a specific project uh like this one I think and it's going to say here's this project and this one's not done but or this one is done this one's not so number two is not done so let's just send a request here to mark an item is complete and and I just got a notification and that notification is telling me that I just got an email that hey this this task review solution was completed a few seconds ago and this is using paper cut which is a local SMTP server that you can use for that kind of testing uh and so you can kind of see that the whole thing went through uh the endpoint hit it P up the entity it marked it complete it fired off a domain event the domain event had a Handler that sent an email I got the email and and everything worked uh just the way you would expect and this is using the new uh HTTP files that you can add in visual studio so you don't even have to use Swagger or Postman all right how much time do I have about four minutes um that was the main thing I wanted to show I think we're doing all right so uh run the sample app and here's some resources and then let's see if there's any questions that was quick that was quick that was great great great job actually I do have a couple there was more conversations and questions but they were I was able to pick two of them up one was from the beginning of the session and this is from Gabriel uh who has is that when would you use clean architecture and when would you use a vertical slice architecture I prefer clean architecture most of the time but I do work in vertical slices so you know the idea of a vertical slice meaning that you're going to build a feature by by building the UI you need whatever Services you need whatever infrastructure you need whatever domain that whole slice through the system that's how I work that's how I ship stuff but as far as the architecture goes I like the the guard rails and and the protections that clean architecture gives me so that I can have the testability so um I don't I'm not usually a fan of of just you know yoloing it and throwing everything in one project and and using the vssa approach so you do it so the way you're cleaning it you do both right so you still do the clean architecture for theu structure and the layout of the way you're doing it but you're saying I'm shipping this slice like from beginning to end that's right that's awesome that's great uh Dan uh fredman asked why do you focus on specifications inste instead of cqrs which is funny because he asked it before you talked about cqs I I again I do both right yeah exactly yeah specifications are a way to keep link from polluting your entire code base right link link is great but there's so many applications you go look at and like everything that touches any part of the system has some Link in it that's manipulating the query and especially if people are passing around I queriable um all of those things could actually be manipulating the actual query that's going to get run and so when there's a performance problem or or any other issue and you're trying to figure out well where is that issue coming from it could be anywhere it could be in data access layer the business service the controller The View anywhere in between yeah I mean link is great but like you said it's it's a it's a slippery leaky abstraction yes right yes and so specifications are great because they're they're they're there they're they're reusable they have a name like the thing about link Expressions they don't have a name unless you like assign it to a variable which no one ever does um whereas specifications they have that name so you can kind of get a catalog of them and see like oh yeah that's the one I need and and you're good to go yeah which is funny because even with link some people and I've done this myself right and it all depends on what the the thing you're doing you may do it more like a query syntax rather the extension method syntax right because cu cu one lens itself nicer for this type of this type of query and the other one lens itself for the especially if you're doing joins and projections and Stu right exactly yeah it's way cleaner to do one way than the other right so awesome well uh there is a bunch of other uh comments and questions so if you want to go look at YouTube Steve you're more than welcome to uh there there's great great content here and I think a lot of people are more asking it's like hey how would you do X how would you do y right which is difficult uh because you always got to know the context in which they're looking at it from Y and and there's a Microsoft sample one last thing called eShop on web that also follows this so it's different from the ESOP they just announced but it's another good sample and it's a and it was one of your resources right that one of the links you showed there great awesome well Steve thank you so much for taking the time a lot of content and we really appreciate you always uh donating your time to that night off
Info
Channel: dotnet
Views: 85,288
Rating: undefined out of 5
Keywords: .NET
Id: yF9SwL0p0Y0
Channel Id: undefined
Length: 29min 17sec (1757 seconds)
Published: Sat Nov 18 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.