# ADR 0001: Implement `data-access` API Requests

| Field | Value |
|-------|-------|
| Status | Draft |
| Date | 29.09.2025 |
| Owners | Lorenz, Nino |
| Participants | N/A |
| Related ADRs | N/A |
| Tags | architecture, data-access, library, swagger |

---
## Summary (Decision in One Sentence)
Standardize data-access library implementation patterns for API requests using Zod schemas for validation, domain-specific models extending generated DTOs, and service layers that integrate with generated Swagger clients.

## Context & Problem Statement
The ISA Frontend application requires consistent and maintainable patterns for implementing API requests across multiple domain libraries. Current data-access libraries show varying implementation approaches that need standardization.

**Business drivers / user needs:**
- Consistent error handling across all API interactions
- Type-safe request/response handling to prevent runtime errors
- Maintainable code structure for easy onboarding and development
- Reliable validation of API inputs and outputs

**Technical constraints:**
- Must integrate with generated Swagger clients (`@generated/swagger/*`)
- Need to support abort signals for request cancellation
- Require caching and performance optimization capabilities
- Must align with existing logging infrastructure (`@isa/core/logging`)
- Support for domain-specific model extensions beyond generated DTOs

**Current pain points:**
- Inconsistent validation patterns across different data-access libraries
- Mixed approaches to error handling and response processing
- Duplication of common patterns (abort signal handling, response parsing)
- Lack of standardized model extension patterns

**Measurable goals:**
- Standardize API request patterns across all 4+ data-access libraries
- Reduce boilerplate code by 40% through shared utilities
- Improve type safety with comprehensive Zod schema coverage

### Scope
**In scope:**
- Schema validation patterns using Zod
- Model definition standards extending generated DTOs
- Service implementation patterns with generated Swagger clients
- Error handling and response processing standardization
- Integration with common utilities and logging

**Out of scope:**
- Modification of generated Swagger client code
- Changes to backend API contracts
- Authentication/authorization mechanisms
- Caching implementation details (handled by decorators)

## Decision
Implement a three-layer architecture pattern for data-access libraries:

1. **Schema Layer**: Use Zod schemas for input validation and type inference, following the naming convention `<Operation>Schema` with corresponding `<Operation>` and `<Operation>Input` types
2. **Model Layer**: Define domain-specific interfaces that extend generated DTOs, using `EntityContainer<T>` pattern for lazy-loaded relationships
3. **Service Layer**: Create injectable services that integrate generated Swagger clients, implement standardized error handling, and support request cancellation via AbortSignal

All data-access libraries will follow the standard export structure: `models`, `schemas`, `services`, and optionally `stores` and `helpers`.

## Rationale
**Alignment with strategic/technical direction:**
- Leverages existing Zod integration for consistent validation across the application
- Builds upon established generated Swagger client infrastructure
- Aligns with Angular dependency injection patterns and service architecture
- Supports the project's type-safety goals with TypeScript

**Trade-offs considered:**
- **Schema validation overhead**: Zod validation adds minimal runtime cost but provides significant development-time safety
- **Model extension complexity**: Interface extension pattern adds a layer but enables domain-specific enhancements
- **Service layer abstraction**: Additional abstraction over generated clients but enables consistent error handling and logging

**Evidence from current implementation:**
- Analysis of 4 data-access libraries shows successful patterns in `catalogue`, `remission`, `crm`, and `oms`
- `RemissionReturnReceiptService` demonstrates effective integration with logging and error handling
- `EntityContainer<T>` pattern proven effective for lazy-loaded relationships in remission domain

**Developer experience impact:**
- Consistent patterns reduce cognitive load when switching between domains
- Type inference from Zod schemas eliminates manual type definitions
- Standardized error handling reduces debugging time
- Auto-completion and type safety improve development velocity

**Long-term maintainability:**
- Clear separation of concerns between validation, models, and API integration
- Generated client changes don't break domain-specific model extensions
- Consistent logging and error handling simplifies troubleshooting

## Alternatives Considered
| Alternative | Summary | Pros | Cons | Reason Not Chosen |
|-------------|---------|------|------|-------------------|
| Option A | | | | |
| Option B | | | | |
| Option C | | | | |

Add deeper detail below if needed:
### Option A – <name>
### Option B – <name>
### Option C – <name>

