# Pattern Comparison: Template vs IAM Service ## Overview This document compares implementation patterns between `_template` (baseline microservice) and `iam-service` (production implementation) to guide documentation updates. ## Module Structure Pattern ### Template Pattern (Simple) ``` src/modules/ ├── common/ # Base repository, shared types ├── feature/ # Example CRUD module ├── health/ # Health checks └── metrics/ # Prometheus metrics ``` ### IAM Pattern (Production) ``` src/modules/ ├── common/ # Base repository, shared types (same) ├── feature/ # Example CRUD module (inherited) ├── health/ # Health checks (same) ├── metrics/ # Prometheus metrics (same) ├── auth/ # + Core authentication ├── rbac/ # + Authorization ├── token/ # + Token management ├── session/ # + Session management ├── mfa/ # + Multi-factor auth ├── social/ # + Social OAuth ├── oidc/ # + OpenID Connect ├── identity/ # + Identity management ├── access/ # + Access workflows └── governance/ # + Compliance & reporting ``` **Documentation Impact**: - Template docs should focus on **foundational patterns** - IAM docs should show **advanced patterns** and **real-world implementation** --- ## Controller Pattern ### Template (Basic) ```typescript // src/modules/feature/feature.controller.ts export class FeatureController { constructor(private service: FeatureService) {} async create(req: Request, res: Response, next: NextFunction) { try { const dto = CreateFeatureDto.parse(req.body); const result = await this.service.create(dto); res.status(201).json({ success: true, data: result }); } catch (error) { next(error); } } } ``` ### IAM (Production with Auth) ```typescript // src/modules/identity/identity.controller.ts export class IdentityController { constructor( private service: IdentityService, private auditService: AuditService ) {} @RequireAuth() @RequirePermission('users', 'create') @RateLimit('strict') async create(req: Request, res: Response, next: NextFunction) { try { const dto = CreateUserDto.parse(req.body); const result = await this.service.create(dto); // Audit logging await this.auditService.log('USER_CREATED', req.user.id, { userId: result.id, correlationId: req.correlationId, }); res.status(201).json({ success: true, data: result }); } catch (error) { next(error); } } } ``` **Pattern Differences**: 1. **Decorators**: IAM uses decorators for auth, permissions, rate limiting 2. **Audit Logging**: IAM logs all operations 3. **Dependency Injection**: IAM injects multiple services (Service + AuditService) **Documentation Impact**: - Template docs: Show basic controller pattern - IAM docs: Show auth decorators, audit logging, multi-service injection --- ## Service Pattern ### Template (Simple Business Logic) ```typescript // src/modules/feature/feature.service.ts export class FeatureService { constructor(private repository: FeatureRepository) {} async create(data: CreateFeatureDto) { // Business validation const existing = await this.repository.findByName(data.name); if (existing) { throw new ConflictError('Feature already exists'); } return await this.repository.create(data); } } ``` ### IAM (with Caching & Complex Logic) ```typescript // src/modules/rbac/rbac.service.ts export class RBACService { constructor( private repository: RBACRepository, private cacheService: CacheService, private auditService: AuditService ) {} async checkPermission( userId: string, resource: string, action: string ): Promise { // Try cache first (L1 → L2) const cacheKey = `user:${userId}:permissions`; const cached = await this.cacheService.get(cacheKey); let permissions: string[]; if (cached) { permissions = cached; } else { // Cache miss - fetch from DB const userRoles = await this.repository.getUserRoles(userId); const rolePermissions = await this.repository.getRolePermissions(userRoles); const directPermissions = await this.repository.getUserPermissions(userId); permissions = [...rolePermissions, ...directPermissions]; // Cache for 5 minutes await this.cacheService.set(cacheKey, permissions, 300); } // Check permission const required = `${resource}:${action}`; const hasPermission = permissions.some(p => this.matchesPermission(p, required)); // Audit log await this.auditService.log( hasPermission ? 'ACCESS_GRANTED' : 'ACCESS_DENIED', userId, { resource, action } ); return hasPermission; } private matchesPermission(granted: string, required: string): boolean { // Handle wildcards: users:*:* matches users:create:org const grantedParts = granted.split(':'); const requiredParts = required.split(':'); return grantedParts.every((part, i) => part === '*' || part === requiredParts[i] ); } } ``` **Pattern Differences**: 1. **Caching**: IAM uses multi-layer caching 2. **Complex Logic**: Permission matching with wildcards 3. **Audit Logging**: Every operation logged 4. **Performance**: Cache-first approach for frequently accessed data **Documentation Impact**: - Template docs: Simple business logic - IAM docs: Caching strategies, complex permission logic, audit logging --- ## Repository Pattern ### Template (Basic CRUD) ```typescript // src/modules/feature/feature.repository.ts export class FeatureRepository extends BaseRepository { constructor(prisma: PrismaClient) { super(prisma, 'feature'); } async findByName(name: string): Promise { return this.prisma.feature.findUnique({ where: { name } }); } } ``` ### IAM (with Joins & Complex Queries) ```typescript // src/modules/rbac/rbac.repository.ts export class RBACRepository extends BaseRepository { constructor(prisma: PrismaClient) { super(prisma, 'role'); } async getUserRoles(userId: string): Promise { const userRoles = await this.prisma.userRole.findMany({ where: { userId, OR: [ { expiresAt: null }, { expiresAt: { gt: new Date() } } ] }, include: { role: true } }); return userRoles.map(ur => ur.role.name); } async getRolePermissions(roleNames: string[]): Promise { const rolePermissions = await this.prisma.rolePermission.findMany({ where: { role: { name: { in: roleNames } } }, include: { permission: true } }); return rolePermissions.map(rp => rp.permission.code); } async getUserPermissions(userId: string): Promise { const userPermissions = await this.prisma.userPermission.findMany({ where: { userId }, include: { permission: true } }); return userPermissions.map(up => up.permission.code); } } ``` **Pattern Differences**: 1. **Complex Joins**: IAM uses nested includes 2. **Conditional Logic**: Expiration checking 3. **Data Aggregation**: Combining data from multiple sources **Documentation Impact**: - Template docs: Basic CRUD with unique lookups - IAM docs: Complex joins, conditional queries, data aggregation --- ## Middleware Pattern ### Template (Basic) ```typescript // src/middlewares/logger.middleware.ts export const loggerMiddleware = ( req: Request, res: Response, next: NextFunction ) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; logger.info('Request completed', { method: req.method, url: req.url, statusCode: res.statusCode, duration, }); }); next(); }; ``` ### IAM (with Correlation IDs & Audit) ```typescript // src/middlewares/logger.middleware.ts export const loggerMiddleware = ( req: Request, res: Response, next: NextFunction ) => { const start = Date.now(); const correlationId = req.correlationId || generateCorrelationId(); // Attach to request for downstream use req.correlationId = correlationId; res.setHeader('x-correlation-id', correlationId); res.on('finish', () => { const duration = Date.now() - start; logger.info('Request completed', { correlationId, method: req.method, url: req.url, statusCode: res.statusCode, duration, userId: req.user?.id, ipAddress: req.ip, userAgent: req.headers['user-agent'], }); // Audit logging for sensitive endpoints if (req.url.startsWith('/api/v1/auth') || req.url.startsWith('/api/v1/rbac')) { auditService.log('API_REQUEST', req.user?.id, { method: req.method, url: req.url, statusCode: res.statusCode, correlationId, }); } }); next(); }; ``` **Pattern Differences**: 1. **Correlation IDs**: IAM propagates correlation IDs 2. **Enhanced Context**: User ID, IP, user agent 3. **Audit Integration**: Sensitive endpoints get audit logs **Documentation Impact**: - Template docs: Basic request logging - IAM docs: Distributed tracing, audit integration, correlation IDs --- ## Configuration Pattern ### Template (Simple Zod Validation) ```typescript // src/config/app.config.ts import { z } from 'zod'; const envSchema = z.object({ PORT: z.coerce.number().default(5000), NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), DATABASE_URL: z.string().url(), REDIS_URL: z.string().url().optional(), }); const env = envSchema.parse(process.env); export const appConfig = { port: env.PORT, env: env.NODE_ENV, database: { url: env.DATABASE_URL }, redis: { url: env.REDIS_URL }, }; ``` ### IAM (with Nested Configs & Secrets) ```typescript // src/config/app.config.ts import { z } from 'zod'; const envSchema = z.object({ // Server PORT: z.coerce.number().default(3001), NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), // Database DATABASE_URL: z.string().url(), DATABASE_POOL_SIZE: z.coerce.number().default(10), // Redis REDIS_HOST: z.string().default('localhost'), REDIS_PORT: z.coerce.number().default(6379), REDIS_PASSWORD: z.string().optional(), REDIS_DB: z.coerce.number().default(0), // JWT JWT_SECRET: z.string().min(32), JWT_REFRESH_SECRET: z.string().min(32), JWT_ACCESS_EXPIRY: z.string().default('15m'), JWT_REFRESH_EXPIRY: z.string().default('7d'), // Encryption ENCRYPTION_KEY: z.string().min(32).optional(), // OAuth GOOGLE_CLIENT_ID: z.string().optional(), GOOGLE_CLIENT_SECRET: z.string().optional(), FACEBOOK_APP_ID: z.string().optional(), FACEBOOK_APP_SECRET: z.string().optional(), GITHUB_CLIENT_ID: z.string().optional(), GITHUB_CLIENT_SECRET: z.string().optional(), // Feature Flags MFA_ENABLED: z.coerce.boolean().default(true), SOCIAL_AUTH_ENABLED: z.coerce.boolean().default(true), TRACING_ENABLED: z.coerce.boolean().default(false), }); const env = envSchema.parse(process.env); export const appConfig = { server: { port: env.PORT, env: env.NODE_ENV, }, database: { url: env.DATABASE_URL, poolSize: env.DATABASE_POOL_SIZE, }, redis: { host: env.REDIS_HOST, port: env.REDIS_PORT, password: env.REDIS_PASSWORD, db: env.REDIS_DB, }, jwt: { secret: env.JWT_SECRET, refreshSecret: env.JWT_REFRESH_SECRET, accessExpiry: env.JWT_ACCESS_EXPIRY, refreshExpiry: env.JWT_REFRESH_EXPIRY, }, encryption: { key: env.ENCRYPTION_KEY, }, oauth: { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, }, facebook: { appId: env.FACEBOOK_APP_ID, appSecret: env.FACEBOOK_APP_SECRET, }, github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET, }, }, features: { mfa: env.MFA_ENABLED, socialAuth: env.SOCIAL_AUTH_ENABLED, tracing: env.TRACING_ENABLED, }, }; ``` **Pattern Differences**: 1. **Nested Configuration**: IAM groups related configs 2. **Secrets Management**: JWT, encryption, OAuth secrets 3. **Feature Flags**: Runtime feature toggles 4. **Complex Validation**: Min length requirements, optional fields **Documentation Impact**: -Template docs: Show basic Zod validation - IAM docs: Show nested configs, secrets management, feature flags --- ## Testing Pattern ### Template (Simple Unit Test) ```typescript // src/modules/feature/__tests__/feature.service.test.ts describe('FeatureService', () => { let service: FeatureService; let mockRepository: any; beforeEach(() => { mockRepository = { findByName: jest.fn(), create: jest.fn(), }; service = new FeatureService(mockRepository); }); it('should create feature', async () => { mockRepository.findByName.mockResolvedValue(null); mockRepository.create.mockResolvedValue({ id: '1', name: 'test' }); const result = await service.create({ name: 'test' }); expect(result).toEqual({ id: '1', name: 'test' }); expect(mockRepository.create).toHaveBeenCalledWith({ name: 'test' }); }); it('should throw conflict error if feature exists', async () => { mockRepository.findByName.mockResolvedValue({ id: '1', name: 'test' }); await expect(service.create({ name: 'test' })) .rejects .toThrow(ConflictError); }); }); ``` ### IAM (with Mocking Complete Dependencies) ```typescript // src/modules/rbac/__tests__/rbac.service.test.ts describe('RBACService', () => { let service: RBACService; let mockRepository: any; let mockCacheService: any; let mockAuditService: any; beforeEach(() => { mockRepository = { getUserRoles: jest.fn(), getRolePermissions: jest.fn(), getUserPermissions: jest.fn(), }; mockCacheService = { get: jest.fn(), set: jest.fn(), }; mockAuditService = { log: jest.fn(), }; service = new RBACService( mockRepository, mockCacheService, mockAuditService ); }); describe('checkPermission', () => { it('should return true if user has permission (cache hit)', async () => { mockCacheService.get.mockResolvedValue(['users:create:*', 'posts:read:*']); const result = await service.checkPermission('user1', 'users', 'create'); expect(result).toBe(true); expect(mockCacheService.get).toHaveBeenCalledWith('user:user1:permissions'); expect(mockRepository.getUserRoles).not.toHaveBeenCalled(); // Cache hit expect(mockAuditService.log).toHaveBeenCalledWith( 'ACCESS_GRANTED', 'user1', { resource: 'users', action: 'create' } ); }); it('should return false if user lacks permission (cache miss)', async () => { mockCacheService.get.mockResolvedValue(null); // Cache miss mockRepository.getUserRoles.mockResolvedValue(['user']); mockRepository.getRolePermissions.mockResolvedValue(['posts:read:*']); mockRepository.getUserPermissions.mockResolvedValue([]); const result = await service.checkPermission('user1', 'users', 'delete'); expect(result).toBe(false); expect(mockCacheService.set).toHaveBeenCalledWith( 'user:user1:permissions', ['posts:read:*'], 300 ); expect(mockAuditService.log).toHaveBeenCalledWith( 'ACCESS_DENIED', 'user1', { resource: 'users', action: 'delete' } ); }); it('should handle wildcard permissions', async () => { mockCacheService.get.mockResolvedValue(['users:*:*']); const result = await service.checkPermission('user1', 'users', 'create'); expect(result).toBe(true); }); }); }); ``` **Pattern Differences**: 1. **Multiple Dependencies**: IAM mocks repository, cache, audit 2. **Cache Behavior**: Tests cache hit and cache miss scenarios 3. **Audit Verification**: Ensures audit logs are created 4. **Complex Logic**: Tests wildcard permission matching **Documentation Impact**: - Template docs: Simple mocking, basic test cases - IAM docs: Complex mocking, cache behavior, audit verification --- ## Summary Table | Pattern | Template | IAM | Docs to Update | |---------|----------|-----|----------------| | **Controller** | Basic try-catch | + Auth decorators, audit | `api-design.md`, `middleware-patterns.md` | | **Service** | Simple logic | + Caching, complex logic | `service-layer-patterns.md`, `caching-patterns.md` | | **Repository** | Basic CRUD | + Complex joins, aggregation | `repository-pattern.md`, `database-prisma.md` | | **Middleware** | Basic logging | + Correlation IDs, audit | `middleware-patterns.md`, `observability-monitoring.md` | | **Configuration** | Simple Zod | + Nested configs, secrets | `configuration-management.md` | | **Testing** | Simple mocks | + Multi-dependency mocks | `testing-patterns.md` | | **Caching** | None | Multi-layer (L1/L2/L3) | `caching-patterns.md` (NEW examples) | | **Security** | Helmet only | + Zero-trust, encryption | `security.md` (NEW examples) | | **Authorization** | None | RBAC + ABAC | NEW skill docs needed | | **Audit Logging** | None | Event sourcing | `observability-monitoring.md` | --- ## Recommended Documentation Updates ### High Priority 1. **Update `caching-patterns.md`** with IAM multi-layer caching 2. **Update `security.md`** with zero-trust, encryption, JWT patterns 3. **Update `service-layer-patterns.md`** with caching integration 4. **Update `middleware-patterns.md`** with correlation IDs, audit 5. **Update `testing-patterns.md`** with complex mocking examples ### Medium Priority 6. **Update `repository-pattern.md`** with complex joins 7. **Update `api-design.md`** with auth middleware patterns 8. **Update `configuration-management.md`** with secrets management 9. **Update `observability-monitoring.md`** with audit logging ### New Content Needed 10. **RBAC patterns** (currently basic in user rules) 11. **Event sourcing** (audit logging implementation) 12. **Zero-trust architecture** (security validation) --- ## Code Example Sources All code examples in docs should reference: - **Template**: For foundational patterns - **IAM Service**: For production patterns **Example Mapping**: - `_template/src/modules/feature/feature.service.ts` → Basic service pattern - `iam-service/src/modules/rbac/rbac.service.ts` → Advanced service with caching - `iam-service/src/core/cache/multi-layer-cache.ts` → Caching implementation - `iam-service/src/core/security/zero-trust-validator.ts` → Security patterns