Alternatives to Using Repositories12:49 with James Churchill
Now that we've gone through the process of adding repositories to our web app, let's talk about the pros and cons of the repository design pattern and look at an alternative approach that utilizes query and command classes.
To follow along committing your changes to this course, you'll need to fork the dotnet-comic-book-library-manager repo. Then you can clone, commit, and push your changes to your fork like this:
git clone <your-fork> cd dotnet-comic-book-library-manager git checkout tags/v3.8 -b alternatives-to-using-repositories
Are Repositories Necessary?
Over the years, some developers have begun to question the value of using repositories. For more information about this discussion, see the following blog posts.
- Favor Query Objects Over Repositories
- Wither the Repository
- Limiting Your Abstractions
- Repositories On Top UnitOfWork Are Not a Good Idea
Organizing Projects By Feature Instead of Class Type
Instead of organizing projects by class type, it's possible to organize your projects by feature. For more information on what this is and how to do it, see this blog post from Tim G. Thomas.
YAGNI - You Aren't Going to Need It
YAGNI, or You Aren't Going to Need It, is the idea that you shouldn't add something to your application until you actually have a need for it. Sounds like a simple idea, but you'd be surprised how often as programmers we feel the urge to add stuff that we don't actually need.
For more information see this article on Wikipedia: https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it
You can download the code for the ComicBookArtistsController_QueriesAndCommands class from this GitHub Gist.
Now that we've gone through the process of adding repositories to our web app, 0:00 let's talk about the pros and cons of the repository design pattern. 0:04 Moving all of our data retrieval and persistence code into a repository. 0:08 Or in our case, a collection of repositories helps to simplify and 0:12 keep our controllers as lean as possible. 0:16 It also helps us to adhere to the dry or don't repeat yourself design principle. 0:19 Instead of duplicating EF queries or commands across controller action methods, 0:24 we can place that code in a repository method and 0:29 call that method from our action methods instead. 0:32 But adding repositories to an application adds an additional layer of abstraction 0:35 between your controller action methods and the database context. 0:39 And in some cases this layer of abstraction can 0:43 feel like you're adding complexity without enough of a clear benefit. 0:46 As an alternative to using repositories, we can use Query and Command classes. 0:51 Query and Command classes encapsulate data persistence code much in 0:56 the same way that repositories do. 1:00 But when you selectively on an as needed basis they can provide the same benefits 1:02 repositories without adding as much complexity. 1:07 Let's look at a couple of examples. 1:11 Before we add our first class let's add two folders to the shared class library 1:14 data folder one named queries and another named commands. 1:19 As the folder name suggests we'll add all of our query classes to the queries folder 1:24 and all of our command classes to the commands folder. 1:29 To make it easier to compare using repositories to using queries and commands 1:32 I added another copy of the comic book artist controller class to our project. 1:37 And reverted the code back to what it was 1:41 before we added our comic book artist repository. 1:44 If you're following along you can find a copy of this code in the teacher's notes. 1:47 Let's start with the Add.getAction() method. 1:51 At the beginning of the method we have a query to retrieve the comic book 1:54 that the user is adding an artist to. 1:58 When using query in command classes you don't automatically 2:00 move every query in command into it's class, 2:04 instead you evaluate it's relative complexity and usage. 2:07 And make a determination if your code and 2:11 overall design will benefit from moving the query or command to it's own class. 2:13 This query isn't overly complex but it's used at least twice. 2:18 Once here in the ad get action method and once in the ad post action method. 2:24 Since it's duplicated and it's what I'd call moderately complex, 2:30 let's move it to a query class. 2:34 Add a class named get comic book query to the queries folder. 2:39 To start, we need a private field for the context. 2:48 And a constructor that accepts a context instance and sets the private field. 2:51 Private Context _context = null. 2:59 Public GetComicBookQuery then a parameter up type Context 3:05 named context and set the private field to that parameter value. 3:13 Then let's add a method name execute that returns a comic book instance. 3:20 And accepts an integer ID value, public. 3:25 ComicBook Execute, int id. 3:31 For the implementation we can switch back to the ControllerAction method. 3:37 And copy the query to the clipboard. 3:44 Add return and then paste the query from the clipboard. 3:50 We've got some things to fix up here. 3:56 Change context to, _context. 3:59 ComicBookId to just id. 4:06 And we need to add using directive for the system data 4:13 entity namespace, using System.Data Entity. 4:18 Now we're ready to update the action method to use our new query class. 4:25 Remove the existing query, instantiate an instance of the GetComicBookQuery class, 4:29 new GetComicBookQuery. 4:35 Add a using directive for the ComicBookSharedDataQueries 4:39 namespace passing the context property from the base controller class. 4:43 And call the execute method, passing in the comicBookId parameter value. 4:47 Then in the add post action method we can replace the query to set the view 4:58 viewModel's ComicBook property with another instance of our query class. 5:02 Now let's review the remaining EF queries in commands that are being used in this 5:13 controller. 5:17 And determine if any of them would be good candidates for 5:17 moving to their own query or command classes. 5:20 Remember we only want to create query or 5:23 command classes to encapsulate complex or duplicated code. 5:26 In the add post action method we've got some code related to add in 5:32 a comic book artist. 5:35 This code isn't repeated anywhere else and its relatively simple. 5:37 So let's go ahead and just leave it as it is. 5:42 In the delete get action method we've got a query to retrieve a comic book artist. 5:46 This query is about the same level of complexity as the query that we 5:50 just moved to a query class. 5:54 But for now it's only used in this one location. 5:56 Again for simplicity's sake let's leave it as it is. 6:00 In the delete post action method we've got 6:05 code related to deleting a comic book artist. 6:07 But again it's relatively simple and it's not repeated anywhere else. 6:10 So let's leave it alone. 6:15 In the validate ComicBookArtists method we've got a query that checks if 6:18 the provided artist and 6:22 role combination has already been added to the comic book or not. 6:23 Again, just like the other queries or commands that we just looked at 6:28 it's only moderately complex, and it only appears in this one location. 6:32 So let's just leave it alone. 6:36 Because it's query or command, it's its own class. 6:39 You might expect that you'd end up with a lot more classes in your project, 6:42 than if you had used repositories. 6:46 But as you just saw the number of classes that you'll end up with 6:48 really depends on the complexity of your apps queries and commands. 6:52 And whether or not they're used for more than one location. 6:55 Even if you end up with more classes in your project than if you had used 6:59 repositories, using query and command classes often make it a little easier 7:03 to find a query or command that you're looking for. 7:07 Since each class contains a single query or command. 7:10 You don't have to determine which repository you're looking for 7:14 then find the specific method within that repository. 7:18 You just find the query command that you're looking for and 7:21 directly open that file. 7:24 Let's go ahead and 7:26 convert the command to add a ComicBooks artist to use the command class. 7:27 So we can look at an example. 7:30 At a class named AddComicBookArtistCommand to the Commands folder. 7:33 As we've see before we need a private field for the context and 7:50 a constructor that accepts a context instance, and sets the private field. 7:53 Private Context_context = null; public 8:00 AddComicBookArtistCommand then a parameter of type context named context. 8:04 And set the private field o the parameter value. 8:15 Then add a method named execute that returns void. 8:20 And accepts three integer values 8:24 comicBookId, artistsId and roleId. 8:28 For the implementation we can grab a copy of the code in the controller action 8:33 method. 8:37 And copy the code to add a comic book artist to the clipboard. 8:47 Paste the code from the clipboard. 8:51 And probably to no one's surprise got some things to fix up again. 8:54 Add a using directive for 8:58 the comic book shared model's name space. Remove the bad references to view model 9:00 and lower case these identifiers ComicBookID, 9:04 ArtistId and roleId. 9:13 Whoops, looks like I have misspelled this parameter. 9:17 ArtistId not artistsId, then replace Context with _context. 9:21 Now we're ready to update the action method to use our new command class. 9:32 Remove the existing code. 9:36 Instantiate an instance of the AddComicBookArtistCommand class. 9:40 Add a using directive for the missing name space; ComicBookShared.Data.Commands. 9:49 Pass in the context property from the base controller class and 9:55 call the execute method. 9:59 Passing in the viewModel.ComicBookId, artistId. 10:03 Enroll Id property values. 10:11 Now we're ready to test our changes. 10:16 To do that we need to comment out the ComicBooks artist controller class. 10:19 And rename the copy of the controller that we've been modifying to the correct name. 10:24 So that ASP.NET MVC's routing engine will find the query and 10:29 command version of the controller. 10:32 Change the name of the class is good enough. 10:36 We can leave the file name alone. 10:39 Let's separate points in the two methods that we updated to use query and 10:41 command classes just to make sure that our testing exercises, those code pass. 10:46 And run the WebApp. 11:02 View the details for Bone #3, and add a new artist. 11:06 And we've hit our first break point. 11:14 Press F5 to continue execution. 11:17 Select Jeff Smith for the artist. 11:24 Pencils. 11:29 Oop, that artist is already in use. 11:30 So choose Archie Goodwin Pencils and save. 11:33 And we're at our second breakpoint. 11:38 Looks like we're good to go. 11:40 Determining how best to organize your data access code can be a challenging exercise. 11:42 That being said there's no need to agonize too much about it. 11:47 Consider the requirements of your app, weigh the pros and cons of each approach. 11:51 And choose the one you and your team thinks will be the best fit. 11:55 Just remember to not overbuild your solution, 11:59 only build the features that you actually need. 12:02 You could always refactor later on. 12:05 Let's do a quick review of the section. 12:08 We created a base controller class in order to eliminate code duplication in our 12:10 controllers. 12:14 We also reviewed the repository design pattern. 12:16 And implemented a new repository for our web app. 12:18 And we saw how to break apart that repository 12:22 into entity type focused repositories and 12:25 created a generic base repository class to further reduce code duplication. 12:28 Then we finished up with a look at how query and 12:34 command classes could be used as an alternative to repositories. 12:36 Next up, we'll finish the Comic Book Library Manager Web App by updating 12:41 the series and artists sections to use EF. 12:45 See you after the break. 12:47
You need to sign up for Treehouse in order to download course files.Sign up