Clean Architecture with ASP.NET Core 3.0 • Jason Taylor • GOTO 2019

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Check out this talk from GOTO Copenhagen 2019 by Jason Taylor, solution architect at SSW. The full talk abstract can be found below:

The explosive growth of web frameworks and the demands of users have changed the approach to building web applications. Many challenges exist, and getting started can be a daunting prospect. Let's change that now.

This talk provides practical guidance and recommendations. We will cover architecture, technologies, tools, and frameworks. We will examine strategies for organizing your projects, folders and files. We will design a system that is simple to build and maintain - all the way from development to production. You leave this talk inspired and prepared to take your enterprise application development to the next level.

What will the audience learn from this talk?
The audience will learn how to build enterprise applications using ASP.NET Core 3 and following the principles of Clean Architecture. This talk provides practical guidance and will cover architecture, technologies, tools, and frameworks.

Does it feature code examples and/or live coding?
The talk features code examples and the attendees will get a link to the code repository on GitHub.

Prerequisite attendee experience level:
Level 100-400 There is something for everyone from beginner to expert.

👍︎︎ 2 👤︎︎ u/mto96 📅︎︎ Apr 21 2020 🗫︎ replies
Captions
[Music] welcome to clean architecture with asp.net course 3.0 my name is Jason Taylor and I'm an SSW solution architect I've been developing now for 19 years and I've learned that the most important principle is kiss or keep it simple stupid and today I'm going to show you the simplest approach to enterprise application development with clean architecture let's get started so with clean architecture the domain and the application layer are at the center of the design this is known as the core of the system well the domain contains enterprise logic and types and the application layer contains business logic and types the difference being that the enterprise logic could be shared across many systems whereas the application logic or the business logic is specific to this system now rather than have core be dependent on concerns such as data access and infrastructure we invert those dependencies so presentation and infrastructure depend on core but core has no dependency on either layer now this is achieved by adding abstractions or interfaces within the application layer which are then implemented outside of the application layer in other layers so for example if we wanted to implement the repository pattern we would add an irepository interface within application and an implementation within infrastructure now with this design all dependencies point inwards so you can see that core has no dependencies on other layers now presentation and infrastructure depend on core but not on one another and that's very important we want to make sure that the logic that we create for this system stays within core if presentation for example took a dependency on an infrastructure so that it could send some notifications that's logic and that logic now has to appear within the presentation layer and it has to orchestrate that interaction between presentation and infrastructure we don't want that to happen because we can't reuse that logic we want a system that's going to last 20 years and if we've got a front end with an asp.net core web api well that's not going to be there in 20 years we need that logic inside of core where it's isolated from all of those things so this results in architecture and design that's independent of frameworks it doesn't require the existence of some framework or tool it's testable and it's easy to test everything in core is isolated from the outside world and it contains your most important system logic so we can 100% unit test that logic it's independent of the UI so right now as I mentioned I'm using asp.net core Web API with an angular front-end but I might want to change that we're all getting a little bit sick of writing JavaScript right so we're going to switch it over to blazer soon and with all of my logic inside of core that's not going to be so hard it's independent of the database right now we're using sequel server with the same design we've also used Postgres sequel Lite and we're going to try it out with cosmos DB as well it's independent of anything external in fact core doesn't know anything at all about the outside world and that's what makes this design so great we've got all of our logic encapsulated within core isolated from the outside world and that will make a difference between a system that lasts three years and a system that lasts 20 years let's take a look over a couple of examples when I first started talking about clean architecture last year we use Northwind traders as a sample why because Northwind traders is cool and I wanted to show the world now I wasn't wrong this repository on github now has over close to 3,000 stars and almost 1,000 Forks and as a result of that there's a great big community behind this project and I want to take a moment to thank them thank you for your support thank you for your pull request thank you for the tough questions you've really helped to evolve this solution to become what it is today so based on this I've gone ahead and created a clean architecture solution template so I wanted a very simple file new project experience right coming back to kiss keep it simple so if you want to excellent file a new project experience with a clean architecture solution you can use this template if you want something cool check out an author and traitors let's take a look at an example so you can see here on my github repo you can access the Northwind traders solution or the clean architecture template one second please there we go okay too slow okay let's try that again there we go so this is the clean architecture solution template on github you can see that it's set up as a github template so I can actually just ctrl click on that and create a new repository from the clean up texture repo so just give it a name and I'm ready to get started but for my experience I wanted a actual dotnet core solution template so to get started you can actually run dotnet new install clean architecture solution template and then to create a project you can go dotnet new CA solution let's have a quick look at that now so if I create a new folder go to Copenhagen jump into that and then we go dotnet new this only has to be done once or when you want to update the package clean architecture dot solution template so we can install the new project template that'll install and then it will give us a list of all of the project templates that are available from the command line so if we scroll back up here you can see the clean architecture solution template at the top and it's short name is ca - solution so we can run that now and that will create the project so what it actually does is create a project with on all of those layers and all of the infrastructure in place ready to get started because this is something that I was doing manually each time I had to create a new client project so if we have a quick peek in here you can see that it's using the correct name spacing also based off the project term folder that we used jumping into source you can see that we have four folders domain application infrastructure and web UI and then jumping back into tests you can see that we have test projects for each of those layers as well so what you get out of the box is a template that gets you up and running quickly we've already wired up kind of the database and asp.net core identity we've provided solutions of everything that goes into the application layer a domain layer an infrastructure layer so it's really easy to get started so I won't run that one because it'll have to restore a bunch of dependencies but we can have a look at this one here so you can see here's the clean architecture solution template in Visual Studio basically ready to go there's a readme which explains a little bit about it and shows you how to get up and running that's the same readme from the github repo that's it so very easy to get started ok so key points of this section so the domain contains enterprise-wide logic and types and that can be shared across multiple systems application layer contains business logic and types and that's specific to this system infrastructure contains all external concerns and previous versions of this talk and in previous versions of the northland trader solution I separated our persistence and infrastructure into two different layers but I found it simpler just to bring them together I mean what's simpler than two projects right one project so I merged it together a lot of the dependencies were similar so that was easy so presentation and infrastructure depend only on application but not on each other and infrastructure and presentation components can be replaced with minimal effort let's take a look at the domain layer so within the domain layer we have entities value objects enumerations logic and custom exceptions we're going to have a look at each of those in turn so for the remainder of this talk we're going to be looking at each layer and I'm going to be making key points on these layers so that hopefully if you if you take a look at this project or the template later you'll be able to get up and running quickly and understand how everything fits together so the first thing I want to show you is this to-do item entity so I've got a really nice to-do item sample in this project I should show you that now so if you go ctrl f5 f5 on this solution this is what you'll get you'll get basically the starter angular template with a complex to do this component so it allows you to manage numerous lists and numerous to-do items and there's lots of nice little features in there for working with lists and lists options and to-do items and and the wonderful thing of course about building a to-do list is that you can also use it to store your requirements as you go once you get it to a certain point so you can see all the things that I've checked off there I haven't asked for a code review that's probably that's probably bad I did I actually I did ask for a code review but it hasn't been done yet so let's have a look I also have open API integrated into this solution so you can click on API access swagger UI and it's fully integrated and fully automated so with the solution when you go down to a new CA solution it's set up so that it will generate the open API specification automatically it will render swagger UI automatically it will also generate an angular typescript client and all of the Associated data types needed to run the front-end I'll have a look at that later but the first thing I wanted to look at was this to-do item GTO so one of the very simple key points that I make is that you shouldn't use data annotations within your domain model and because we don't want to clutter our domain model with data annotations in the past they were used for validation and for object relational modeling by EF core but nowadays EF court doesn't use it for validation or it only uses it for the object relational mapping and the alternative is to use fluent API configuration which provides a much more powerful configuration system anyway so we can just go ahead and remove those and I also want to talk about value objects so in this solution I've got an example of an ad account value object okay and so you can see that a value object is a complex type and it doesn't have an identity and why would I create an ad account value object player I could store an ad account just as easily as a string right and that's what I want you to think about when you're defining your entities and you define something that's a primitive type think about is it really a primitive type do all strings represent all value objects or are sorry do all strings represent all ad accounts or a ad accounts a little bit more conflict than that and the answer of course is what ad accounts are more complicated they consist of a domain name and a user name in a certain format and when we when we work with an ad account we often want to access say the domain name or the user name or the whole thing and so if we use a string we have to write logic associated with interacting with that value object somewhere in our system now new developers are not going to know where that logic is and it's going to appear all over the system but instead using a complex type such as an ad account value object we have a place to store all of that logic and that makes it very easy for users to work with our system they don't have to think how can I access the ad account just user name part there's a property for that unfortunately with any framework or one of the features that has is the owned entity types and it's very easy to then configure that value object in using the fluent API configuration so you can see here we have a model builder entity called order that owns one shipping address and what's that's going to do is it's actually in this order entity when it relates that to the relational model in the database it'll store an order table or an orders table with a column called ID and then it will have a column called shipping address underscore street and shipping address underscore city so energy Framework court handles all of that for us so when we use these value objects in this way and we configure them using the owns one type we don't have to worry about converting that back from our object model to relational model and the other way it's very simple so it's a great approach so one of the things that I wanted to mention and this is again about making it easy for new developers is to always initialize your collections so you can see here I'm initializing the items collection and also recommend to remove or simply set they set it to private and why do we do these things it's about forcing developers into the pit of success we want to make it easy for them to do the right thing and hard for them to do the wrong thing and so if we don't do this new developers might say I needed to do list so I'll go over I'd to-do list equals new to-do list and then I'll initialize the to-do items collection because that's what they've been used to doing but if we set this to private they actually can't do that and what's more they never have to worry about whether it's initialized or not because whether they're creating it themselves or it's coming back from a of course that collection is ready to go so there's something that they no longer have to worry about it makes our code simpler more concise one of the things that I didn't show you in the ad account value object is an example of a custom domain exception so you can see here that if for some reason I failed to create the ad account from the account string it throws an ad account invalid exception why would I do that well it's simple the ad account invalid exception is much easier to understand and debug than an index out of range exception okay so we can create these exceptions for these expected domain type events I've also added a few features in here just to help people get started there's the auditable entity base class any entity that derives from that will automatically have those fields populated when the changes are persisted to the database so it'll store the ID of the user who created the entity the date and time that they created it and if it's being modified the user ID who modified it and the date and time that they modified it and all that's necessary is just to derive from that property it's kind of a starter sample you can definitely build that out into something more complex now there's also some sample unit tests associated with this at the moment it's just for the value objects but at least it shows you how to get started so you can see that when you're working with a value object it's really easy to create one based on an account string you just say a count of four we've got a two string method which we can ensure returns the correct format we have an implicit conversion operator which makes it easy to convert from an ID account to a string just by our assignment we have an explicit conversion operator which makes it easy to cast from a string to an ad account and we have this exception which thrown in the case that the ad account string is invalid so when we talk about the value objects and all the logic encapsulated within it you can see that we have a really simple place to encapsulate a whole bunch of logic that we don't have to have in some helper class that new developers won't be able to find okay so the key points so avoid using dart annotations they clutter our domain model and we can use fluent API configuration which we'll see when we look at the infrastructure layer use value objects where appropriate I just want you to think is it really a primitive type or is it more complicated than that well I have logic associated with that type create custom domain exceptions to make it easier to work with your system initialize all collections and use private setters again to make it easier to work with your system and automatically track changes with the auditable and C base type so now I'll have a look at the application layer so within the application layer we have interfaces our interfaces that are defined within the application layer and then implemented in the outside layers such as infrastructure our models have view models and DTRS our logic commands and queries we'll talk about that shortly our validators and of course again our custom exceptions so everyone by now has heard of CQRS but if you haven't it stands for command query responsibility segregation and with CQRS we separate our reads from our rights when people talk about CQRS they say it's great it maximizes performance and scalability but for me it's all about simplicity because we're CQRS it's easy to add new features I just add a new command or a query and it's also easy to maintain my changes typically only affect one command or query and that means that I have less braking changes what's more is that if your audience CQRS you should also use mediator those two are like the perfect couple so with mediator we define our commands and queries as requests and that means that the application layer is just a series of request response objects and that might not sound like a big deal but it's what happens next it's the ability to attach additional behavior to any request that comes into the system so with mediator we can attach behavior before and/or after each request so if we need to implement some cross-cutting concerns in the application layer then we can do that very easily so for example if we wanted to validate every single request that comes in through the application layer well we can do that and that's already in this solution so let's have a look ok so you can see in the application layer I basically have a single common folder and then three feature folders so within the common folder I have some behaviors so there's a mediator pavis for those cross-cutting concerns some custom exceptions some interfaces for interacting with the infrastructure some mappings we'll talk about that later and some some models those are shared models but inside of this to-do lists folder I have some commands and queries you can see everything's very neatly organized and easy to find so I've got a query here I've got an export to do this query which will export a CSV export to do list as a CSV I've got to get to do this query let's have a look at this one this one's kind of nice because it's made up of DT OS and view models so you can see here I have a get to do is query and it consists of a query which could also be a DTO so the query itself is a DTO now I don't have any properties that I'm passing in with this query but it wouldn't be much to say ad pagination and say we start to add things like page size and current page and that sort of thing ok some of the commands have properties that you can have a look at you can see that I've nested their handler so with mediator when we define our commands and queries as a request we're basically defining an I request which is the query and an I request handler so it separates the request from the handling of the request I'll just zoom that in a little bit more and so you can see I've nested this and this is something that I've done to improve the discoverability of the queries so when you actually go to the to do list controller that's where the goon that's where the query is invoked and you can see here that we're using mediator to send the query and we all have for this one because there's no properties we just specify get to do is query if there were some properties like the pagination then we could just pass that back from the client as follows or you could use a request object if you prefer but what I found is that when developers saw this they would go control f12 on get to do is query and they would come to a file that contained just the query so it was hard to kind of understand and then find the handler actually have to go to solution Explorer and then go one down and double click on the handler so by nesting the query or just by putting the two classes in the same file you improve the discoverability which is great so let's have a look at this query so we've got a query which is a request and it's going to return a to dues VM the today's VM has everything that the view model will require to render there to reduce view which includes a lookup list of priority levels and the to-do lists themselves which is of type to-do list DTO and to-do list eto contains the DTO properties wait there we go so the query returns to dues VM and then the handler says ok this is the get to do is query Handler and I handle requests of type get to do is query and I return it to dues VM then down here we've got the constructor or injecting our dependencies and here we have the implementation and so the thing that I really like about this the thing that makes this very simple is that this is the business case of getting a list of to-do items okay so obviously there's some requirement behind that the business has come to me and said they want to implement that sorry I pressed l5 I think and I've been able to encapsulate everything that's associated with that business case in this one file and what's more I also have all of the files that are associated with this business case in this one folder so what I need to change this I come to this single folder and everything that I need is here I don't have to jump around to multiple folders I don't have to jump around to a DTS folder or a view models folder I don't have to go to a handlers folder or a queries folder it's all here and what's more because it's co-located like this I'm actually discourage the sharing of this code I don't want people to reuse this code I want this code just to be used for this business case and you might think that's kind of strange but if I do that I don't have breaking change and if I start to share it pretty soon we're going to start to see conditionals a period in there and they're going to be conditionals for seperate use cases for certain queries and it's just going to make things more complicated than it needs to be so i co-locate these things together i make it easy to change and i avoid breaking changes and i think that's really good approach so you can see in here we have our commands everything's very nice and easy to find we've got to create to-do list command it's also very simple we've got our DTO all we need to create a to-do list is a title and we've got the handler and that's got all of the logic associated with it and creating the to-do list so I want to show you some of the cross-cutting concerns so if we jump into behaviors you can see I have three behaviors implemented I have a request logger that logs every single request that comes into the system I have a request performance behavior that logs warnings for any request that takes too long and I have a request validation behavior that validates every single request that comes through the system it's very simple to set these up you can see it's going me now with just a little bit of code I'm actually logging every single request that comes through the system including the user ID who initiated the request the username and the request itself and that's not just the name of the request it's the actual DTO it's the request and all of the arguments that were associated with that request so if I have a problem I can go to the log I can grab that DTO I can create a unit test I can see that test fail and I can fix it it's it's a simple thing it's 32 lines of code but with it comes a great amount of value because remember everything in the application layer is represented as a request all of that all of the requests come into the system either through a commander or query and I'm logging all those what's more I'm validating all of those and so I have a request validation behavior so this runs before the request and it basically says okay for this request this dto this command or query try to find any validators and if there are validators associated with it collect up all the errors if there are any and then if the failure count if the error count is not equal to zero then throw a validation exception and so every single request that comes into the system is automatically validated all that my developers need to do if they want something to be validated it's creative elevator so here's an example of a validator this is using fluent validation and you can see that it has a rule for their title as part of the create to the list command it says the title shouldn't be empty it should have a maximum length of 200 characters and it must be a unique title and you can see here I'm injecting in my application DB context which is my interface to my actual DB context and I'm checking the collection to see that that is a unique title so this is some really powerful validation that I've created here and that's an important point well we can use data annotations for validation they tend to only be suited for simple scenarios and when we want something a little bit more complex then we can use flow and validation and it provides the best the best result for simple scenarios and more complicated scenarios so the other thing that I've actually implemented in this in this new version in this template is some mapping behavior so with Auto mapper I've done it in a way that I'm super happy with the result and I've backed it by unit tests so that any of those runtime exceptions that used to kind of cause headaches with Auto mapper I'm less likely to encounter let me show you so if we're going to this to-do lists query and have a look at this to-do list dto you might have noticed that it's implementing IMAP from two dualist and so that's basically saying this to-do-list DTO can be mapped from a to-do list and auto map it handles the rest now I also have the to-do item DTO and it says I map from to-do item but actually it's not going to be a convention space mapping because Auto map is all about conventions it'll do a lot for you if you follow the conventions but then you have to specify a configuration so you can see that while the to-do lists DTO didn't have a mapping method the to-do item GTO does have a mapping method and just specifies some configuration on how to map the priority field now this is actually made possible through c-sharp eight interfaces they provide default implementations so I can actually say for classes that implement this interface if they don't specify an implementation for this mapping method then just use the default implementation and the default implementation basically says well this is just going to be an automatic inventions based mapping so create a map from the entity to the derived type which is the DTO so I don't actually have to specify anything just implementing an eye map from and the entity name is an office what's more I didn't want to wire all those up manually so I've created this mapping profile and added this apply mappings from assembly method so all of these DT OS with the eye map from being implemented I've got a method that will just scan the assembly find them all actually invoke what's called the mapping interface and the mapping will then add that map to the profile so auto map knows about it so then finally when I go to use that mapping in Mike get to do list query I can just say project to to-do list DT oh and it does that nested projection for me without me writing more code than a little bit of configuration for the priority over to-do item so I'm really excited about that but I couldn't have written that without writing some unit tests and so in here inside of application unit test I have some mapping tests and they're very simple I have one test which is a is actually a test provided by the automat framework that basically just says assert that the configuration is valid for all of the map types and that will basically say if there's any properties that are not being assigned to fail this test and that's going to avoid a runtime error and that's great and then I have this test which is a theory and I just specify my expected mappings and all this test to is created an instance and then try to map and if it fails we know something's gone wrong with that mapping and so I protected myself from probably eighteen ninety percent of the problems that I could run into with this technique also very happy with that now I have this dependency injection class in the application layer which is basically just an extension method on I collection services and what that allows me to do is to just have an extension method that wires up all of my application dependencies so that I don't have to do it in startups es the startup dot CS tends to get very bloated if if you're just using the built-in dependency injection so you can see here all I have to do is say services don't add application excuse me that's very nice so last thing to look out in the application layer is just unit tests so you can see this unit test for the mapping behavior there is unit tests for the commands and there are unit tests for the validators as well so you have good examples of how to test all of those elements inside of core all right so key points using CQRS and mediator simplifies your overall design and what's more mediator simplifies the cross-cutting concerns with that pipeline of behavior fluent validation is useful for all validation scenarios so not just simple scenarios like data annotations or a mapper can be used to simplify your mappings and projections and this application layer is independent of infrastructure concerns we interact with infrastructure only vial of interfaces so now let's have a quick look at the infrastructure layer so within infrastructure we have our persistence concerns our identity concerns so using asp.net for identity file system we've got some examples of CSV files the system clock we've got an abstraction for working with the system clock because that's a dependency - and API clients so big question unit of work and repository pattern should we implement these patterns so show a hands who thinks we should still be implementing these patterns so about five people and I'm gonna I'm going to take a leap and say that the rest of you think we shouldn't be implementing these patterns well the interesting thing is that we're all right that is the correct answer whether we think we should implement them or should not implement them that's correct we'll get into that in a second but first with the F core it's not always the best choice because you have core insulates your code from database changes if core uses a provider system and it's simply a matter of you bringing in a new get package for sequel server or sequel Lite or Postgres or cosmos whatever it is that we want to use and we can change that out so DB context actually is a unit of work and the DB set actually is a repository so if we're just trying to implement those patterns for the sake of having those patterns and programming in that way we already have that with the F core in the past say if EF 6 and earlier we used to implement the repository and the universe so that we could do write effective unit tests we don't need to do that anymore since we have the EF core in memory provider we also have the sequel Lite remember of provider and they can be really helpful in kind of using those tools as testing tools to write unit tests so with that in mind what do the experts think so first we have Jimmy Jimmy Bogart creator of automap and mediator and chief architect at head springs and he says I'm over repositories and definitely over abstracting your data layer then we have Steve Smith and Steve Smith's being a Microsoft MVP since all time and he was a Microsoft regional director of for 10 years and he says no you don't need a repository but there are many benefits and you should consider it when Steve said that he's been really diplomatic if you've listened to his podcasts listen to his blog posts and looked at and looked at his conversations on Twitter's he's definitely Pro repository and he has a lot of really really good designs on how to create a great repository and why you should be using it then we have John Smith so he's the author of energy framework core in action and he says no the repository slash unit of work pattern isn't useful with the F core so with that in mind when the experts don't agree what should we do well it's really simple we just need to remember that the repository and unit of work is just design patterns okay if it solves a problem we have that's great use it if it doesn't solve the problem you have then don't use it because that would just introduce unnecessary complexity so what sort of problems could a unit of work and repository design patent solve well one very simple problem is the dependency on the enemy framework or framework if we didn't want our solution to be dependent on that framework then implementing the unit of work and repository pattern would solve that problem another problem is if we wanted to limit access to certain entities so we were going to create order and order details with order at the aggregate route well then we could create repositories only at the aggregate route and force developers to only update orders with the collection of order details they wouldn't be able to update an order detail individually and therefore we'd be able to have lots of logic and validation associated with that so just think any design pattern does it solve a problem I have great use it let's have a look so inside of the infrastructure layers we see we have four folders we have files which is that CSV help for example that I've put together we have identity which is my concerns related to asp.net core identity we have persistence which is by fluid API configurations for my entities my DB context and my DB contacts seed if we have services I'm at the moment the sister date/time service in there so that we don't have a dependency on the machine clock let's have a look first at the fluent API configuration so you can see that it's quite simple here I've got a to-do item configuration and it's do list configuration and that just allows me to define how that entity will be mapped to the relational model until I that up all I have to do is say apply configuration from assembly that's built into F core now as of of core to one and specify the assembly now you'll note that I've left this based on model creating statement now in the past you didn't actually need to leave that in there so if you've written code like this you've probably just left it out and it's worked just fine well it's not the case anymore if you're working with asp.net core identity has a version three and upwards it actually does have an implementation in the base so you'll leave that in otherwise you'll run into an error and add it back in so when you create in these these configurations I want you to remember that ear of core is conventions based framework so the first thing that you want to do is make sure that you understand the conventions and don't create configuration for for conventions because you're just making it more complicated than it needs to be for example you could say something like this this is my typing you could say that that properties are key right but you don't need to do that the fact that it's named ID means that energy framework call will automatically assume that it's a key and so then in the to-do item entity it has a list ID and it in it based on based on the name of that list ID and the name of this property it assumes that that's a foreign key and maps it as so so always when you're working with a conventions based system know the conventions because it's going to make it much simpler for you to work with and means that your code is going to be simpler as a result so now having a quick look at the DB context you can see that in here we've got our identity concerns being wired up and we also have this chase Save Changes async method which I've overridden and you can see here that this is where I've implemented the auditable entity functionality so it basically looks to see if the entity is being added or modified and then sets those properties using these two services so very simple and something that you can build on so inside of identity we've got the application user we've got this identity result extensions which I built to make this identity service a little bit simpler so for now I've just created a single identity service a lot of time in the past I might create a user service and a role service and that sort of thing but the concerns for this application are relatively simple but what you can see is the identity services implementing the identity service which is inside of the application layer so when I need to write logic against identity I'm just writing logic against this service so I can change this implementation whenever I want I can move away from asp.net core identity so you can see there's some samples of in there of working with identity okay so it's important to note that no layers depends on infrastructure it's completely isolated from the rest of the system and it also has a dependency injection extension and in this extension of white up all of the dependencies that are associated with infrastructure so you can see there's the database there's the interface which I used to interact with the database there's some test configuration for identity server in the backend we've got lots of unit tests that are working with identity server and testing against authenticated controllers so there's good examples of that there's the configuration if it's not part of the part of the test environment and that's it so again all of this logic has been moved out of startup CS into this extension methods so that all I have to do within startup is just say add infrastructure so you can see I pass through the configuration and the environment so I have that information available inside of the test folder there's some integration tests which you can take a look at which are all about testing that auditable entity logic okay so key points infrastructure is independent of the database we can switch out a provider and choose a different database solution the solution however that I've built is not independent of entity framework core I've kind of gone all-in with entity framework course so if I do decide to move to a different RM in the future I'll have a little bit of pain there but I'm okay with that I've been using EF for I don't even remember how long now so useful an API configuration over data annotations it's just a better approach it does more and it means that you domain models not clogged with data annotations prefer and know your conventions prefer conventions over configuration automatically apply all entity type configurations and no layers depend on the infrastructure layer eg presentation layer if we do that will result in logic being created in the wrong places and we need it to stay within core ok finally the presentation layer so within the presentation layer it can be whatever we want we've kept all of the logic inside of core so really do you that we create is very simple we have well-defined view models we have well-defined queries and commands so that interactions being made as simple as possible so it could be a single page application angular reactive view it could be blazer web api razor pages MVC even web forms if you prefer let's have a look so one thing that I want to show is the typical example of a controller whoops I don't know what happened there so in this example of a controller you see the DB context is being injected directly into the controller entities are being returned from the controller which is which which is a bit of a no-no it comes with a whole host of issues that we don't really want one to have to deal with security and complexity and all of the logics kind of as simple as this logic is all of the logic is within the controller and that's because we didn't we didn't give ourself any other choice when you inject something like a DB context which is such a low-level concept with something like a controller there's no place left to put your logic okay but we've avoided that problem with this solution because we're using CQRS we've moved all of our logic into commands and queries in the application layer so this is a typical example but let's have a look at the same controller in the clean architecture solution so you can see with this with this controller we don't even have a constructor right why have a constructor if you don't have a constructor it's simpler so we just got a base class the the method for get just basically sends a mediator request mediators come in from the base class and that's it being injected by a property injection the method for getting by ID it's just basically two lines of code one line of code for create a few lines of code maybe you don't need that for update and two lines of code for delete so there's absolutely no logic in there we've essentially reduced this controller to infrastructure it does one thing it takes in a request and returns a response so it's as simple as can be and there's no logic within there all of the logics been moved into the relevant commands and crew so you can see also that when we talked about that it is view model that for each query we're trying to return a well-defined view moral so that when the client receives that response it has everything that it needs to render that view whatever it may be and that means that the client doesn't have to make additional API calls to get more information so encapsulate everything in well-defined view models and make it simpler again so I mentioned that open API is running behind the scenes in this solution and I'll give you a quick look at how that's set up I also have a blog post on it that you can check as well so essentially with open API I prefer to use an swag so an swag is probably the best tool chain because it's capable of generating the specifications it's capable of generating the clients it's kind of an all-in-one shop and it has a nice some windows application that allows you to specify that configuration so you can get up and running really quickly so in this solution in the web UI what I have it doing at Build time excuse me is it generates the specification just there and it generates an angular climb there it is just there and when I talk about this I talk about how it's bridging the gap between the back end and the front end and the reason for that is all those really nicely defined few models and all those nicely defined commands are created I generated here this is code that I don't have to write but if we come down to the bottom here so you can see here's my update to-do item detail command so I have these types nicely defined in the backend so instead of creating manually creating these versions on the front end they're now generated and the angular client to access the web api is now generated and that all happens automatically I just have to build the web UI project and all of that happens if you build in micro servers with this approach you can also use n swag to generate c-sharp clients and so then you can publish those at NuGet packages show them with the other services depending on your approach of course so one of the things that I'm doing in the application layer is something does occur if something happens wrong we throw an exception and so I've created an exception middleware which is responsible for intercepting those exceptions from the application layer and converting them into something a little bit more meaningful excuse me so you see here that we've got two two requests being handled a validation exception and are not found exception so if a validation exception is encountered it will basically take those failures turn it into a 400 bad request response and return the result to the client I'll be releasing a new version of that shortly which will work with validation problem details so that'll be even a nicer experience then we have the not found exception and that's basically it's wrong when it can't find some entity when it's trying to get something by ID or to update or delete something and so it returns that in - it turns that into a not found response code and returns the message back to the client so we have that same same nice consistent experience at least from the asp.net core perspective so with those exceptions it's really up to the client how they will handle that and in this case I've handled it using custom exception middleware with identity there's just a couple of classes in here under services I've got the current user service which will basically just interrogate the clients and find the user ID so then I can pass that to my identity service within infrastructure and do things with that user one of the things that I've added to the web UI is a whole bunch of integration tests and there was quite a lot of work that went into this just because of the integration with identity so if i zoom in here you can see that we're using asp.net core web application factory to wire up all of our dependencies and spin up a a host we've got an integration test helper as test version of the current user service a test of the date/time service end of the test version of the identity service but with all of that work done we actually have some really simple tests that we can write so you can see here that this create test is referencing the controllers the to do items controller and the create action and so we have some tests written for that so you can see I've got to help a method to grab a HTTP client not just a HTTP client but an authenticated client so I've made it really easy to write these integration tests so if you wanting to test at that level where you're exercising the full asp.net core stack you can and it's simple because it's already set up so you can see here I'm getting my HTTP client authenticated I'm creating a command I'm getting I'm turning that into request content and then I'm posting that get in response and just ensuring the success status code so you can see really simple to write these tests and what I'm trying to do with these tests is just to verify the basic system inputs and outputs I want to make sure that kind of at a high level everything's working nicely and the fact that I can write these tests and that I can write them quickly it's just fantastic so here's our delete test so you can see that we're basically saying hey I've got a valid ID and I want to make sure that if I delete that that it returns this success status code we don't care that it was deleted from the database or anything like that we just want to kind of make sure at a high level things are working we can write other tests to check that it was deleted from the database if we get time in this one given an invalid ID basically ensures that the HTTP status code returned is not found so again a basic system input-output test very quick to write because all of the infrastructure is in place so key points controllers should not contain him any impact sorry controllers should not contain any application logic it's responsible only for taking a request and convert it into a response and that logic list lives in the application layer we should create and consume well-defined view models don't make your clients ask questions open API bridges the gap between the front end and the back end and ensberg automates the generation of open api specifications and clients and we automate that using a simple msbuild task so that we don't have to look at it again so thank you for coming to my talk today if you're keen to learn more please grab the code and/or install the template and try it out give it a go I think you'll find this approach is simple to build and maintain all the way from development to production thank you [Applause]
Info
Channel: GOTO Conferences
Views: 144,997
Rating: 4.8608561 out of 5
Keywords: GOTO, GOTOcon, GOTO Conference, GOTO (Software Conference), Videos for Developers, Computer Science, Programming, GOTOcph, GOTO Copenhagen, Jason Taylor, SSW, Software Architecture, .NET, dotNET, C#, C Sharp, C# dotNET
Id: dK4Yb6-LxAk
Channel Id: undefined
Length: 50min 47sec (3047 seconds)
Published: Tue Apr 21 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.