skip to content

Can a Domain Service depend on a Repository interface, such as one used to check that no other Customer already has a given email address? Where's the line between a legitimate domain-service dependency and infrastructure concerns leaking into the domain layer?

level: principalimportance: should knowfreq 35%

answer

  1. uniqueness needs the whole collection
  2. repository interface owned by domain layer
  3. DIP / ports-and-adapters framing
  4. infra depends on domain, not reverse
  5. concrete infra class or SQL type = leak

basics

~20 s

Yes, when a rule needs to look across many records, like checking an email is unique. The line: depend on an interface defined by the domain, never a concrete database or HTTP class, and never let query details leak in.

solid answer

~50 s

Some rules are genuinely about a whole collection rather than a single instance - uniqueness constraints are the clearest example, since a new Customer has nothing to compare itself against. That still counts as domain logic, so it's legitimate for a domain service to depend on a Repository, as long as the dependency is an interface owned by the domain layer (the Dependency Inversion Principle, or ports and adapters), with the concrete database implementation supplied at the infrastructure layer and wired in at startup. The line gets crossed when the service depends on infrastructure-flavored abstractions instead - a concrete ORM class, a raw SQL query, an HTTP client, or a framework-specific type in its signature - because then the domain layer's tests and reasoning become entangled with technical concerns, and the model stops being portable across infrastructure choices.

go deeper

for a junior

Should recognize that a uniqueness check needs to look at other records somehow and accept that a domain service can use a repository for that.

for a middle

Should articulate that the repository should be an interface, not a concrete database class, injected into the domain service.

for a senior

Should explain the Dependency Inversion Principle mechanics of who owns the interface and why, and identify infrastructure-leak smells in method signatures.

for a principal

Should reason about this as an architectural boundary decision with organization-wide consequences - test speed, migration blast radius, and how to prevent leaks systematically (e.g. via module boundaries or architecture tests) rather than case-by-case review.

