 On today's Visual Studio Toolbox, Chris Woodruff and I wrap up our three-part series on building web API. We're going to talk about best practices and architecture. Hi, welcome to Visual Studio Toolbox. I'm your host, Robert Green, and with me is Chris Woodruff. Hey, Chris. Hey, Robert. How are you? I'm awesome. Excellent. We are wrapping up, for now, our three-part series on web API. Yep. So in episode one, we showed how to create a web API. So that was pretty straightforward. ASP.NET Core. ASP.Core. In episode two, we showed integration testing, which is a new feature in ASP.NET Core. Very, very cool for really easily testing. What happens if you call the service, do you get back success or error codes? Not testing the data, but testing the calling of the service, really easy to do. Then today, we're going to talk a little bit about best practices and architecture. Because you look at our sample code, and we know about MVC and separation of concerns, and MVVM and separation of concerns, but in our little project here, everything is dumped together. So we should probably talk about that. Then what are some of the best practices for doing this for real? Yeah. Cool. I don't have all the answers, so I'll say that to begin with. I just have my example. Everyone can take my example and make it better. There's a lot of smarter people out there. There are a few things. So I'm usually the dumbest person in the room. I just know a lot of smart people, so that's my gift. But yeah, so we're going to look at architecture, and we're going to really just reinforce this idea of decoupling. If this talk is about anything, it's really about decoupling concerns and decoupling responsibilities. We're going to see doing that, but using a lot of the same techniques that you're already used to. So it's not like there's an entirely different way you decouple. MVC and MVVM are similar. If you've seen one, you understand what the other one does, we're going to use very similar techniques here. So we're going to look at a modified more advanced like N tier architecture. Maybe I could come back and we could do a talk on hexagonal architecture, which is really more complex and it deals with domain-driven programming and domain-driven development. But we'll talk about that later. We'll talk about that later. This is more of a simple architecture that people can really start using right away with their ASP.net core web APIs. So let's talk about what you and I do wrong, because I think- We only have 30 minutes. Yeah, yeah. So we're going to have to whip through this list. So bad habits. So what kind of bad habits do we have? In the first video that we showed, we were doing all of our data access from controllers in the actions. Well, that's a bad habit. That's kind of code behind. Smacks of code behind. Yeah, yeah, it really does. It really smacks of cramming all your code into one area and then you're kind of locked in. Right. So don't do that. Even though we showed on the demo, don't do it for your production code. I say don't have all your code in a single project. One, it's hard to test. Two, if you're on a big team, you won't step on people's toes. And if you update one project, it may not, and you go through CI, continuous integration and building out on the server, it minimizes the work that those DevOps systems have to do. And then we don't want to couple the data access, which in our case is entity framework core, to our project domain knowledge. And I'll explain that once we take a look at some of the layers and once we take a look at the code. And then we don't want to forget, and another bad habit is people don't think about unit testing and integration testing. Or they don't do it. They think about how they should do it, but they don't do enough of it. Yeah, and you should build your solutions, build your systems so that with testing in mind, right? Absolutely. Yeah, because if I build it in a way that I can't test everything, excuse me, then it's a failure. Okay, so you gotta think about unit testing and integration testing. So how I build my APIs is I have this triangle in this case, but I have four different areas I look at. I have my API, which just handles the actual API that people call. So it really contains all my controllers and my actions, and those correspond to entities, in this case, using this architecture. And the actions correspond to different verbs that we can do against those entity types. So with the Chinook database, we have albums and artists as an example, and we can do things like get all of our artists, or get all of our albums, or get one, or delete, or update, or insert new entities. So that's all the APIs should do. Then we take a look at domain. And domain is kind of the heart of my architecture. It really does, it's where all the business logic lives, it's where all the validation lives. It's really the center, and that's why it's in the center of this triangle, or pyramid. And then data, I mean, you need to have some way to get data, even if it's mocking, you still have to have data access to get to send back your mock data. But in this case, data in a production sense is where I have all of my entity framework core logic. And I separate it because I wanna be able to test all, I wanna be able to test my APIs and my domain against production database and mock database, or mock data access, my mock data. And then the last one is tests, which are important. Don't forget about tests, you have to have your test projects that go along with your everything else. So we'll quickly go through what each layer should be. So API layer, like a UI of ASP.NET MVC project, the API endpoints should not know anything about domain knowledge or the data access. All it should know is how to get those HTTP calls and how to respond back from those NHTTPs. It's a view, the view doesn't know about the view model, the view doesn't know about the model. Exactly. It should interact with view models to ensure the greatest flexibility. Now, we were talking about this earlier, and I'm gonna use an example. In my Chinook database, there's a entity called invoice, and invoices are made up of multiple invoice lines, right? But we don't know the total amount of an invoice unless we know all of the invoice lines, individual costs. And so in most databases, in my databases, I don't save the total amount for an invoice. I calculate that on the fly. So if you take a look at one side, we have our data models that are based one to one on the tables in our database. On the other side, I've got these view models, and how do I convert, how do I take a invoice data model instance and convert it so it has a total property inside of it? It has to go through my domain layer and figure out all of the individual invoice lines, total those up, insert that into a property in the corresponding view model, and then vice versa when a view model comes in from my API, and I don't have, and I have a total, maybe I use it to verify that that total equals all of the corresponding invoice line rolled up total. So I use it for validation coming from the other way. So that's why the API only deals with view models. It doesn't know anything about the data models. Domain layer contains both my entity models, my data models, and my view models. So because it needs to do the conversion, it contains all of my interfaces for my data retrievals so I can keep a well-defined standard for all my data access. Now, what I mean by that is that if I have two different ways that my API can get data, meaning maybe I get it from SQL server and that's in one project and then I have another project for my mock data, each one of those needs to know the interfaces that they have to adhere to, the contracts, so to say. And so my domain layer contains all of my contracts that my data access projects have to adhere to. Because if they don't, then they won't work well or they won't work at all. So all my interfaces are in my domain layer. And then this also allows me to use dependency injection for my repositories. So those repositories that live in those individual data access projects can be injected into my domain layer. So if they're coming from repositories that use any framework core or use the mock, my mock access, mock data access, the domain layer doesn't care. It doesn't even, it really doesn't. It doesn't know. No. Sure. And then I think of my domain layer as a supervisor and there's an actual class called supervisor that does all the heavy lifting in there. Data layer, pretty simple. It's where all the heavy data lifting happens. Everything that it does is defined based on interfaces from the domain layer. We talked about that. And in my sample architecture instance, I use any framework core 2.1, but you can use anything that adheres to .NET standard 2.0. Okay. Okay. And then I also use mock for testing up a data access layer for testing. And then my tests, I have two types of tests, integration that we looked at previous video and traditional unit testing. Which we cover fully with my series with filter pixie. Exactly. And so this is integrated for API endpoint testing and controllers. So we also test out our APIs and the controllers that are in the APIs. And then we cover all of our unit tests for our repositories and our individual data access layers. You can test out, really, you should be testing a lot more than I show in my sample. Okay. And that's it. All right. So let's dig in and show. Let's see the sample. Yeah, let's dig in and show the sample. Okay. So let's first take a look at the different projects. So if I collapse all these down and just show the different projects that we have, there are a number of different projects. Like I said, we have Chinook.API. Now I set up a naming convention for my projects where I have a base name and then a dot and then whatever it corresponds to. So I have Chinook.API, Chinook.Domain, Chinook.Data, Chinook.IntegrationTests, MockData. So I have two, so my MockData adheres to the same interfaces as my Chinook.Data. And then I have integration tests and then I have unit tests. In here, I also have a MSUnit test because my unit tests are all XUnit and I try to make sure that I'm gonna be running some MSUnit tests for people that like that library for testing. So I try not to be biased and just look at one side. So the first thing I wanna take a look at and I'm gonna close all these documents is I wanna take a look at my API. Now in my API, I'm gonna show my startup and I'm gonna make this small. So in here, I recommend, and you don't have to do this, but I recommend not putting all of my configuration stuff into my startup. So if you see here, I have a bunch of these like configure repositories and configure supervisor and AdMiddleware and my cores, which we've never talked about, but I have all these things that I wanna do that I wanna inject and stand up for my services, but I don't want my configure services to be this gigantic method. So if you take a look out here under configurations, I have some classes that handle all those and let's take a look at like configuring connections. So I have an ad connection provider call that basically goes out and gets the connection string from my app settings.json and it just sets that up, adds that DB context, injects that DB context into my project and then returns it. And for me, in my thinking, it is better for me to push all these stuff out so that I know what I'm working with instead of having this gigantic method. So that's one kind of best practice that I do in this architecture. If I go back to my startup, I also use Swagger. I don't know if you know Swagger, but in this, and this project is out on my GitHub account. So it's a repository that we'll probably link to in the episode notes, but we use Swagger for this and Swagger is really simple. Swagger is kind of a human readable form, has a UI to it, so you can actually kind of see what you're working with and we'll show it at the end of this video. It's kind of cool. You can see how that's set up. You can see how I do cores and also some other things, but don't wanna dwell too deep. Let's take a look at controllers because my controllers are dumb. They're very dumb. So this is a album controller and this is a get all, so this is a action to get all albums. So all I do in here is I'm really just calling this Chinook Supervisor, which was using dependency injection was injected into this project and if I take a look up here in the constructor, I just grab that Chinook Supervisor, assign it to an internal private property on this controller and I can use it and I just call get all album async. So I'm using async calls all over the place so that I'm not kind of hurting the performance of my API and if I catch an exception coming back from that, I send back a status code of 500 in this example. You can be more, you can get more advanced fancy and do different things, but for this example, all I wanna know is did I get some kind of internal error behind the controller action? If I did, I send back a 500, meaning I made the mistake, a 400 is you made the mistake, I made the mistake, so I'm gonna send back a 500 and then I'm gonna send back the exception, which will allow the person getting that response back to get some kind of information about the exception that occurred. And that's all I'm doing. I mean, if you take a look at all of this, it's really kind of simple. All my controller actions are very simple. Like if I wanna get a specific ID, a specific album from the corresponding data access, whatever it is, I come in and I check to see if that album exists so that if it doesn't exist in the database, I send back a not found. Okay. And that's a 404, right? Right. Yeah. If everything gets sent back okay, I send back an okay with the response, with that one album to be sent back. If I catch an exception, I send back a status code of 500. Okay. This is all really my actions do, my controllers do. They just get, they call out to my supervisor to get data, to get information, and then they send it back out or send back some error or some kind of other response code. So really, that's all my API project does. Right. Simple. So now let's take a look at the data side. Okay. Of the solution. So the data side is actually really simple also. It's just data access. That's all we're doing. Sure. We don't care about validation or making sure that the data is correct. We just get it and send it back. So we really just have these repositories. So we're using the repository pattern to have all these calls. And they really just correspond to certain things like we have a call that says album exists. So it just goes out and says, does this ID exist in the database? And then we can have like get all async and get by ID async and add async and, I mean, our credit cards, right? It's just straight forward EF stuff. Yeah, it's just straight forward EF stuff. And it's really basic stuff. Okay. Okay. So we're still doing kind of boring stuff. But let's get into the domain now. So let's close that out. Our domain is a little interesting. So we keep all of our stuff in here. So when I created and generated my entity models, they actually got created in the data access layer. But I actually pulled them out because all of my data access, my mock and my SQL server have to adhere to the same entity models, right? So I wanted to keep them separated from each one of my data access layers. So I keep them in my domain so that everyone can reference them and they're referencing the same classes. They don't have different classes and someone could go in and modify one entity class in one project and would break the other one and break the whole thing. I also have my view models which correspond to what I'm interacting with the API. And are these the same view models that you would write in a XAML application? Yeah, it's the same corresponding. I mean, they're just, I mean, and they almost look exactly alike. So if I take a look at an album, you can see album is made up of an album ID, a title, and an artist ID. But what if I wanted an album to also have the artist name included in it? So this is an example where I have a view model that has a slightly different shape. Okay, so this is a different view model than the view model we have in MVVM because the view model in MVVM does the actual data manipulation. Yeah, so no, it doesn't do that. This is just basically giving me the shape that I wanna work with through my API. So you can see that the only thing that's really different between the two is really in this, I've inserted a artist name into my view model so that when I send back and someone says, give me all the albums that were created by or maybe if I had some kind of date on this, maybe I could say give me all the albums that were created in 1979 or produced in 1970. Or you want all Dave Matthews albums. Sometimes it's Dave Matthews, sometimes it's Dave Matthews band, right? Yeah, exactly. Dave Matthews, sure. So we've got that. So that's just an example why I have entity models and view models is because sometimes I want different stuff to be to work with the APIs. Right. So if we, also we have our repositories and these are just our interfaces. So that's why I keep all of my interfaces in this domain so that all of my data access have to adhere to these contracts. Okay. And the last two are I have my supervisor which is just one gigantic class. I don't really like it but I couldn't get away from having a bunch of circular reference issues. So I have one gigantic, I call it the Chinook supervisor and it gets injected all of the repositories that I'm working with. So you can see here that in the constructor I get back all of the repositories from whatever data access project I'm working with either the mock one or the entity framework core one. And it uses those repositories to do certain things. Like if I say get all album async I basically get all of my, I call that corresponding repository and say give me back all my albums and then I convert that list over to view models. And I have a bunch of converters that convert over. Now, I could use other third-party tools that do that conversion automatically but I did converters just to show people what you could do. Right. So in the end and then I convert them and I don't really even need to do this anymore. I thought I got rid of all these but really all the supervisor does is it converts one side to the other. Either converts view models to entity models or entity models to view models. Because I tend to put that code in my view model. You could, you could, yeah. It's kind of clunky. But I do it and I could put some validation. I'm gonna be working. I like the idea of separating that out because the view model is retrieving data and preparing it for the view, right? But I don't have a view. So I call it a view model. Maybe I should call it like an API model. Maybe. So maybe I'm not calling it the right thing. I'm calling it something to distinguish it from. But even when it, in Xamalapse, when it literally is the view model, I like the idea of the view model calls out to something which goes and gets the data and then does the manipulation, passes it back to the view model which then turns around to the view and says, here's your data with artist title attached or invoice already calculated instead of having that code living in the view model. So retrieve the data from the database and then do the conversions. I break out all this because I can build unit tests against a supervisor object and I can build unit tests against the convergence. And it separates the concerns. If you change the way you do the conversions, it's all in one place. Exactly. Okay. Yeah. I like that. So that's really my domain. And I can also show you my mock data. My mock data looks almost like my data that uses any of the framework. It just has really simple repositories in this. I just create one for like get all async for album. I'm just hand coding one album and then I send it back. Now I know there's more advanced as you probably saw with Phil, there's more advanced ways to do mocking. This is just a simple way that one, people can see just how to do it in a basic sense. Mock queue or MO mock or mock queue or MO queue or however you pronounce it. There's tons of ways to do it. This is just a simple way that I'm just sending back one thing and it's just a bare bone as basic as it can get. Okay. And would I do this for production? No, for like a real production API, no. This is just a demo. Sure. And then again, as you've seen in the last video, this is the same exact thing that we were doing before with integration testing. You can go back and take a look at that video. And you got your unit tests in there as well, of course. And then I have unit tests down below where in this example, I just unit test my repositories. And these actually go out and unit test the mock, the mock data access. Because some of these tests, I check to see how many instances, entity instances I'm returning. So I really want that done in a mock environment. You don't want to tax the database. You don't want to spin up Azure charges. You don't want to necessarily have an entire mock database. Yeah, you should be able to test based on mock data. And that's really it. That's my architecture. So it just comes back to I try to decouple as much stuff as I can from each other. I try to break it down into smallest pieces. So one, I can understand them. Because like I said, I'm not super smart. So I have to break everything down into small chunks so I can keep organized everything in my brain. And I can test each individual piece. All my gears can be tested without any other information around. Yeah. And that's it. Very cool. Yeah. So like I said in the beginning, this is just my way of doing it. When I do talks, I do this talk around this architecture quite a bit out in the community. I always say, and I get people that come up to me and go, well, why don't you do this? And why don't you do this? And I take their suggestions and I'll build them back in this. I've had quite a few little suggestions how to tweak things and make things better. So like I said, I don't know everything. I'm just showing you what I've learned over the years. Right. And try to kind of give a little wisdom out to people. Cool. Yeah. All right. So I think this went really well. Yeah. Our series on doing web API, we saw how to build them. We saw how to test them. We saw how to architect them. That's awesome. Thanks so much for doing this. Yeah, thank you. Hope you guys enjoyed that and we will see you next time on Visual Studio Toolbox.