Clean Architecture with ASP.NET Core 7 | .NET Conf 2022

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
>> Now we are onto Steve Smith talking about clean architecture with the ASP.Net. Here we go. >> Hey, thank you very much. Welcome everybody. My name is Steve Smith and today we're going to be talking about clean architecture in ASP.Net Core 7 apps. I work for a company called NimblePros we're small.NET consultancy, help a lot of companies moved to.Net Core,.NET 7 and implement architecture and microservices, things like that. I use clean architecture quite a bit. I have a template to help others, including my clients, get started with it. When they're creating a new microservice or creating a new ASP.Net Core 7 app. They can just start from a solution template and it has all the pieces already set up and ready to go. I'm going to talk about a little bit of the theory behind clean architecture. We're going to jump right into the code because we only have about 28 minutes and I have to go really fast. Let's start with what is clean architecture? Clean architecture is the architecture formerly known as onion architecture or hexagonal, or sometimes ports and adapters. It's really meant to be a domain centric approach to organizing dependencies. I have a course on plural site about solid principles. If you follow all of the solid principles, you cannot arrive at this clean architecture style of arranging your dependencies using dependency inversion, relying on dependency injection, focusing on interface dependencies rather than concrete dependencies, things like that. That's mostly what leads you toward clean architecture, along with a healthy dose of separation of concerns. The other thing about clean architecture is that you want to depend on infrastructure as loosely as possible. You're going to have coupling to your infrastructure concerns, your database, how you send e-mails, how you call various web APIs you might leverage. But you want to have those things at arm's length. You don't want to have to do surgery on your whole application just because you change how you send e-mails or you want to be able to test it with an in-memory database instead of an actual Cosmos DB or something like that. Also, by eliminating or minimizing the direct dependency on these infrastructure concerns, it makes it so that we're better able to focus on the business logic or the domain logic if you're using domain during design terminology. Now, clean architecture is not meant to be a one size fits all solution. It's really designed for certain types of applications. It's very closely aligned with domain-driven design. If you're using domain-driven design, cleaner architecture is often a good choice because it does a good job of keeping your domain model very pure and free from dependencies. It's going to be in its own project. That doesn't depend on anything else, which is really nice. If you don't have complex business logic and you're not trying to write testable code, then clean architecture might be overkill. You probably don't need this for a quick demo application or even a lot of internal apps or things that are just a lot of CRUD, create, read, update, delete types of applications with a lot of logic. Clean architecture is useful when you expect to have something you don't want to write unit tests for and have confidence that the business logic is working properly. If you don't have a lot of conditional logic in your domain model, then cleaner architecture may not provided a whole lot of benefit and it maybe a little more cost in terms of the number of projects, number of files involved than if you just had everything in one web project for instance. You want to make sure too, that you're comfortable with the fact that this architectural design is enforcing some of these policies. You might have a general rule that you don't want your domain model to depend on, say, a DbContext from entity framework. Maybe somebody else on your team decides that would be really useful at some point, they want to inject one into a method. If you're just putting everything in one project, nothing prevents that. If you have new DbContext is in one folder and your domain model is in another folder all on the same project. You can just use whatever you want, wherever you want. But when you use the clean architecture solution, you are basically relying on the build system to enforce some of these rules because the core project does not depend on the project where are the DbContext is located or any other data access concerns. There is no way for someone to take that dependency inside the domain model. Visual Studio, VS Code, the compiler, they're all stop you from doing that. That policy is actually enforced by the architecture itself. Now if you don't need that, if you're a solo developer and you know that you're going to do things the right way. When you want to bend the rules or break the rules, you're cool with that and you're going to do the right thing. Then again, you may not need this type of policy enforcement, but you can think about the clean architecture solution structure as being very similar to using a strongly type language or using scoping for your fields inside your classes, having protected or private visibility. You don't need to have any of those constraints inside your code to write software. We could write all our software, which is static global variables everywhere. But we use these scoping rules and these constraints and these type systems to make it less likely that we'll make mistakes. That's exactly what this architecture does for you. Now, typically, there have been two approaches to architectural layers. The two most popular, one of them is something that looks like this, where you have this N-Tier, N-Layer architecture, where each layer depends on the layer below it and ultimately the data access layer is the lowest level code layer. It depends on the database. What clean architecture does is it flips this around a little bit so that the domain model is the lowest level dependency. Everything else depends on it. The database is off to the side. It's not the key focus anymore. You can see here's an example of N-Tier architecture. This is actually from 2001 from Microsoft MSDN website, it's showing guidance on how to move from classic VB6 or ASP things where everything was managed together in one file toward some more layering and some more separation. Then here's an example of what hexagonal or ports and adapters architecture looks like, where the domain layer is the center of the universe and it exposes these ports or these interfaces, though then implemented by adapters to do things like talk to other web services or communicate data to a database, things like that. With our clean architecture approach that we're going to use, we're going to have our core project in the center, and that's going to have our domain model as well as the abstractions that we are going to use, different interfaces. Around the outside of that, we're going to have the web project, which is how applications are going to interact with that. If you've got a blazer, or angular, or whatever type of front end needs to communicate with this web project. That's going to be one of the external things that depends on the core project. Then we're going to have also the infrastructure project separate. It is responsible for all of the other things you're going to collaborate with. Any types of web services or Azure services, databases, especially all those things are going to be inside of the infrastructure project. Then if you have tests, which of course you should, those will be in separate projects as well. Testing the core project as well as other projects. I mentioned that there is a template that you can use to go in and get started with this, you can grab it right off of nuget.org. It's totally free. It's really easy to install. If you go to nuget.org, you'll see the instructions right here to just say, dotnet new- -install and then specify the template and version. This one is for.Net 6,.Net 7 will be available in a short while as soon as we get that published. Once everything is out of prerelease. Once you have the template installed, you can just say dotnet new and then clean-arch A-R-C-H to create a new template. We'll see that in just a second. But first, let's review the rules. The rules for clean architecture are first of all, that you're going to model, all the business rules and all of your entities, the types that you're using, your customer or product or things like that. Those are all going to live inside the core project. Then the second rule is that all of your dependencies flow toward the core project, not away from it. Core doesn't depend on other things, especially not infrastructure. Then lastly, we're going to say that the inner projects are responsible for defining any interfaces that we need and an outer projects will implement them. Sometimes the same project will implement interfaces that are in it. We'll see that in a minute. Let's start with the core project. Again, this is the innermost project. This is the one that has all your business logic in it. It's going to have things like interfaces. It's going to have your domain model aggregates if you're using those and entities, it'll have value objects, which are other domain model types that don't have an identity. They wouldn't like date time and string. You can compare those based on their values, not based on an ID. In domain services, any custom domain exceptions you're going to have with all live inside of core if you're using domain events and there are event handlers, these frequently would be in core as well. Specifications which are really nice pattern to use for data access. I have a nuget package for that as well. You should check out. Combining specification with repositories for data access makes it so that you can have really powerful data access on top of entity framework without having to constantly write additional repository classes or add additional repository methods. If you've tried using repository and found that part of it to be annoying that you can't say and add more classes or methods, checkout specification because it totally solves that problem. You may have custom validators using fluid validation or something similar. You may have enums or smart enums as well. You could even have custom guard clauses or other utilities that are inside this core project that represent the business logic of your system. With that, let's do a quick demo of the core project as well as how to get this template setup. I'm going to go ahead and just bring up the Terminal here. >> I have not yet installed the template yet, so I'm going to do that right now in my Scratch folder. I've install the template in my local machine. I haven't created a new template for this. Then we'll cd into DotNetConf.2022Eddition, and we can just take a look at what that created for us. Now when you create things with this template, the nice thing about it compared to just downloading a zip file from GitHub is that it removes everything for you. The fact that I named it DotNetConf.2022Eddition means that that's what all my namespaces and folders and things will be, so you don't have to do that tedious work of renaming that stuff. We can obviously do this in code. Visual Studio Code will work just fine. We'll bring this up and we'll open it in Visual Studio as well in just a moment. Inside of this, you'll see we've got four folders. We have our core, our infrastructure and web, as well as something called shared kernel, and we'll talk about that in just a minute. Now, inside of here we're going to take a look at core. Core has a couple of top-level aggregates so you can organize the inside of these projects however you want. But my recommendation is to, rather than organizing them by the type of thing it is like entities, value objects, events, interfaces. I prefer to organize it based on the type of application that I'm building. In this example, out of the box you get a little bit of sample code just to show you how the patterns work and how things fit together. You can totally delete all that stuff. It's just part of the template, but it shows you how you might organize things, and so within the contributor aggregate, there is the contributor entity here, and it's going to just be a class that inherits from entity base. It has a few properties and things on it, and it also has these related events. Inside the events folder, there's this notion of a customer deleted event that just takes in the ID of the thing that was deleted, and so if you're using domain events, it makes sense to put the domain events that relate to a particular aggregate or to a particular entity near it, rather than just putting all of them in the one top-level events folder, for example. We have interfaces for different services and things we want to use. A lot of the interfaces we're going to use are pretty common, and so they're actually defined in the shared kernel, which we'll talk about soon, and then the project aggregate is a more complicated object graph. It has a project at its root, and then each project has a list of to-do items, and so to-do item is another entity that's also inside the same aggregate folder, and it just has things that you might have on a to-do list, like a title and whether or not it's done and who the contributor is. Inside of here you've also gotten more events. You've also got some handlers for those events and then some specifications are defined. Now I don't have time to really talk about how the whole specification pattern works. But essentially each specification allows you to define a query in it, and that query can then be translated at runtime using Entity Framework Core or another RM so that you're able to capture the query logic in your domain model and not have this query logic spread all over your controllers or endpoints or wherever else they might be. That's pretty much a quick overview of what you can do inside of the core project. The last bit here is just some domain services that you might have. For instance, here's a service for deleting contributors, and the reason why that's a service is because it wants to also publish an event. Rather than just calling your repository and say delete this thing, we wanted to make sure that when we get to delete this thing, we also are going to publish an event, and in this case it's using mediator for that purpose. Let's jump back to the slides and move on to the next section, and that is going to be the infrastructure project. Inside of our infrastructure project, what are we going to have in there? We're going to have anything that has to do with external dependencies as well as implementations of some of the abstractions that we might have created. If you're using domain-driven design, a very popular design pattern from DDD is the repository pattern, and so the repository pattern is not any framework. You'll hear people online say, well, we don't need to use repository because MD framework implements repository. Well, that's true but the whole value of the repository pattern is that there is an abstraction that you own, that is part of your code, and so Entity Framework does support adding and removing things from a DB set. But there is no interface there that's yours, that's the frameworks abstraction, and so you're not following the repository pattern by leveraging some third-party framework, you're only following the pattern if you have that abstraction. Your implementation of your abstraction would live inside the infrastructure as these repositories. You would also have your DbContext if you're using Entity Framework would live here. If you're using Dapper or using some other tool for doing data access, that implementation would live in this project. If you're using caching, using decorators, which I highly recommend. These would also live inside of infrastructure and would use whatever caching technology, whether it's a memory cache or distributed cache on top of your repository implementation, and caching isn't only for repositories. The other thing you might be using is third-party APIs, where maybe you're sending emails, maybe you're talking to GitHub API or Twitter's API or whatever it might be. You might also want to add a caching decorator around an API client. So keep that in mind when you're creating these API clients. If you have stuff that you're reading frequently and it doesn't change too often, especially if you've got limitations on how frequently you can make those requests, caching can really help you here in the decorators the right pattern to use for that most of the time. If you need to talk to the file system or if you need to talk to Cloud-based storage, all that stuff is infrastructure. If you need to send emails or SMS or other messages, that's all infrastructure. You don't want to have hard-coded into your domain logic or in your web UI. Anything that says, hey, I'm going to talk to a system DotNet, DotMail and use SMTP to send this email. Rather, you want to have an interface for sending email, and one or more implementations of that interface would live in infrastructure. Maybe when you're on localhost, you don't actually send emails and you have some fake version. But when you're in the Cloud, you're going to use SendGrid or whatever it might be to actually send emails, and it's really easy to swap between those in your dependency injection if you've got both those types sitting in your infrastructure project. If your logic depends on the system clock, it's often a good idea to have an abstraction for this, an ID time or an iClock interface, and then inside of infrastructure, implement that and it's really trivial to implement it because typically your interface is going to have now or today on it, and you're just going to say, Well, here's a class that is the system clock date time that implements the ID time interface, and by the way, it's now property is just datetime.now, so it's trivial to implement, but it can make it much easier for you to do some testing around the system clock that would otherwise be very difficult. You may have other services in here as well with dependencies, and you may have certain interfaces that are needed for those services that have dependencies. If you have a service that works with Azure, let's say, and it's using Blob storage types or something like that, and it's passing around as an argument or as a return type, a custom Azure type from an Azure SDK, the interface for that service should live inside of infrastructure. Because if you put that interface in your core project, now your core project would have to have a reference to that Azure SDK in order to work, and we don't want to have that dependency in core so we're going to keep those interfaces and their services inside infrastructure. Now let's take a look at the infrastructure project, and at this point, I'm going to go ahead and create openness up a Visual Studio, so let me just open project or solution, and we'll go find the DotNetConf.2022Eddition. >> Now I didn't like that. I think this is the preview build. Let me just check that real quick. This is 174 preview or thought this would work. But note doesn't look like it likes this project for some reason. I'm not sure what's going on here, but we'll just keep with code. Let me switch back to VS code and we'll just continue on. Next thing we're looking at is infrastructure. Inside of infrastructure you can see we have things like an SMTP mail sender that knows how to send e-mails using SMTP, the I e-mail sender is defined inside of core, where as you can see we have core interfaces up here. E-mail sender just has this SendEmail async. Then here's the implementation details for sending with our local SMTP client. But if you wanted to wire this up to use fake e-mail, you have this one as well. It's not at all unusual when you're using clean architecture and you're using a dependency inverted architecture like this one, that you would have more than one implementation of an interface that you could swap between depending on which environment you are in, what you have in your personal, like local development environment might be different than what you're going to set up in a shared dev environment that you deploy two or staging environment or production environment. By having these different versions available, you can easily have the right dependencies running in each environment as you go. In addition to that, you're going to have any of your data needs. This is going to include your dB Context. In this example, the dB Context just has these three types of entities that come out of the box with a template. You can delete all this, but it's just there to show you how would you set up. You've got to do items, projects, contributors. In here also is an implementation of how we're going to send out domain events after we save logic. You'll see this overridden Save Changes async method here. Within this, this is where we're going to check and see if any entities have any events on them. If they do, we are going to dispatch those events, in this case using mediator. That's how some of these domain events occur when you save something inside the system, it triggers something else happening elsewhere in the system. To really nice pattern for decoupling logic inside your application through the use of events. If you're already familiar with events from userInterfaceStyle events like click events or load events, it's the same idea but applied to your business logic. Then our repository here, notice this is our type that we own and it's referencing interfaces that we control. This is using the repository base in this case is coming from our data specification. But I'm inheriting it and I can override or change anything I want. This is open source, so I could totally forget it or change it and do whatever I want with it. Even though it is a third party library here, I have control over it. Then these interfaces here are again, things that I have control over and I can modify. Anything I want to do to change how my repository behaves, or if I want to add additional methods or whatever, I have that control to do it here. Now, because the specification one pretty much comes with everything you generally need, most of the time for most of my clients, we don't change this very much. But the important detail is that you have that power to do it. You don't couple your entire application everywhere to a framework or third-party library implementation detail like a dB context that will make it more difficult for you to change that decision in the future, to extend that behavior using caching or other things, and to be able to modify it based on what environment you're in. The other thing that you would have in here are your any framework configuration files. You can specify how the types should be configured with EF and how they map to the database. That's infrastructure. Now let's look at the web project. The web project is your standard ASP.Net core project. It's going to have anything, it's an ASP.Net Core type. It's going to have your API endpoints, whether you're using minimal APIs or controllers, or an API endpoint package like one of the ones I've published. If you're using server-side rendering, you might have Razor pages, you might have controllers and views. Any types of DTOs like API models, ViewModels, if you're using the Reaper pattern, the request endpoint response pattern, those requests and response details, would all live here using filters, model binders, model validators, tag helpers, any of these ASP.Net Core ideas or concepts or patterns, all of these things would live in the web project because that's where ASP.Net Core lives. Now this also serves as your composition route for the application. This is the application entry point. The composition is where your dependencies are composed. Inside of program.cs, you're going to have the service collection is going to wire up all the services that you're going to use. It's going to specify service lifetimes like transient are scoped, etc. It's going to happen in the web project or it's going to be kicked off by the web project. You may have other services with their own interfaces here as well. Again, this just depends on if those interfaces rely on ASP.Net Core types or types that are defined in your web projects. If you have a service that returns back any API model or a ViewModel, we can't put that service in core or an infrastructure because those DTOs are defined in web. We're going to put those services and their interfaces inside web as well. Now let's do a quick demo. Take a look at that. Inside of the Web project here, we've got API models, different DTOs. We've got an API folder that's using API controllers. The idea here is when you install this template, you're probably only going to use one type of thing. You're going to be doing APIs or you're going to be Razor pages, you can do views. Depending on which flavor of ASP.Net Core you want, your going to keep that and delete everything else. If you'd like API controllers and that's how you want to do things. There's an example of a project's controller here. You would keep this folder and it's related folder and we delete the rest of it. If you want to use controllers and views, you can do the controllers folder has things that return views. You keep that along with the views folder. If you just want to have API endpoints, you can do something like this where you've got this create endpoint and this is using the Dallas API endpoints project. It will basically create one file per endpoint, which have really nice way to do APIs. There's another one that is using a different library, and this is the fast endpoints library. This one uses the new minimal API structure. It's actually a little bit more performance than API endpoints, but has a slightly different way of working with it. You configure it in a configure method, and then you still have a handle async method. It's basically using the same Reaper pattern as API endpoints. If you have custom filters they'd go in here, you've custom razor pages, V-models, reviews, all that stuff lives inside the web project. We're just about out of time. Let me cover one last thing and that is, what do you do with the code that you want to have in every one of your solutions. That is what DDD calls a shared kernel. It's a Domain Driven Design term for holding these common types and typically it's referenced by core. These types could be used by anything. The best way to distribute this is as a nuget package. Inside of the template I have a project for shared kernel, but you should take that and publish it as your own nuget package from your own internal corporate nuget server. What goes in here, there's going to be all base types, especially DDD base types. Your base entity, your base value-added, your base domain events or specification. Other common interfaces, common exceptions, off types of things like, user types or things like that. Coming Guard clauses, other common libraries you might use for DI. If everywhere you're using DI, you want to use artifact, let's say then that might be inside here. If you have certain logging library you want to use everywhere, certain validators, all that stuff could live in shared kernel. However, you don't want to have any infrastructure dependencies. You just want to make sure that you don't keep infrastructure inside shared kernel at all. It's even more important than core because all the different solutions that rely on this package, all of their core projects are going to rely on this. It's even more important than a core project that you don't include infrastructure dependencies in your shared kernel. Let's take a look at what's insured kernel here that comes with the template. Again, you would put this in your own nuget package, but it just has base classes. Domain event base and the implementation of the dispatcher for sending domain events that in this case uses mediator. But you can swap that out with something else if you prefer a base entity class that in this case uses integers, but you could modify it to be an entity of T so that you could have integer or good support or string or whatever you want to use for your key type and implementation of value objects here as well. You've got those things in here as well as some basic repositories for aggregate roots, repository and domain admin dispatching. With that, I am out of time. Some resources here for you real quick. You can get the clean architecture template from nuget or you can see the source on GitHub under our Dallas clean architecture, you can also check out the eShop on web reference application that's available from Microsoft. It's also on GitHub. It's also using clean architecture. We'll show you how a real application not built analyzing it. There's a companion even if it goes with it, which is linked to right here. It's also find it from that repo. With that, thank you very much and hope you're having a great.NET 2022 and I will pass it back to you.
Info
Channel: dotnet
Views: 100,199
Rating: undefined out of 5
Keywords: .NET
Id: j6u7Pw6dyUw
Channel Id: undefined
Length: 27min 58sec (1678 seconds)
Published: Mon Nov 14 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.