There is a myth that the reason for using a Repository pattern that goes a little something like this: "You use a Repository pattern so that you can swap out the database technology if you want to switch to something else." In this post, I will bust this myth.
First off, I'm not saying that the swapping rationale is not valid. I've encountered one instance where we actually wanted to switch the data-store technology but the cost and risks of doing so would be well beyond what we were willing to invest since the data access code bled into all the other code! In this case, had the original developer used some kind of abstraction or at least a clean data layer, we would have been able to reduce certain risks at a reasonable cost.
Chances are that you will not change the data-store technology. However, what you will likely want to do is some automated testing. If you are going to do that - and since it's a fast, cheap, and reliable way to increase quality and reduce bugs, you should do that. Hell, if you are doing any sort of testing at all besides end-to-end testing this applies to you too. Testing is the main reason for using the Repository pattern or a similar abstraction of the data.
You could run tests that are dependent on the database, or you could choose no to - especially early in development. If you choose to not depend on the db, then you will need to supply some stand-in values for the core business logic to function. In order to supply those stand-in values, you may want to read them from a different source like a flat file, or use one of the many test-doubles to suit your needs.
For any reasonably complex system - let's say one with multiple data sources - you may not have full control over the data source. You may not be able to change the values. Or maybe others are changing the values for some other efforts and adversely impact your efforts. When you are doing your development work and suddenly your program no longer works as expected or you cannot verify your work due to some other work which impacts the program's dependency - your progress will grind to a halt while you sort it out.
So what do you do? You could copy the db while in a known good state; or you can write up your own db and use that for a source. You could write a bunch of insert statements to set up the database with whatever values you need. You could even write new values to and read from the database for each test case. You could even add some special logic just for the tests that write to the database, even if your program does not require you to do so. However, using an abstraction can lead to a cleaner approach when it comes to testing your business functions.
With an abstraction of the data layer, you can wrap all of the nasty hobbitses of sql statements, ORMs, or whatever you have cleanly behind something that looks like the code you are writing in the layer you are working on. You can supply values to the business logic by mocking, stubbing, faking, or otherwise substituting the implementation of the abstraction to suit your needs. You can use a scientific approach to testing your code as you are implementing it by changing certain variables during different test scenarios.
For an example, let's consider a case where the system needs to send an email. Let's say the recipient list comes from some service via an API and the email itself comes from an internal database. And let's say we want to use an SMTP to send the email for now. All three of those things fall outside of the boundaries of the program. In the Hexagonal Architecture sense, the business logic depends on those but not on the specifics of the implementations of those. So go ahead and abstract them from your business logic.
Your business logic should: fetch the list of recipients, merge with the email template, send the emails. It should not care how you send the email or where the recipients or template come from. I would focus on testing the template mashing code and that the email is sent under the correct conditions with the correct values. I would try running different permutations of values through the tests - there are some techniques that I've found to work very well for testing logic this way while reducing the number of tests which can muddy up what the they should convey to other developers. Look for those in a future post.
Some of the resources that the emailer system consume can change (though mostly unlikely) and the Hex pattern can ease the transition. More importantly though, patterns like Repository aid writing and running the test code which is there to guide and make clear the intent of the business functions. The tests are there to offer an example of how the modules are to be used and to show how they should behave given specific scenarios. They are these to clean up the business logic from the data access code so you and other developers who work on the system don't have to wade through oodles of data access muck to sort out the business functioning of the system. In these ways the TCO of the system can be reduced since changes can be applied more smoothly.