1. Introduction
Understanding the advantages and limitations of Repository patterns
1Repository Pattern: Advantages and Limitations
The "Repository pattern" is widely used to organize data access responsibilities and abstract database operations from business logic. By introducing a Repository, you can reduce dependencies on SQL and query languages, achieving more testable code—this is a significant benefit.
However, this pattern also has limitations in real-world development. When a Repository handles both "data retrieval logic (query construction)" and "database access (execution)," the following problems tend to emerge over time:
- ⚠️Each time slightly different queries are added for different use cases, the Repository becomes bloated
- ⚠️Or, creating separate Repositories for each use case results in numerous Repositories (and interfaces)
- ⚠️To handle various queries, flag parameters and conditional branching increase, reducing method readability and maintainability
- ⚠️Query changes for specific use cases may have unexpected impacts on other features
- ⚠️Information that should be encapsulated within domain objects leaks into the Repository, breaking object-oriented design
💡 Question: Have you ever experienced "using Repository but not getting the benefits properly"?
Why I Felt "Separation" Was Necessary
Facing these challenges repeatedly, I began to question:
"Are query construction and database execution really the same responsibility?"
Query construction—determining which objects and under what conditions to target—is part of "business logic" deeply related to domain rules and use cases. On the other hand, executing queries against a database to retrieve data is merely I/O processing.
Rather than cramming these two into one Repository, shouldn't they be clearly separated? This thinking led me to the Query Delegation Pattern approach.
Here, I will explain in detail the design philosophy and concrete effects of the Query Delegation Pattern. Let's explore how to leverage the advantages of traditional Repository patterns while further clarifying responsibilities and improving domain model health.
2. Problems with Traditional Repository Patterns
Issues from Mixing Query Construction and I/O
In typical Repository patterns, Repository classes handle both "query construction to determine what data to retrieve" and "database query execution."
However, from the perspective that query construction is directly related to business logic while I/O belongs to the infrastructure layer, these are fundamentally different responsibilities. When these two are mixed within a Repository, various problems gradually emerge:
- Query assembly methods are confined within the Repository, making domain object intentions and context less visible
- Query changes have wide-ranging impacts, increasing the risk of changes affecting all features using the Repository
- Mock classes implementing Repository interfaces need to be created for testing
- When processing concentrates in one Repository, numerous methods need to be implemented, hindering continuous development
- Separating Repositories per use case results in creating similar Repository interfaces and implementation classes, creating psychological barriers to test construction
Ideally, the domain side (use case side) should explicitly express "what data is needed," and delegate only "execution" to the Repository, improving design health.
Reusability vs. Loss of Context
One of the attractions of the Repository pattern is its high reusability. For example, if a Repository has one method to "retrieve records filtered by specific conditions," it can be commonly used across multiple use cases.
However, this high reusability often comes at the cost of unclear business intent. Repository methods designed generically tend to lose information about why that data is needed and in what context it's used.
💭 Consideration:The more reusable Repository methods become, the further they drift from expressing specific business intent
This problem is also important from a testing perspective. Methods with unclear business intent make it ambiguous "what should be tested" when creating test cases, potentially leading to insufficient testing.
3. What is Query Delegation Pattern?
Basic Concept
Query Delegation Pattern is an approach that clearly separates the responsibilities of "query construction" and "query execution."
2Clear Separation of Responsibilities
🎯 Query Construction (Domain Logic)
- • Determines what data is needed
- • Sets conditions based on business rules
- • Handled by domain objects
⚡ Query Execution (Infrastructure Logic)
- • Executes constructed queries
- • Handles I/O processing with database
- • Handled by Repository
In traditional Repository patterns, Repository classes held both responsibilities. Query Delegation Pattern separates these as follows:
- Domain objects construct queries (what to retrieve)
- Repository executes queries (actual retrieval processing)
💡 Key Point:Repository is only concerned with "how to retrieve" rather than "what to retrieve"
3.5. Query Reusability
?How to Handle Query Reusability?
In Query Delegation Pattern, queries are fundamentally constructed individually for each use case. However, there are certainly cases where "this query should be commonly used across multiple use cases."
Recommended Approach:
- Extract only the queries you want to reuse into traits (or utility classes)
- Only the necessary use cases explicitly utilize these shared queries
🎯 Design Philosophy:Rather than carelessly reusing queries without considering context, carefully examine during the design phase: "Is this query truly safe to use across different contexts?"
This thoughtful approach to reuse design is a key point in Query Delegation Pattern's architecture. By being explicit about when and where queries are shared, we maintain the pattern's core benefits while enabling practical code reuse where it makes sense.
4. Comparison with Traditional Patterns
Implementation Differences
❌ Traditional Repository Pattern
Repository handles both:
- Query construction logic
- Database execution
Problems:
- Mixed responsibilities
- Difficult to test
- Repository bloat
✅ Query Delegation Pattern
Separated responsibilities:
- Domain → Query construction
- Repository → Execution only
Benefits:
- Clear responsibility separation
- Easy to test
- Improved maintainability
5. Implementation in ApexEloquent
ApexEloquent adopts this Query Delegation Pattern at the framework level. Here are the main characteristics during implementation:
3Implementation Features
- 🔧Dynamic Query Construction: Domain objects construct queries based on conditions
- 🔧Built-in Repository: Common execution processing is handled by the built-in Repository
- 🔧Test Support: Query construction and I/O processing can be tested independently
- 🔧Read-only Field Mocking: Flexible data manipulation during testing
6. Summary
Value of Query Delegation Pattern
Query Delegation Pattern is an approach that solves the fundamental problem of traditional Repository patterns—the mixing of "query construction" and "database execution" responsibilities.
Main Effects:
- ✓Improved maintainability through clear responsibility separation
- ✓Enhanced testability without complex mock implementations
- ✓Improved domain model health through proper encapsulation
- ✓Reduced coupling between business logic and data access layers
"Query Delegation Pattern represents not just a technical solution, but a philosophical shift toward cleaner architectural thinking in data access layer design."