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