## Consequences
### Positive
- …
### Negative / Risks / Debt Introduced
- …
### Neutral / Open Questions
- …

## Implementation Plan

### Phase 0 – Analysis & Standards (Completed)
- ✅ Analyzed existing data-access libraries (`catalogue`, `remission`, `crm`, `oms`)
- ✅ Identified common patterns and best practices
- ✅ Documented standard library structure

### Phase 1 – Common Utilities Enhancement
- Enhance `@isa/common/data-access` with additional utilities
- Add standardized error types and response handling
- Create reusable operators and decorators
- Add helper functions for common API patterns

### Phase 2 – Template & Generator Creation
- Create Nx generator for new data-access libraries
- Develop template files for schemas, models, and services
- Add code snippets and documentation templates
- Create migration guide for existing libraries

### Phase 3 – Existing Library Standardization
- Update `catalogue/data-access` to follow complete pattern
- Migrate `crm/data-access` to standard structure
- Ensure `remission/data-access` follows all conventions
- Standardize `oms/data-access` implementation

### Phase 4 – New Library Implementation
- Apply patterns to new domain libraries as they're created
- Use Nx generator for consistent setup
- Enforce patterns through code review and linting

### Tasks / Workstreams
**Infrastructure:**
- Update `@isa/common/data-access` with enhanced utilities
- Add ESLint rules for data-access pattern enforcement
- Update `tsconfig.base.json` path mappings as needed

**Library Enhancements:**
- Create Nx generator: `nx g @isa/generators:data-access-lib <domain>`
- Add utility functions to `@isa/common/data-access`
- Enhanced error handling and logging patterns

**Migration Tasks:**
- Standardize schema validation across all libraries
- Ensure consistent model extension patterns
- Align service implementations with logging standards
- Update tests to match new patterns

**Documentation:**
- Create data-access implementation guide
- Update onboarding materials with patterns
- Add code examples to development wiki
- Document generator usage and options

### Acceptance Criteria
- [ ] All data-access libraries follow standardized structure
- [ ] All API requests use Zod schema validation
- [ ] All services implement consistent error handling
- [ ] All services support AbortSignal for cancellation
- [ ] All models extend generated DTOs appropriately
- [ ] Nx generator produces compliant library structure
- [ ] Code review checklist includes data-access patterns
- [ ] Performance benchmarks show no degradation

### Rollback Plan
- Individual library changes can be reverted via Git
- Generated libraries can be recreated with previous patterns
- No breaking changes to existing public APIs
- Gradual migration allows for partial rollback by domain

## Architectural Impact
### Nx / Monorepo Layout
- Data-access libraries follow domain-based organization: `libs/<domain>/data-access/`
- Each library exports standard modules: `models`, `schemas`, `services`
- Dependencies on `@isa/common/data-access` for shared utilities
- Integration with generated Swagger clients via `@generated/swagger/<api-name>`

### Module / Library Design
**Standard public API structure (`src/index.ts`):**
```typescript
export * from './lib/models';
export * from './lib/schemas';
export * from './lib/services';
// Optional: stores, helpers
```

**Path aliases in `tsconfig.base.json`:**
- `@isa/<domain>/data-access` for each domain library
- `@generated/swagger/<api-name>` for generated clients
- `@isa/common/data-access` for shared utilities

### State Management
- Services integrate with NgRx signal stores in feature libraries
- `EntityContainer<T>` pattern supports lazy loading in state management
- Resource factory pattern used for async state management (see remission examples)
- Caching implemented via decorators (`@Cache`, `@InFlight`)

### Runtime & Performance
- Zod schema validation adds minimal runtime overhead
- Generated clients are tree-shakeable
- AbortSignal support enables request cancellation
- Caching decorators reduce redundant API calls
- `firstValueFrom` pattern avoids memory leaks from subscriptions

### Security & Compliance
- All API calls go through generated clients with consistent auth handling
- Input validation via Zod schemas prevents injection attacks
- AbortSignal support enables proper request cleanup
- Logging excludes sensitive data through structured context

### Observability & Logging
- Consistent logging via `@isa/core/logging` with service-level context
- Structured logging with operation context and request metadata
- Error logging includes request details without sensitive data
- Debug logging for development troubleshooting

