<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jay Freestone</title>
    <description>The latest articles on DEV Community by Jay Freestone (@jayfreestone).</description>
    <link>https://dev.to/jayfreestone</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3948209%2F0a36e80a-1b21-4b23-bbad-65811c831292.jpeg</url>
      <title>DEV Community: Jay Freestone</title>
      <link>https://dev.to/jayfreestone</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jayfreestone"/>
    <language>en</language>
    <item>
      <title>You might not need… the repository pattern</title>
      <dc:creator>Jay Freestone</dc:creator>
      <pubDate>Sat, 23 May 2026 21:20:00 +0000</pubDate>
      <link>https://dev.to/jayfreestone/you-might-not-need-the-repository-pattern-46b</link>
      <guid>https://dev.to/jayfreestone/you-might-not-need-the-repository-pattern-46b</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is mostly about CRUD-heavy backend applications in TypeScript, Go, and Rust, especially ones using modern typed query builders or lightweight ORMs. I’m not arguing that repositories are useless. I’m arguing that unless a repository protects a real aggregate boundary or hides genuinely meaningful persistence complexity, it usually becomes a worse interface over your database.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The repository pattern originates from &lt;a href="https://martinfowler.com/books/eaa.html" rel="noopener noreferrer"&gt;&lt;em&gt;Patterns of Enterprise Application Architecture&lt;/em&gt;&lt;/a&gt;, with Domain-Driven Design and 'layered' (i.e. hexagonal/clean/onion etc) architecture expanding on it from there.&lt;/p&gt;

&lt;p&gt;There are legitimate reasons it gained in popularity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It provides a clean way to separate IO from business logic.&lt;/li&gt;
&lt;li&gt;Testing business logic becomes a lot easier/faster, since there's a clean seam to swap in a test-double and keep everything in-memory.&lt;/li&gt;
&lt;li&gt;It theoretically makes it easier to switch out the backing store if you change database/provider. &lt;/li&gt;
&lt;li&gt;It fits well into the OOP world of Java, C# and friends, where ORMs map to entities.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is a repository?
&lt;/h2&gt;

&lt;p&gt;Let's define the strict, traditional version of a repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A repository operates on aggregate roots. These are the invariant boundaries of your domain model. In traditional DDD, only one aggregate should be committed per operation, with everything else becoming eventually consistent (which helps to minimize locking). The C# documentation &lt;a href="https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-events-design-implementation#single-transaction-across-aggregates-versus-eventual-consistency-across-aggregates" rel="noopener noreferrer"&gt;suggests relaxing this&lt;/a&gt; if strong consistency is important.&lt;/li&gt;
&lt;li&gt;A repository returns aggregates, fully hydrated. Vernon is explicit on this: repositories are &lt;em&gt;not&lt;/em&gt; Data-Access-Objects. He spends an entire section distinguishing them. A DAO is expressed in terms of database tables and provides CRUD over them, while a repository operates on aggregates.&lt;/li&gt;
&lt;li&gt;Since a repository is (theoretically) persistence-ignorant, it should in no way orchestrate a 'unit of work', i.e. a transaction boundary. If you're following the one-aggregate-per-operation rule, this becomes a lot easier. If not, you probably need to cheat and pass an open transaction through &lt;a href="https://nodejs.org/api/async_context.html" rel="noopener noreferrer"&gt;ALS&lt;/a&gt; or some kind of thread-local storage equivalent. &lt;a href="https://docs.nestjs.com/recipes/async-local-storage" rel="noopener noreferrer"&gt;Nest CLS&lt;/a&gt; is a great example of this working really well. If you're using Go, I guess you can pretend you're not funneling it through the &lt;code&gt;ctx&lt;/code&gt; grab-bag on every method.&lt;/li&gt;
&lt;li&gt;The querying interface depends on context. Evans permits dedicated query methods (&lt;code&gt;findCancelledOrders&lt;/code&gt;), and suggests &lt;a href="https://en.wikipedia.org/wiki/Specification_pattern" rel="noopener noreferrer"&gt;specifications&lt;/a&gt; when things become unwieldy. Vernon's &lt;em&gt;strictest&lt;/em&gt; version of a repository is just &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;save&lt;/code&gt;, &lt;code&gt;fromId&lt;/code&gt;, but this is only practical when splitting reads from writes (CQRS). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Vernon also distinguishes between persistence-style repositories (where you have to explicitly 'save' or update objects) and collection-oriented ones (which auto-track dirty objects). In TS/Go/Rust you're probably implementing a persistence-style one.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Repositories in the wild
&lt;/h2&gt;

