business-logic-entry-point-repository-interface
Repository Interface for Business Logic Entry Points
Goal
Business-logic entry points must depend on a repository interface — not on the concrete repository implementation.
When the programming language supports interfaces, protocols, traits, abstract classes, or equivalent abstraction mechanisms, the entry point must reference the repository through that abstraction. The concrete implementation is provided from outside the entry point — through dependency injection, constructor parameters, module-level wiring, or the equivalent pattern used by the project.
This rule decouples the business logic from the persistence implementation. The entry point defines what repository operations it needs; the infrastructure layer decides how those operations are fulfilled.
When the language does not support interfaces or equivalent abstraction mechanisms, this skill does not apply.
What Counts as In Scope
Apply this skill to code that does one or more of these things:
- defines a business-logic entry point that uses a repository
- imports or references a repository from a business-logic entry point
- defines how a repository is provided to a business-logic entry point
- introduces a new repository dependency in a business-logic entry point
Do not apply this skill when:
- the programming language does not support interfaces, protocols, traits, or equivalent abstraction mechanisms
- the task only modifies the repository implementation without changing how the entry point depends on it
The Rule
-
Define a repository interface for each repository.
- Use the language's idiomatic abstraction mechanism — interface, protocol, trait, abstract class, or equivalent.
- The interface declares the repository operations using domain-term names and domain types.
- The interface lives in the business-logic or domain layer, not in the infrastructure or persistence layer.
-
The entry point depends on the interface, not the implementation.
- The entry point's parameters, constructor arguments, or module-level references must use the repository interface type.
- The entry point must not import or reference the concrete repository implementation.
- The entry point must not import or reference persistence-technology modules.
-
The concrete implementation is provided from outside the entry point.
- The infrastructure or composition layer creates the concrete repository and provides it to the entry point.
- The wiring mechanism follows the project's conventions — constructor injection, function parameters, module wiring, or equivalent.
-
The concrete implementation satisfies the interface.
- The repository implementation implements, conforms to, or satisfies the repository interface.
- The implementation handles persistence-technology details internally — ORM calls, database clients, query builders — without leaking them through the interface.
Detection Workflow
-
Identify repository dependencies in business-logic entry points.
- Find imports, parameters, constructor arguments, or references to repositories in entry-point code.
-
Check whether the dependency is on an interface or a concrete implementation.
- If the entry point references a repository interface type — correct.
- If the entry point references a concrete repository class, module, or implementation — this skill applies.
-
Check whether a repository interface exists.
- If an interface exists but the entry point bypasses it, fix the dependency.
- If no interface exists, create one based on the operations the entry point needs.
-
Check where the concrete implementation is provided.
- Verify that wiring happens outside the entry point — in a composition root, dependency injection container, or equivalent.
Writing or Changing Entry Points
-
Define the repository interface if it does not exist.
- Declare the operations the entry point needs, using domain-term names and domain types.
- Place the interface in the business-logic or domain layer — alongside or near the entry point, following the project's conventions.
-
Make the entry point depend on the interface.
- Use the interface type for the repository parameter or dependency.
- Remove any import of the concrete repository implementation from the entry-point file.
-
Implement the interface in the infrastructure layer.
- Create or update the concrete repository to satisfy the interface.
- Keep all persistence-technology details inside the implementation.
-
Wire the concrete implementation to the entry point from outside.
- Provide the concrete repository to the entry point at the composition level.
- Follow the project's existing wiring conventions.
Delegation to Execution Context
When the business-logic-entry-point-execution-context skill is active in the project, repository interface method signatures do not include the transaction parameter. The repository implementation retrieves the transaction from the execution context internally. When the transaction from the execution context is undefined or null, the repository must create a new standalone transaction for that operation. All other rules from this skill still apply: the entry point depends on the interface, the concrete implementation is provided from outside, and the interface lives in the business-logic or domain layer.
Examples
Interface and entry point depending on it (with execution context):
// Repository interface — in the business-logic layer
interface OrderRepository {
create(order: Order): ResultAsync<Order, RepositoryError>
findById(orderId: OrderId): ResultAsync<Order, RepositoryError>
}
// Entry point depends on the interface
function createOrderCommandHandler(
orderRepository: OrderRepository, // interface, not implementation
) {
return function (command: CreateOrderCommand) {
return runWithExecutionContext(
() => orderRepository.create(Order.create(command.customerId, command.items)),
{ transaction: { isolationLevel: "REPEATABLE READ" } },
)
}
}
Interface and entry point (explicit passing, for languages without execution context):
// Repository interface — in the business-logic layer
interface OrderRepository {
save(transaction: Transaction, order: Order): ResultAsync<Order, RepositoryError>
findById(transaction: Transaction, orderId: OrderId): ResultAsync<Order, RepositoryError>
}
// Entry point depends on the interface
function createOrderCommandHandler(
orderRepository: OrderRepository, // interface, not implementation
) {
return function (command: CreateOrderCommand) {
return withTransaction((transaction) =>
orderRepository.save(transaction, Order.create(command.customerId, command.items))
)
}
}
# Repository interface — in the business-logic layer
class OrderRepository(Protocol):
def save(self, tx: Transaction, order: Order) -> Order: ...
def find_by_id(self, tx: Transaction, order_id: OrderId) -> Order: ...
# Entry point depends on the interface
def create_order_command_handler(
order_repository: OrderRepository, # protocol, not implementation
) -> Callable:
def handler(command: CreateOrderCommand):
with transaction() as tx:
order = Order.create(customer_id=command.customer_id, items=command.items)
return order_repository.save(tx, order)
return handler
// Repository interface — in the business-logic layer
interface OrderRepository {
fun save(tx: Transaction, order: Order): Order
fun findById(tx: Transaction, orderId: OrderId): Order
}
// Entry point depends on the interface
fun createOrderCommandHandler(
orderRepository: OrderRepository, // interface, not implementation
) = fun(command: CreateOrderCommand): Order {
return withTransaction { tx ->
val order = Order.create(customerId = command.customerId, items = command.items)
orderRepository.save(tx, order)
}
}
Not this — entry point depending on the concrete implementation:
// Bad: entry point imports and depends on concrete implementation
import { PrismaOrderRepository } from '../infrastructure/prisma-order-repository'
function createOrderCommandHandler(
orderRepository: PrismaOrderRepository, // concrete implementation, not interface
) {
return function (command: CreateOrderCommand) {
// ...
}
}
Review Questions
When reading or reviewing code, ask:
- Does the programming language support interfaces, protocols, traits, or equivalent abstractions?
- Does the entry point depend on a repository interface or on a concrete implementation?
- Does the entry point import any concrete repository class or persistence-technology module?
- Is the repository interface defined in the business-logic or domain layer?
- Is the concrete implementation provided to the entry point from outside?
If the entry point depends on a concrete repository implementation in a language that supports interfaces, apply this skill.
Report the Outcome
When finishing the task:
- state whether the language supports interfaces or equivalent abstraction mechanisms
- state which repository interfaces were created or already existed
- state which entry points were changed to depend on the interface instead of the concrete implementation
- state where the concrete implementation is wired to the entry point
More from code-sherpas/agent-skills
neverthrow-return-types
Require `neverthrow`-based return types in TypeScript and JavaScript code whenever the surrounding technology allows it. Use when creating, refactoring, reviewing, or extending standalone functions, exported module functions, class methods, object methods, service methods, repository methods, and similar APIs that should expose explicit success and failure result types in their signatures. Prefer `Result<T, E>` for synchronous code and `ResultAsync<T, E>` for asynchronous code. Only skip a `neverthrow` return type when a framework, library, runtime interface, or externally imposed contract is incompatible and requires a different return shape.
16neverthrow-wrap-exceptions
Capture exceptions and promise failures with `neverthrow` instead of hand-written `try/catch` in TypeScript and JavaScript code. Use when wrapping synchronous functions that may throw, promise-returning functions that may throw before returning, existing `PromiseLike` values that may reject, or third-party APIs such as parsers, database clients, HTTP clients, file-system helpers, serializers, and SDK calls. Prefer `Result.fromThrowable` for synchronous throwers, `ResultAsync.fromThrowable` for promise-returning functions that may throw or reject, and `ResultAsync.fromPromise` when you already have a `PromiseLike` value in hand. Only keep `try/catch` when the language construct, cleanup requirement, or framework boundary truly requires it.
11atomic-design
Create or update web UI components with a strict reuse-first workflow. Use when building, refactoring, restyling, or extending frontend or template components while minimizing raw DOM or HTML by reusing or generalizing existing components first.
10write-persistence-representations
Create or update persistence-layer data representations in any stack, including ORM entities, schema definitions, table mappings, document models, collection definitions, and similar database-facing code. Use when agents needs to add or change persisted fields, identifiers, relationships, indexes, timestamps, auditing fields, or storage mappings in frameworks, libraries, or ORMs such as Prisma, TypeORM, Sequelize, Drizzle, Mongoose, Hibernate/JPA, Doctrine, Ecto, Active Record, or equivalent persistence technologies.
7business-logic
Identify, interpret, review, or write business logic in code. Use when an agent needs to decide whether code expresses business rules, business algorithms, or business workflows, or when it must implement, preserve, or refactor code that creates, stores, or transforms data according to real business policies.
7immutable-domain-entities
Require the immutable design pattern for domain entities. Use when an agent needs to create, modify, review, or interpret domain entities and should preserve identity while expressing state changes through new immutable instances. Domain entities must be modeled as immutable classes, not as plain type aliases or interfaces paired with standalone functions.
7