### DX / Tooling
- Consistent patterns reduce learning curve across domains
- Type inference from Zod schemas eliminates manual type definitions
- Auto-completion from TypeScript interfaces
- Standard error handling patterns

## Detailed Design Elements

### Schema Validation Pattern
**Structure:**
```typescript
// Input validation schema
export const SearchByTermSchema = z.object({
  searchTerm: z.string().min(1, 'Search term must not be empty'),
  skip: z.number().int().min(0).default(0),
  take: z.number().int().min(1).max(100).default(20),
});

// Type inference
export type SearchByTerm = z.infer<typeof SearchByTermSchema>;
export type SearchByTermInput = z.input<typeof SearchByTermSchema>;
```

### Model Extension Pattern
**Generated DTO Extension:**
```typescript
import { ProductDTO } from '@generated/swagger/cat-search-api';

export interface Product extends ProductDTO {
  name: string;
  contributors: string;
  catalogProductNumber: string;
  // Domain-specific enhancements
}
```

**Entity Container Pattern:**
```typescript
export interface Return extends ReturnDTO {
  id: number;
  receipts: EntityContainer<Receipt>[]; // Lazy-loaded relationships
}
```

### Service Implementation Pattern
**Standard service structure:**
```typescript
@Injectable({ providedIn: 'root' })
export class DomainService {
  #apiService = inject(GeneratedApiService);
  #logger = logger(() => ({ service: 'DomainService' }));

  async fetchData(params: InputType, abortSignal?: AbortSignal): Promise<ResultType> {
    const validated = ValidationSchema.parse(params);
    
    let req$ = this.#apiService.operation(validated);
    if (abortSignal) {
      req$ = req$.pipe(takeUntilAborted(abortSignal));
    }
    
    const res = await firstValueFrom(req$);
    
    if (res.error) {
      this.#logger.error('Operation failed', new Error(res.message));
      throw new ResponseArgsError(res);
    }
    
    return res.result as ResultType;
  }
}
```

### Error Handling Strategy
1. **Input Validation**: Zod schemas validate and transform inputs
2. **API Error Handling**: Check `res.error` from generated clients
3. **Structured Logging**: Log errors with context via `@isa/core/logging`
4. **Error Propagation**: Throw `ResponseArgsError` for consistent handling

### Concurrency & Cancellation
- **AbortSignal Support**: All async operations accept optional AbortSignal
- **RxJS Integration**: Use `takeUntilAborted` operator for cancellation
- **Promise Pattern**: `firstValueFrom` prevents subscription memory leaks
- **Caching**: `@InFlight` decorator prevents duplicate concurrent requests

### Extension Points
- **Custom Decorators**: `@Cache`, `@InFlight`, `@CacheTimeToLive`
- **Schema Transformations**: Zod `.transform()` for data normalization
- **Model Inheritance**: Interface extension for domain-specific properties
- **Service Composition**: Services can depend on other domain services

## Code Examples

### Complete Data-Access Library Structure
```typescript
// libs/domain/data-access/src/lib/schemas/fetch-items.schema.ts
import { z } from 'zod';

export const FetchItemsSchema = z.object({
  categoryId: z.string().min(1),
  skip: z.number().int().min(0).default(0),
  take: z.number().int().min(1).max(100).default(20),
  filters: z.record(z.any()).default({}),
});

export type FetchItems = z.infer<typeof FetchItemsSchema>;
export type FetchItemsInput = z.input<typeof FetchItemsSchema>;

// libs/domain/data-access/src/lib/models/item.ts
import { ItemDTO } from '@generated/swagger/domain-api';
import { EntityContainer } from '@isa/common/data-access';
import { Category } from './category';

export interface Item extends ItemDTO {
  id: number;
  displayName: string;
  category: EntityContainer<Category>;
  // Domain-specific enhancements
  isAvailable: boolean;
  formattedPrice: string;
}

// libs/domain/data-access/src/lib/services/item.service.ts
import { inject, Injectable } from '@angular/core';
import { ItemService as GeneratedItemService } from '@generated/swagger/domain-api';
import { firstValueFrom } from 'rxjs';
import { takeUntilAborted, ResponseArgsError } from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { FetchItemsInput, FetchItemsSchema } from '../schemas';
import { Item } from '../models';

@Injectable({ providedIn: 'root' })
export class ItemService {
  #itemService = inject(GeneratedItemService);
  #logger = logger(() => ({ service: 'ItemService' }));

  async fetchItems(
    params: FetchItemsInput,
    abortSignal?: AbortSignal
  ): Promise<Item[]> {
    this.#logger.debug('Fetching items', () => ({ params }));
    
    const { categoryId, skip, take, filters } = FetchItemsSchema.parse(params);
    
    let req$ = this.#itemService.getItems({
      categoryId,
      queryToken: { skip, take, filter: filters }
    });
    
    if (abortSignal) {
      req$ = req$.pipe(takeUntilAborted(abortSignal));
    }
    
    const res = await firstValueFrom(req$);
    
    if (res.error) {
      this.#logger.error('Failed to fetch items', new Error(res.message));
      throw new ResponseArgsError(res);
    }
    
    this.#logger.info('Successfully fetched items', () => ({ 
      count: res.result?.length || 0 
    }));
    
    return res.result as Item[];
  }
}

// libs/domain/data-access/src/index.ts
export * from './lib/models';
export * from './lib/schemas';
export * from './lib/services';
```