## The principle the domain layer holds to The general principle in DDD's layered view of an application is that the domain layer should be able to express and enforce business rules without knowing anything about how those rules are implemented technically: - no SQL - no HTTP - no framework annotations - ideally not even an awareness of which database engine is in use Most of the time this is achieved simply: an entity or value object works purely off in-memory data given to it. But some business rules cannot be evaluated purely in-memory from the perspective of a single object, because they're inherently about a relationship to data the object doesn't hold - the paradigm case is a **uniqueness constraint**. A rule like 'no two Customers may share an email address' cannot be checked by a `Customer` entity being constructed, because that `Customer` has nothing to compare itself against; the rule is a property of the whole collection of existing Customers, not of any one instance. ## Where the Repository legitimately comes in This is exactly the situation a Domain Service is meant to handle, and it's also the situation where the domain layer legitimately needs to reach outside itself for data - via a Repository. The resolution DDD uses (borrowed from and consistent with the broader **Dependency Inversion Principle**, and often discussed under the 'ports and adapters' or hexagonal architecture framing) is: 1. to define the Repository as an **interface that lives in the domain layer** and is expressed entirely in domain vocabulary and domain types - for example, an interface with a method like `existsByEmail(Email email): Boolean`, using the domain's own `Email` value object, not a raw string, and returning a plain boolean, not a database cursor or an ORM entity. 2. The domain service (say, a `UniqueEmailChecker` or similar) **depends only on this interface**. 3. The concrete implementation of that interface - the class that actually runs a SQL query or calls a search index - **lives in the infrastructure layer** and implements the interface, and is wired into the domain service at application startup via dependency injection. This means the domain layer, including the domain service, has **zero compile-time or runtime knowledge** of which database, ORM, or query mechanism actually answers the question - it only knows the shape of the answer it needs. ## What the indirection buys The benefit this buys is significant and is the whole reason the indirection is worth the extra interface: - the domain logic - what counts as a violation of uniqueness, when the check should be performed, what to do if it fails - can be **unit tested with a simple in-memory fake** implementing the repository interface, with no database, no test containers, and no network calls, making the tests fast and the domain logic verifiable in isolation from infrastructure concerns entirely. - It also means the actual **persistence technology can change** - swapping a relational database for a search index, for instance - without touching a single line of domain logic, only the infrastructure-layer implementation of the interface. ## Where the line gets crossed The line gets crossed, and infrastructure concerns leak into the domain layer, in several recognizable ways. 1. **The most direct is injecting a concrete infrastructure class instead of the interface** - for example, a domain service depending on `JpaCustomerRepository` or a specific ORM's Session/EntityManager type directly, rather than on a domain-owned `CustomerRepository` interface; at that point the domain service's compilation and testing are coupled to a specific persistence framework, and swapping technology means rewriting domain code. 2. **A second is a repository interface whose method signatures leak infrastructure types** - returning a paginated database cursor type, or accepting a raw SQL fragment as a parameter, rather than domain types and simple values; even though it's still technically an interface, its shape betrays and locks in the implementation technology. 3. **A third, more subtle case is a domain service reaching for infrastructure directly for something other than a repository** - making an HTTP call to a third-party fraud-check API, for instance, without going through a similarly domain-owned interface (an abstraction like `FraudCheck` rather than a concrete HTTP client) - which is the same leak in a different guise: the domain layer now has a compile-time dependency on an HTTP library and its exception types. ## The failure modes, over a project's lifetime The failure modes of getting this wrong compound over a project's lifetime rather than causing an immediate crash. - **Domain-layer unit tests slow down** and require test databases or mocked HTTP servers, which teams often respond to by writing fewer of them, since the friction of setting up infrastructure for every test discourages the kind of fast, frequent testing that catches regressions in business rules early. - **Changing infrastructure** - migrating databases, replacing a third-party API - becomes a domain-layer change instead of an infrastructure-layer change, expanding the blast radius and risk of what should be a purely technical migration. - **And architecturally, over time, the domain layer accretes framework-specific annotations and types** throughout, until it can no longer be extracted, reused, or reasoned about independently of the specific technology stack it happened to be built on - defeating one of the central promises of DDD's layered approach, which is that the domain model should be the most stable, technology-independent part of the system precisely because it encodes the business rules that outlive any particular database or framework choice.

  • Who defines the Repository interface's method signatures - the domain layer or the infrastructure layer?
    The domain layer defines the interface, expressed in domain vocabulary and types, precisely because the domain is what needs the capability and knows what shape of answer it requires; the infrastructure layer only supplies an implementation that satisfies that interface, which is the Dependency Inversion Principle in action - infrastructure depends on the domain's abstraction, not the other way around.
  • Is a uniqueness check like this always a Domain Service, or could it sometimes be enforced differently?
    It's often expressed as a domain service, but in practice teams frequently also add a database-level unique constraint as a defense-in-depth safety net against race conditions, since two concurrent requests could both pass the domain-level check before either has committed; the domain-service check documents and enforces the business intent, while the database constraint guarantees correctness under concurrency.
  • What if the uniqueness check needs to call an external third-party service instead of a local database?
    The same principle applies: define a domain-owned interface expressing the capability in domain terms (e.g., a TaxIdVerifier with a verify(TaxId) method), and put the actual HTTP call, retry logic, and API-specific error handling in an infrastructure-layer adapter implementing that interface, keeping the domain service free of any awareness that a network call is even involved.

Depending on a repository interface is like a chef ordering ingredients through a written order form (the interface) without needing to know which supplier truck delivers them; the moment the chef starts calling the supplier's warehouse directly and demanding a specific truck route, the kitchen's recipes become dependent on one supplier's logistics.

saying these in an interview costs you the question

  • Injects a concrete ORM or database-specific repository class directly into a domain service
  • Repository interface methods return database cursor types or accept raw SQL/query-language strings
  • Domain service makes an HTTP call directly instead of going through a domain-owned interface
  • Can't explain who 'owns' the repository interface (domain layer, not infrastructure)
  • Believes any repository dependency at all makes something 'not really' a Domain Service
  • No answer for what happens under concurrent requests racing past a uniqueness check

context