repository-service-pattern.md
Updated Repository Service Pattern with comprehensive pagination standards and corrected configuration examples
repository-service-pattern.mdv1.0.119.2 KB
repository-service-pattern.md(markdown)
1# Repository Service Pattern
2
3**A specialized implementation of the [Micro-Block Design Pattern](../micro-block/micro-block-pattern.md) for data access layer abstraction in AI-collaborative development environments.**
4
5## Overview
6
7The Repository Service Pattern extends the foundational [Micro-Block Design Pattern](utaba/main/patterns/micro-block/micro-block-pattern.md) with specialized capabilities for data access layer management. This pattern provides a clean separation between service-level abstractions and repository-level implementation details, enabling AI agents to discover and compose data access operations through well-defined contracts while maintaining performance through repository-managed caching strategies.
8
9**Key Innovation**: Services provide discovery metadata and dependency injection abstraction, while repositories handle all data access logic, caching, and performance optimization internally.
10
11## Core Principles Extension
12
13This pattern inherits all 9 principles from the core micro-block pattern and adds repository-specific specializations:
14
15### 1. **Service-Repository Separation** *(extends Service Abstraction)*
16- **Services** provide lightweight abstractions with rich metadata for AI discovery
17- **Repositories** handle all data access, caching, and performance optimization
18- Clear boundary: Services delegate, repositories execute
19
20### 2. **Repository Portability** *(extends Self-Contained Modularity)*
21- Repositories define their own configuration interfaces
22- No dependencies on project-specific configuration classes
23- Enables cross-project reusability and npm package publishing
24
25### 3. **Cache Encapsulation** *(extends Loose Coupling)*
26- Caching logic is completely encapsulated within repositories
27- Services have zero knowledge of caching strategies
28- Cache services are dependency-injected into repositories, not managed by services
29
30### 4. **Database Abstraction** *(extends Substitutability)*
31- Multiple repository implementations can satisfy the same service contract
32- AI agents can select optimal implementations based on metadata
33- Runtime switching between database technologies without service changes
34
35## Architecture Components
36
37### Service Layer (Thin Abstraction)
38```typescript
39/**
40 * Service provides discovery metadata and delegates to repository
41 */
42export class UserRepositoryService extends BaseService implements IUserRepositoryService {
43 private userRepository: UserRepository;
44
45 constructor(
46 config: UserRepositoryServiceConfig, // Service-specific config interface
47 databaseService: IDatabaseService, // Database dependency
48 cacheService: ICacheService, // Cache dependency for repository
49 logger: ILogger // Logging dependency
50 ) {
51 super();
52 this.validateConfiguration(config); // Inherited validation
53
54 // Repository receives injected dependencies
55 this.userRepository = new UserRepository(databaseService, cacheService, logger);
56 }
57
58 // Pure delegation - no business logic
59 async findByEmail(email: string): Promise<User | null> {
60 return this.userRepository.findByEmail(email);
61 }
62
63 getMetadata(): ServiceMetadata {
64 return UserRepositoryService.metadata; // Rich metadata for AI discovery
65 }
66}
67```
68
69### Repository Layer (Implementation & Caching)
70```typescript
71/**
72 * Repository handles all data access, caching, and optimization
73 */
74export class UserRepository extends BaseRepository<User> implements IUserRepository {
75 constructor(
76 db: IDatabaseService,
77 private cacheService?: ICacheService,
78 logger?: ILogger
79 ) {
80 super(db, logger);
81 }
82
83 async findByEmail(email: string): Promise<User | null> {
84 // Repository manages its own caching strategy
85 const cacheKey = `user:email:${email.toLowerCase()}`;
86
87 if (this.cacheService) {
88 const cached = await this.cacheService.get<User>(cacheKey);
89 if (cached) return cached;
90 }
91
92 const user = await this.findFirst({ email });
93
94 if (this.cacheService && user) {
95 await this.cacheService.set(cacheKey, user, 300); // 5 min TTL
96 }
97
98 return user;
99 }
100}
101```
102
103## Service Configuration Pattern
104
105### Portable Configuration Interfaces
106```typescript
107/**
108 * CORRECT: Service defines its own configuration interface
109 */
110export interface UserRepositoryServiceConfig {
111 // Empty - no configuration needed for this service
112}
113
114/**
115 * WRONG: Service depends on project configuration
116 */
117interface BadServiceConfig {
118 appConfig: AppConfig; // ❌ Project-specific dependency
119 globalSettings: any; // ❌ Non-portable
120}
121```
122
123### ServiceRegistry Configuration Mapping
124```typescript
125// ServiceRegistry acts as configuration adapter
126this.register('IUserRepositoryService', () => {
127 // Empty configuration object
128 const serviceConfig: UserRepositoryServiceConfig = {};
129
130 const databaseService = this.get<IDatabaseService>('IDatabaseService');
131 const cacheService = this.get<ICacheService>('ICacheService:user');
132 const logger = this.getLogger('UserRepositoryService');
133
134 return new UserRepositoryService(serviceConfig, databaseService, cacheService, logger);
135});
136```
137
138## Rich Metadata for AI Discovery
139
140### Service Metadata Structure
141```typescript
142export class UserRepositoryService extends BaseService {
143 static readonly metadata: ServiceMetadata = {
144 name: 'UserRepositoryService',
145 displayName: 'User Repository Service',
146 description: 'Provides user management operations with caching and performance optimization',
147 contract: 'IUserRepositoryService',
148 implementation: 'UserRepositoryService',
149 version: '1.0.0',
150 contractVersion: '1.0',
151
152 // Features for AI selection
153 features: [
154 'User CRUD operations',
155 'Email verification',
156 'API key generation and validation',
157 'Performance caching',
158 'Security-focused operations'
159 ],
160
161 // Limitations for AI awareness
162 limitations: [
163 'Requires SQL Server database',
164 'Author ID is immutable once set',
165 'API key validation requires author ID'
166 ],
167
168 // Dependencies for DI resolution
169 dependencies: {
170 services: ['IDatabaseService', 'ICacheService']
171 },
172
173 // Deployment requirements
174 requirements: {
175 services: ['IDatabaseService', 'ICacheService'],
176 runtime: ['SQL Server', 'Node.js 18+'],
177 configuration: {
178 required: [],
179 optional: []
180 }
181 },
182
183 // AI selection guidance
184 recommendations: {
185 idealFor: [
186 'User authentication systems',
187 'API key management',
188 'Email verification workflows'
189 ],
190 acceptableFor: [
191 'General user management',
192 'Registration systems'
193 ],
194 notRecommendedFor: [
195 'High-frequency read operations without caching',
196 'Systems requiring complex user hierarchies'
197 ]
198 },
199
200 // Configuration schema for validation
201 configurationSchema: {
202 // Empty - no configuration needed
203 }
204 };
205}
206```
207
208## Registry Integration Patterns
209
210### ✅ CORRECT: Single Access Path
211```typescript
212// Commands access services through ServiceRegistry
213const serviceRegistry = ServiceRegistry.getInstance();
214const userService = serviceRegistry.get<IUserRepositoryService>('IUserRepositoryService');
215
216// Services delegate to repositories
217const user = await userService.findByEmail('user@example.com');
218```
219
220### ❌ WRONG: Direct Repository Access
221```typescript
222// DON'T: Bypass service layer
223const repository = new UserRepository(dbService, cacheService, logger);
224
225// DON'T: Import repositories directly in commands
226import { UserRepository } from '@/repositories/UserRepository';
227```
228
229## Error Handling & Validation
230
231### Repository-Level Error Management
232```typescript
233export class UserRepositoryError extends BaseError {
234 constructor(message: string, code?: string, details?: Record<string, any>) {
235 super(message, code, details);
236 this.name = 'UserRepositoryError';
237 }
238}
239
240export class UserRepository extends BaseRepository<User> {
241 async findByEmail(email: string): Promise<User | null> {
242 try {
243 // Repository handles its own validation
244 if (!email || !email.includes('@')) {
245 throw new UserRepositoryError(
246 'Invalid email format provided',
247 'INVALID_EMAIL',
248 { email, validationRule: 'must contain @' }
249 );
250 }
251
252 return await this.findFirst({ email });
253 } catch (error) {
254 // Repository-specific error handling
255 if (error instanceof UserRepositoryError) {
256 throw error;
257 }
258
259 throw new UserRepositoryError(
260 'Database operation failed',
261 'DATABASE_ERROR',
262 { originalError: error.message }
263 );
264 }
265 }
266}
267```
268
269## Common Anti-Patterns
270
271### ❌ Service-Level Caching
272```typescript
273// WRONG: Service implementing caching logic
274export class BadUserRepositoryService extends BaseService {
275 private cacheService: ICacheService;
276
277 async findByEmail(email: string): Promise<User | null> {
278 // ❌ Service should not manage caching
279 const cacheKey = `user:${email}`;
280 const cached = await this.cacheService.get(cacheKey);
281 if (cached) return cached;
282
283 const user = await this.userRepository.findByEmail(email);
284 await this.cacheService.set(cacheKey, user);
285 return user;
286 }
287}
288```
289
290### ❌ Project-Specific Configuration Dependencies
291```typescript
292// WRONG: Service depends on project configuration classes
293export class BadRepositoryService extends BaseService {
294 constructor(
295 private appConfig: AppConfig, // ❌ Project-specific dependency
296 databaseService: IDatabaseService
297 ) {
298 super();
299 // Service is now coupled to AppConfig structure
300 }
301}
302```
303
304### ❌ Direct Repository Instantiation in Commands
305```typescript
306// WRONG: Commands creating repositories directly
307export class SomeCommand {
308 constructor(services?: Record<string, any>) {
309 const dbService = services?.['IDatabaseService'];
310 // ❌ Bypasses service layer abstraction
311 this.userRepository = new UserRepository(dbService);
312 }
313}
314```
315
316## Best Practices Summary
317
318### Service Layer
3191. **Pure Delegation** - Services contain no business logic, only delegation
3202. **Rich Metadata** - Provide comprehensive metadata for AI discovery
3213. **Configuration Portability** - Define service-specific configuration interfaces
3224. **Zero Cache Knowledge** - Services know nothing about caching strategies
323
324### Repository Layer
3251. **Encapsulated Caching** - Repositories manage their own caching strategies
3262. **Domain-Specific Optimization** - Implement caching patterns optimized for data access patterns
3273. **Error Handling** - Provide domain-specific error types and handling
3284. **Performance Focus** - Optimize for specific database technologies and use cases
3295. **Pagination Implementation** - Use `PaginatedResult<T>` for collection methods with SQL OFFSET/LIMIT
330
331### Registry Integration
3321. **Single Access Path** - Always access services through ServiceRegistry
3332. **Service Defaults** - Services use their own default configurations, not project config
3343. **Dependency Injection** - All dependencies injected through constructor
3354. **AI Discovery** - Rich metadata enables intelligent service selection
336
337### Testing Strategy
3381. **Service Testing** - Focus on delegation and metadata correctness
3392. **Repository Testing** - Comprehensive testing of caching and data access logic
3403. **Mock Boundaries** - Mock at the repository level for service tests
3414. **Integration Testing** - Test full service → repository → database flow
342
343## Pagination Standards
344
345All repository collection methods must implement pagination using the `PaginatedResult<T>` pattern for performance and consistency.
346
347### PaginatedResult Type Definition
348```typescript
349/**
350 * Standardized pagination response structure
351 */
352export interface PaginatedResult<T> {
353 data: T[];
354 pagination: {
355 total: number;
356 offset: number;
357 limit: number;
358 };
359}
360```
361
362### Repository Pagination Implementation
363```typescript
364/**
365 * Repository handles SQL-level pagination for performance
366 */
367export class UserRepository extends BaseRepository<User> {
368 async searchUsers(query?: string, offset: number = 0, limit: number = 100): Promise<PaginatedResult<User>> {
369 // Count query for total records
370 const countQuery = `
371 SELECT COUNT(*) as total
372 FROM Users
373 WHERE (@query IS NULL OR name LIKE '%' + @query + '%')
374 `;
375
376 // Data query with SQL OFFSET/LIMIT
377 const dataQuery = `
378 SELECT * FROM Users
379 WHERE (@query IS NULL OR name LIKE '%' + @query + '%')
380 ORDER BY createdAt DESC
381 OFFSET @offset ROWS
382 FETCH NEXT @limit ROWS ONLY
383 `;
384
385 // Execute queries in parallel for performance
386 const [countResult, users] = await Promise.all([
387 this.db.queryOne<{ total: number }>(countQuery, { query }),
388 this.db.queryMany<User>(dataQuery, { query, offset, limit })
389 ]);
390
391 return {
392 data: users,
393 pagination: {
394 total: countResult.total,
395 offset,
396 limit
397 }
398 };
399 }
400
401 // All collection methods follow this pattern
402 async getActiveUsers(offset: number = 0, limit: number = 100): Promise<PaginatedResult<User>> {
403 const countQuery = `SELECT COUNT(*) as total FROM Users WHERE isActive = 1`;
404 const dataQuery = `
405 SELECT * FROM Users
406 WHERE isActive = 1
407 ORDER BY lastLoginAt DESC
408 OFFSET @offset ROWS
409 FETCH NEXT @limit ROWS ONLY
410 `;
411
412 const [countResult, users] = await Promise.all([
413 this.db.queryOne<{ total: number }>(countQuery),
414 this.db.queryMany<User>(dataQuery, { offset, limit })
415 ]);
416
417 return {
418 data: users,
419 pagination: { total: countResult.total, offset, limit }
420 };
421 }
422}
423```
424
425### Service Layer Pagination Delegation
426```typescript
427/**
428 * Services delegate pagination parameters without modification
429 */
430export class UserRepositoryService extends BaseService implements IUserRepositoryService {
431 // Pure delegation with pagination parameters
432 async searchUsers(query?: string, offset?: number, limit?: number): Promise<PaginatedResult<User>> {
433 return this.userRepository.searchUsers(query, offset, limit);
434 }
435
436 async getActiveUsers(offset?: number, limit?: number): Promise<PaginatedResult<User>> {
437 return this.userRepository.getActiveUsers(offset, limit);
438 }
439}
440```
441
442### Key Pagination Requirements
443
4441. **SQL-Level Pagination**: Always use `OFFSET/FETCH NEXT` for database performance
4452. **Default Limits**:
446 - API routes: 100 records (balanced for performance)
447 - Internal data loading: 999 records (complete data sets)
4483. **No Memory Slicing**: Never load all data and slice in application memory
4494. **Service Delegation**: Services pass pagination parameters unchanged to repositories
4505. **Parallel Queries**: Execute count and data queries in parallel for optimal performance
451
452### Command Usage with Pagination
453```typescript
454export class ListUsersCommand {
455 async execute(): Promise<ListUsersOutput> {
456 // Extract pagination from input with defaults
457 const offset = this.input?.offset ?? 0;
458 const limit = this.input?.limit ?? 100;
459
460 // Use repository pagination directly
461 const result = await this.userService.searchUsers(query, offset, limit);
462
463 return {
464 users: result.data,
465 total: result.pagination.total,
466 offset: result.pagination.offset,
467 limit: result.pagination.limit
468 };
469 }
470}
471```
472
473### API Route Pagination
474```typescript
475export async function GET(request: NextRequest) {
476 const { offset, limit } = extractPaginationParams(request.url);
477
478 // Default limit is 100 for API routes
479 const result = await userService.searchUsers(query, offset, limit);
480
481 return NextResponse.json({
482 data: result.data,
483 pagination: result.pagination
484 });
485}
486```
487
488### Common Pagination Anti-Patterns
489
490#### ❌ Loading All Data and Slicing
491```typescript
492// WRONG: Loads entire dataset into memory
493async searchUsers(query?: string, offset: number = 0, limit: number = 100) {
494 const allUsers = await this.db.queryMany<User>(`SELECT * FROM Users`);
495 const filtered = allUsers.filter(u => u.name.includes(query || ''));
496 return filtered.slice(offset, offset + limit); // ❌ Memory inefficient
497}
498```
499
500#### ❌ Service-Level Pagination Logic
501```typescript
502// WRONG: Service implementing pagination logic
503export class BadUserService extends BaseService {
504 async searchUsers(query?: string, page: number = 1, pageSize: number = 10) {
505 // ❌ Services should not calculate offset/limit
506 const offset = (page - 1) * pageSize;
507 const users = await this.repository.getUsers();
508 return users.slice(offset, offset + pageSize);
509 }
510}
511```
512
513#### ❌ Missing Total Count
514```typescript
515// WRONG: No total count for pagination metadata
516async searchUsers(query?: string, offset: number = 0, limit: number = 100) {
517 const users = await this.db.queryMany<User>(`
518 SELECT * FROM Users
519 OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY
520 `, { offset, limit });
521
522 return users; // ❌ Missing pagination metadata
523}
524```
525
526## Implementation Checklist
527
528### Creating a New Repository Service
529
530- [ ] **Define Service Interface** extending `BaseService`
531- [ ] **Create Service-Specific Configuration Interface** (never depend on project config)
532- [ ] **Implement Service Class** with pure delegation methods
533- [ ] **Define Rich Metadata** with features, limitations, and recommendations
534- [ ] **Register in ServiceRegistry** with configuration mapping
535- [ ] **Create Repository Implementation** with internal caching logic
536- [ ] **Implement Repository Interface** extending `BaseRepository`
537- [ ] **Add Comprehensive Tests** for both service and repository layers
538- [ ] **Document Usage Patterns** for AI discovery and human developers
539
540### Service Validation
541- [ ] Service contains zero business logic (only delegation)
542- [ ] Service has no cache management code
543- [ ] Service uses its own configuration interface
544- [ ] Service provides comprehensive metadata
545- [ ] All dependencies injected through constructor
546- [ ] Service implements proper health checks and lifecycle methods
547
548### Repository Validation
549- [ ] Repository handles all caching internally
550- [ ] Repository implements domain-specific optimization
551- [ ] Repository provides detailed error handling
552- [ ] Repository supports configuration injection
553- [ ] Repository includes comprehensive test coverage
554- [ ] Repository follows BaseRepository patterns
555
556### Pagination Implementation Checklist
557
558**Repository Pagination Requirements:**
559- [ ] **Collection methods return `PaginatedResult<T>`** with data and pagination metadata
560- [ ] **SQL queries use OFFSET/FETCH NEXT** for database-level pagination
561- [ ] **Count queries run in parallel** with data queries for performance
562- [ ] **Default parameters defined** (offset: 0, limit: 100 for API, 999 for internal)
563- [ ] **Service delegation implemented** - services pass pagination parameters unchanged
564- [ ] **No memory pagination** - never load all data and slice in application layer
565
566**Service Pagination Requirements:**
567- [ ] Service methods accept optional offset/limit parameters
568- [ ] Service methods delegate to repository without modification
569- [ ] Service metadata documents pagination support
570- [ ] Service configuration specifies pagination defaults
571- [ ] No pagination logic implemented in service layer
572
573This pattern ensures clean separation of concerns, maximum reusability, and optimal performance while maintaining the AI-collaborative development principles of the core micro-block architecture.
Metadata
- Path
- utaba/main/patterns/micro-block/repository-service-pattern.md
- Namespace
- utaba/main/patterns/micro-block
- Author
- utaba
- Category
- patterns
- Contract Version
- 1.0.1
- MIME Type
- text/markdown
- Published
- 26-Jul-2025
- Last Updated
- 26-Jul-2025