### Usage in Feature Components
```typescript
// feature component using the data-access library
import { Component, inject, signal } from '@angular/core';
import { ItemService, Item, FetchItemsInput } from '@isa/domain/data-access';

@Component({
  selector: 'app-item-list',
  template: `
    @if (loading()) {
      <div>Loading...</div>
    } @else {
      @for (item of items(); track item.id) {
        <div>{{ item.displayName }}</div>
      }
    }
  `
})
export class ItemListComponent {
  #itemService = inject(ItemService);
  
  items = signal<Item[]>([]);
  loading = signal(false);
  
  async loadItems(categoryId: string) {
    this.loading.set(true);
    
    try {
      const params: FetchItemsInput = {
        categoryId,
        take: 50,
        filters: { active: true }
      };
      
      const items = await this.#itemService.fetchItems(params);
      this.items.set(items);
    } catch (error) {
      console.error('Failed to load items', error);
    } finally {
      this.loading.set(false);
    }
  }
}
```

### Migration Pattern for Existing Services
```typescript
// Before: Direct HTTP client usage
@Injectable()
export class LegacyItemService {
  constructor(private http: HttpClient) {}
  
  getItems(categoryId: string): Observable<any> {
    return this.http.get(`/api/items?category=${categoryId}`);
  }
}

// After: Standardized data-access pattern
@Injectable({ providedIn: 'root' })
export class ItemService {
  #itemService = inject(GeneratedItemService);
  #logger = logger(() => ({ service: 'ItemService' }));
  
  async fetchItems(params: FetchItemsInput, abortSignal?: AbortSignal): Promise<Item[]> {
    const validated = FetchItemsSchema.parse(params);
    // ... standard implementation pattern
  }
}
```

## Open Questions / Follow-Ups
- Unresolved design clarifications
- Dependent ADRs required
- External approvals needed

## Decision Review & Revalidation
When and how this ADR will be re-evaluated (date, trigger conditions, metrics thresholds).

## Status Log
| Date | Change | Author |
|------|--------|--------|
| 2025-09-29 | Created (Draft) | Lorenz, Nino |
| 2025-09-25 | Analysis completed, comprehensive patterns documented | AI Assistant |

## References
**Existing Implementation Examples:**
- `libs/catalogue/data-access` - Basic schema and service patterns
- `libs/remission/data-access` - Advanced patterns with EntityContainer and stores
- `libs/common/data-access` - Shared utilities and response types
- `generated/swagger/` - Generated API clients integration

**Key Dependencies:**
- [Zod](https://github.com/colinhacks/zod) - Schema validation library
- [ng-swagger-gen](https://github.com/cyclosproject/ng-swagger-gen) - OpenAPI client generation
- `@isa/core/logging` - Structured logging infrastructure
- `@isa/common/data-access` - Shared utilities and types

**Related Documentation:**
- ISA Frontend Copilot Instructions - Data-access patterns
- Tech Stack Documentation - Architecture overview
- Code Style Guidelines - TypeScript and Angular patterns

---
> Document updates MUST reference this ADR number in commit messages: `ADR-NNNN:` prefix.
> Keep this document updated through all lifecycle stages.