&lt;p&gt;If you follow a strict domain model, and propagate changes to other aggregates through events, you can adhere to the above criteria. You're probably writing Java, C#, or JS with &lt;a href="https://nestjs.com/" rel="noopener noreferrer"&gt;Nest&lt;/a&gt; (which really, &lt;em&gt;really&lt;/em&gt; wants to be Java).&lt;/p&gt;

&lt;p&gt;Your repositories might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SupplierRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Supplier&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is that most applications of the repository pattern in the wild aren't this.&lt;/p&gt;

&lt;p&gt;They're this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SupplierRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;supplierData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SupplierPojo&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;supplierData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SupplierPojo&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Conditions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pagination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Pagination&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Supplier&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;getWithProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;findActiveById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Supplier&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// etc.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, I've seen all kinds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// I'm not making it up, I have genuinely seen this stuff. &lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SupplierRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Leak of tx!&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// Leak of tx... and no longer concerned with an aggregate.&lt;/span&gt;
  &lt;span class="nf"&gt;createLinkToSupplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;supplierId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SupplierLink&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// This one doesn't take a tx, because it's a convenience&lt;/span&gt;
  &lt;span class="c1"&gt;// method which also calls the analytics service (and then&lt;/span&gt;
  &lt;span class="c1"&gt;// writes the data).&lt;/span&gt;
  &lt;span class="nf"&gt;createLinkToSupplierCommitAndEmitEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SupplierLink&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the cursed offspring of a repository and a DAO wearing DDD clothing. The explicit transactions, methods that aren't aggregate-scoped, and side-effect ridden convenience helpers are exactly what Vernon warns against. If your 'repository' needs to take a &lt;code&gt;Transaction&lt;/code&gt; parameter, you've lost your abstraction.&lt;/p&gt;

&lt;p&gt;The interface bloat (&lt;code&gt;findActiveById&lt;/code&gt;, &lt;code&gt;getWithProduct&lt;/code&gt;, &lt;code&gt;list(criteria, pagination)&lt;/code&gt;) usually means you've conflated commands (which legitimately want aggregate-shaped objects) with queries (which want view-appropriate projections). The textbook answer here is CQRS: split the repository in two, with the write side handling aggregates and a separate query model handling reads.&lt;/p&gt;

&lt;p&gt;But CQRS only solves part of the problem. Even on the write side, you'll have legitimate criteria queries: 'find pending orders to cancel', 'users with outstanding invoices to remind' etc. These aren't display queries, they're locating aggregates that need business logic run against them. Even if you adopt CQRS you'll likely end up with extra criteria-finding methods on the write-side repo.&lt;/p&gt;

