What good is a repository?
This week I'm following up on last week's tip about the Repository pattern. A listener pointed out to me that I never directly answered the question posed in the last episode of "Do I need a repository?" I'll be sure to do so here.
Sponsor - DevIQ
Thanks to DevIQ for sponsoring this episode! Check out their list of available courses and how-to videos.
Show Notes / Transcript
Last episode I addressed a fairly common online argument against the use of repositories. I suggest you listen to that episode and then jump back into this one, but hey, you do what you want. I addressed the usual arguments against using a repository by using a particular article as an example, but I want to be clear that that article was by no means the direct cause of my response. In fact, I hadn't even seen that particular article until I sat down to record the episode and it happened to be pretty high up in my search results and made a good example of the kinds of arguments folks like to throw out when arguing against the pattern. So, now that we've heard and perhaps refuted some of the arguments against using the repository pattern, let's talk about why you might use it.
What good is a Repository in your application?
The Repository pattern is simply an abstraction of your persistence strategy for your application. In fact, the repository pattern is most frequently used along with the strategy design pattern as a way to pull low level knowledge of persistence details out of your application's other classes. I've heard the repository pattern also described as an example of the facade design pattern, since it hides away much of the detail of this or that persistence technology and exposes a much simpler interface for getting and storing entities. I can get behind that definition, too. You can think of the repository pattern as essentially a particular use case of the facade pattern in which the complex underlying implementation is related to persistence. There's one more pattern we can consider in relation to the repository, though, and that's the adapter. The main difference between a facade and an adapter is in the intent. A facade's intent is to simplify, while an adapter's intent is to allow multiple implementations to be accessed through a common interface. A repository typically does both of these things, providing a simple interface that hides unneeded complexity and allows multiple implementations, like relational, non-relational, in-memory, or even file- or API-based approaches. So, the repository pattern is all about providing an intention-revealing name to a facade or adapter that can be used as a strategy to reduce coupling in your application. Let's drill into these other patterns a little more.
The strategy pattern lets you change how a class does something without having to change the class itself. If you're familiar with dependency injection, you already know this pattern. It works by passing in as a parameter a particular implementation to be used, allowing this implementation to vary without the class that uses it having to change. It's one of the most powerful design patterns for writing loosely-coupled code that follows the SOLID principles. It's very challenging to write unit-testable code in strongly typed languages without using this pattern.
The facade pattern is helpful when you want to make a complex API easier to use. This might be because the complexity is unnecessary, or because there are certain "right ways" to do things in your particular application and you want to make it easier for your team to follow the right path and avoid the wrong ones. Creating a facade to expose simple persistence operations like creating, updating, and deleting records, as well as some mechanism for fetching and querying, is a pretty common technique and can allow teams to focus on business logic moreso than data access logic in many cases. That said, keep in mind that the facade can hide useful features and sometimes necessary complexity that client software should otherwise be able to access. Care must be taken in how the facade is designed to ensure it doesn't cripple the use of the libraries it wraps.
The adapter pattern is helpful for testing, since it allows tests to easily substitute in implementations that behave however the particular test requires. Using adapters can also allow an application to work more flexibly with different actual providers, plugging in the appropriate one as necessary.
You can see that the Repository pattern is really a particular implementation of one or more other, more generic patterns. Next week I'll talk a little bit more about the pattern, and how it can be further extended by layering additional patterns on top of it. Let's wrap up this episode by answering an important question.
Do I need a repository?
No, you don't need a repository. However, if any of the benefits I've described in the last two episodes sounds like something you might want in your application, you might consider it. If you want unit testability, an abstraction over data access will save you a lot of pain. If you want flexible, modular code, the same is true. If you want low-level access to all the features of your data access technology everywhere you use it, then you might not want an abstraction over it. If your application isn't set up to make use of dependency injection, it may not be worthwhile, since newing up repositories everywhere you need them will still tightly couple you to implementation details.
Do you think your team or application could be improved by better use of design patterns? I offer remote and onsite workshops guaranteed to improve your coding skills and application code quality. Contact me at ardalis.com and let's see how I can help.