&lt;p&gt;If any bit of this sounds/looks familiar, I'm here to tell you that you don't need a repository (or, you don't have one) and that is &lt;em&gt;totally ok&lt;/em&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  You might not have a domain model
&lt;/h2&gt;

&lt;p&gt;Most of us writing Rust, Go and TypeScript are not really writing 'object-oriented' software in the traditional sense.&lt;/p&gt;

&lt;p&gt;While many of us dream of the beautiful &lt;a href="https://martinfowler.com/bliki/UbiquitousLanguage.html" rel="noopener noreferrer"&gt;ubiquitous language&lt;/a&gt; from the blue book, most of us don't really have a true domain model in &lt;em&gt;code&lt;/em&gt;. We may have a shared language between product, UXD and engineering, but when the chips are down it's essentially just data. Data we rip out, transform, and put back into place. It doesn't have much of an in-memory lifecycle.&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://www.youtube.com/watch?v=wo84LFzx5nI" rel="noopener noreferrer"&gt;Casey Muratori says&lt;/a&gt;, OOP makes more sense when something has a &lt;em&gt;real lifecycle&lt;/em&gt;, like a server. It's a &lt;em&gt;thing&lt;/em&gt;, it's not just data briefly masquerading as an entity in memory before it's re-serialized.&lt;/p&gt;

&lt;p&gt;Are you enforcing invariants at the aggregate root level? Or even creating aggregates instead of POJOs?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Constructor etc...&lt;/span&gt;

  &lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shipping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasBeenShipped&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HasShippedError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canceledAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;hydrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Bypass the constructor&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Probably not, and again, that's ok!&lt;/p&gt;

&lt;p&gt;The interesting cases are invariants that can't sit inside an aggregate at all. The traditional example is the unique-email case: &lt;code&gt;throw new EmailAlreadyRegisteredToUser()&lt;/code&gt;. It can't be checked from inside the &lt;code&gt;User&lt;/code&gt; aggregate, because the rule spans all users. DDD has a specific term for this: set-based invariants, and there's no good solution. You either push it into a service, enforce it at the persistence layer, or accept eventual consistency and handle the error later.&lt;/p&gt;

&lt;p&gt;The point is that aggregate-as-invariant-boundary doesn't extend to relationships &lt;em&gt;between&lt;/em&gt; aggregates, and most of the invariants people actually care about in enterprise OLTP applications are exactly this set-based kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  You might be reinventing the ORM
&lt;/h2&gt;

&lt;p&gt;Many repositories I see end up reinventing the modern ORM.&lt;/p&gt;

&lt;p&gt;The ORM as it's referred to in classic programming books (e.g. &lt;a href="https://hibernate.org/" rel="noopener noreferrer"&gt;Hibernate&lt;/a&gt; and friends) is a &lt;em&gt;very&lt;/em&gt; different beast from today's ORM. Modern ORMs like Drizzle (and to some extent Prisma) are more akin to typed query builders.&lt;/p&gt;

&lt;p&gt;They don't map to entities, they give you a freeform typed canvas to build queries from. What you do with that is up to you. It's beautiful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recentOrders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;placedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;itemCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;`count(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;orderItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;innerJoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leftJoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;thirtyDaysAgo&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placedAt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This leads to small, targeted queries and performant, targeted updates.&lt;/p&gt;

&lt;p&gt;If you're writing a repository method which has filtering, pagination, or god forbid a &lt;a href="https://en.wikipedia.org/wiki/Specification_pattern" rel="noopener noreferrer"&gt;specification&lt;/a&gt;, you're probably just reinventing a worse version of the syntax your ORM provides you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FindOrdersOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;OrderStatus&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;placedAfter&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;placedBefore&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;minTotal&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;includeCanceled&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Do we use TS trickery here to strengthen the return type when this is `true`?&lt;/span&gt;
  &lt;span class="nl"&gt;includeItems&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;placedAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;total&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customerName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sortDirection&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OrderRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FindOrdersOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if you exercise discipline, you're probably over-fetching data just for the sake of working with a discrete 'entity'.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you do have a domain model...
&lt;/h2&gt;

&lt;p&gt;If you do have a traditional domain model, a desire for strong consistency leaves you with a lot of little repositories which need to be coordinated (losing the aggregate-root-as-invariant idea):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlaceOrderCommand&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;orderRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;inventoryRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InventoryRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;UnitOfWork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Business logic...&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inventoryRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inventoryItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also either have to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;orderRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWithInventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;orderRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;inventoryRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or you'll have to forever fetch inventory alongside an order. In which case, is &lt;code&gt;Order&lt;/code&gt; the unit of consistency (aggregate root) for &lt;code&gt;Inventory&lt;/code&gt;? Maybe?&lt;/p&gt;

&lt;p&gt;If you have a language which supports lazy loading (masquerading as sync property calls) inside a transactional boundary, then you can kind of fake fetching sub-entities since &lt;code&gt;order.inventory&lt;/code&gt; can arrive late.&lt;/p&gt;

&lt;p&gt;For many languages though, you can't (async/await sneaks in). Even if you do, and even though I &lt;a href="https://enterprisecraftsmanship.com/posts/defense-lazy-loading/" rel="noopener noreferrer"&gt;admire Vlad&lt;/a&gt;, I feel like this really breaks the 'clean' and IO-less domain model concept.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can't just 'swap' your database
&lt;/h2&gt;

&lt;p&gt;The few times when I have had to swap the persistence layer, it has never been as clean as swapping out the guts of the repository. Persistence layers have drastically different characteristics, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction handling.&lt;/li&gt;
&lt;li&gt;Performance.&lt;/li&gt;
&lt;li&gt;Key constraints (or lack thereof).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As Mike Acton says, if your data changes then your &lt;a href="https://www.youtube.com/watch?v=rX0ItVEVjHc" rel="noopener noreferrer"&gt;&lt;em&gt;entire problem changes&lt;/em&gt;&lt;/a&gt;. I love the idealistic view of carving up the problem domain, but realistically unless you're swapping MySQL for Postgres, it is &lt;em&gt;never&lt;/em&gt; this straightforward.&lt;/p&gt;

&lt;p&gt;If the move changes joins, transactional guarantees, indexing strategies, consistency, latency, or bulk-access patterns, then good luck.&lt;/p&gt;

&lt;p&gt;Perhaps if you model tiny aggregates (as recommended) and accept eventual consistency, then this is more feasible. But I have to say, as someone who had to rip out DynamoDB in favor of Postgres, no level of abstraction or layered architecture is going to save you.&lt;/p&gt;

&lt;p&gt;If you don't follow the grain of the persistence layer in some way, then even if you get it to work, you will be doing it the &lt;em&gt;wrong way&lt;/em&gt;. &lt;a href="https://www.jayfreestone.com/writing/follow-the-grain/" rel="noopener noreferrer"&gt;Follow the grain&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  You should be running tests against your real database
&lt;/h2&gt;

&lt;p&gt;One reason people keep repositories around is testability: 'we can stub the repository and keep the unit tests fast'. This has aged badly.&lt;/p&gt;

&lt;p&gt;I'm a huge fan of integration tests, even if their &lt;a href="https://www.jayfreestone.com/writing/integration-tests/" rel="noopener noreferrer"&gt;definition is nebulous&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Modern DBs are fast enough to run your real test suite against them. I'm not going to &lt;a href="https://www.jayfreestone.com/writing/opinionated-guide-to-unit-tests/#dont-mock-owned-dependencies" rel="noopener noreferrer"&gt;rehash the argument here&lt;/a&gt;, but running against an in-memory hashmap provides &lt;em&gt;zero&lt;/em&gt; confidence anything works in your CRUD app. Your entire app is making sure you extract, transform and store back the right data. Have something more complex? It goes in a unit test and shouldn't be I/O-bound anyway.&lt;/p&gt;

&lt;p&gt;This is even more compelling thanks to things like &lt;a href="https://pglite.dev/" rel="noopener noreferrer"&gt;PGLite&lt;/a&gt;, but just spinning up Postgres in a container is &lt;em&gt;more than fast enough&lt;/em&gt; today, and provides a huge amount of confidence in the code you've written.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, do I need a repository?
&lt;/h2&gt;

&lt;p&gt;Maybe, but make sure you're actually getting value out of it.&lt;/p&gt;

&lt;p&gt;You probably don't have invariant-enforcing aggregates in the DDD sense, and you probably don't have a &lt;a href="https://martinfowler.com/eaaCatalog/unitOfWork.html" rel="noopener noreferrer"&gt;Unit of Work&lt;/a&gt; sitting above your repos.&lt;/p&gt;

&lt;p&gt;You can still do DI if you want, and you can (and should) extract data-layer helpers. But the repository pattern quickly degenerates into a thin and leaky wrapper unless you really commit to it.&lt;/p&gt;

&lt;p&gt;You might argue that it doesn’t matter whether you call it a repository, or whether it fits some formal definition, as long as it’s useful. Fair enough. But the abstraction is pointless unless it protects a real domain boundary, improves testability in a way your integration tests don't, or hides meaningful persistence complexity.&lt;/p&gt;

&lt;p&gt;As Evans says: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In general, don't fight your frameworks. Seek ways to keep the fundamentals of domain-driven design and let go of the specifics when the framework is antagonistic. Look for affinities between the concepts of domain-driven design and the concepts in the framework.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Domain Driven Design, Eric Evans&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your language, framework or setup doesn't fit the pattern, don't adopt it.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>database</category>
    </item>
  </channel>
</rss>
