API Proxy Design

1. Overview

The Registration Portal operates as a JHipster gateway application, proxying most API requests to the admin-service backend. This design provides a clean separation between the presentation layer (Angular) and the business logic layer (Spring Boot services).

Key Benefits:

  • Single Entry Point - All API calls go through gateway

  • Security Layer - OAuth2 token validation at gateway

  • Service Discovery - Gateway routes requests to correct service

  • Load Balancing - Can distribute requests across service instances

  • Circuit Breaking - Fault tolerance for backend failures

2. Proxy Architecture

proxy-architecture

3. Gateway Routes Configuration

3.1. Route Definitions

spring:
  cloud:
    gateway:
      routes:
        - id: admin-service
          uri: http://localhost:8080
          predicates:
            - Path=/api/**,/services/**
          filters:
            - RewritePath=/(?<segment>.*), /$\{segment}
            - name: Retry
              args:
                retries: 3
                statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
                methods: GET,POST,PUT,DELETE
                backoff:
                  firstBackoff: 10ms
                  maxBackoff: 50ms
                  factor: 2
                  basedOnPreviousValue: false
        - id: admin-service-websocket
          uri: ws://localhost:8080
          predicates:
            - Path=/websocket/**

3.2. Route Patterns

Pattern Destination Purpose

/api/**

admin-service

REST API endpoints

/services/**

admin-service

Microservice endpoints

/websocket/**

admin-service (WebSocket)

Real-time communication

/oauth/**

Local gateway

OAuth2 authentication

/management/**

Local gateway

Actuator endpoints

4. Proxied Endpoints

4.1. Person API

Method Frontend Path Proxied To Service

GET

/api/people/{id}

admin-service

PersonService

POST

/api/people

admin-service

PersonService

PUT

/api/people/{id}

admin-service

PersonService

DELETE

/api/people/{id}

admin-service

PersonService

GET

/api/people/linked

admin-service

PersonService

POST

/api/people/match

admin-service

PersonService

4.2. LinkedPerson API

Method Frontend Path Proxied To Service

POST

/api/linked-people/link/{id}/{type}

admin-service

LinkedPersonService

GET

/api/linked-people

admin-service

LinkedPersonService

DELETE

/api/linked-people/{id}

admin-service

LinkedPersonService

4.3. Form/Process API

Method Frontend Path Proxied To Service

GET

/api/forms/membership/{periodId}

admin-service

FormService

POST

/api/forms/next

admin-service

FormService

POST

/api/forms/processes/{id}/reset

admin-service

ProcessService

POST

/api/forms/processes/{id}/resume

admin-service

ProcessService

4.4. Event Registration API

Method Frontend Path Proxied To Service

GET

/api/events/{id}

admin-service

EventService

POST

/api/event-participants/register

admin-service

EventParticipantService

4.5. Membership API

Method Frontend Path Proxied To Service

GET

/api/membership-periods/{id}

admin-service

MembershipPeriodService

GET

/api/membership-types/{typeId}/persons/obfuscate

admin-service

MembershipService

5. Local Gateway Endpoints

5.1. OAuth2 Endpoints

Method Path Purpose

POST

/oauth/token

Obtain access token

GET

/oauth/authorize

Authorization code flow

POST

/oauth/logout

Logout and invalidate token

5.2. Management Endpoints

Method Path Purpose

GET

/management/health

Health check

GET

/management/info

Application info

GET

/management/metrics

Prometheus metrics

GET

/management/env

Environment properties

6. Service Configuration

6.1. ApplicationConfigService

The Angular application uses ApplicationConfigService to construct proxied endpoint URLs:

@Injectable({ providedIn: 'root' })
export class ApplicationConfigService {
  private endpointPrefix = '';

  setEndpointPrefix(endpointPrefix: string): void {
    this.endpointPrefix = endpointPrefix;
  }

  getEndpointFor(api: string, microservice?: string): string {
    if (microservice) {
      return `${this.endpointPrefix}services/${microservice}/${api}`;
    }
    return `${this.endpointPrefix}${api}`;
  }
}

Usage Examples:

// Proxied to admin-service
this.applicationConfigService.getEndpointFor('api/people', 'admin-service')
// Returns: "services/admin-service/api/people"

// Direct gateway endpoint
this.applicationConfigService.getEndpointFor('api/account')
// Returns: "api/account"

6.2. Service URL Construction

PeopleService Example:

@Injectable({ providedIn: 'root' })
export class PeopleService {
  readonly peopleUrl = 'services/admin-service/api/people';

  getPerson(id: number): Observable<IPerson> {
    return this.personResourceService.getPerson(id)
      .pipe(mergeMap(response => this.blobUtils.parseBlob<IPerson>(response)));
  }
}

LinkedPersonStateService Example:

@Injectable({ providedIn: 'root' })
export class LinkedPersonStateService {
  linkEdPersonUrl = this.applicationConfigService
    .getEndpointFor('api/linked-people/link', 'admin-service');

  linkPerson(id: number, type: string, userKey: string): Observable<ILinkedPerson> {
    return this.http.post<ILinkedPerson>(
      `${this.linkEdPersonUrl}/${id}/${type}?userKey=${userKey}&organisationId=8`,
      { observe: 'response' }
    );
  }
}

7. Request Flow

7.1. Standard API Request

api-request-flow

7.2. Request with Error

error-flow

8. Generated API Clients

The portal uses generated TypeScript API clients from OpenAPI specifications:

8.1. Registration API TypeScript Client

import {
  FormResourceService,
  PersonResourceService,
  MembershipPeriodResourceService
} from '@christhonie/registration-api-ts';

@Injectable({ providedIn: 'root' })
export class FormService {
  private formResourceService = inject(FormResourceService);

  getFormMembership(membershipPeriodId: number, userKey: string):
    Observable<FormDTO> {
    return this.formResourceService
      .getFormMembership(membershipPeriodId, userKey)
      .pipe(mergeMap(response => this.blobUtils.parseBlob<FormDTO>(response)));
  }
}

Package: @christhonie/registration-api-ts

Key Classes:

  • FormResourceService

  • PersonResourceService

  • MembershipPeriodResourceService

  • EventResourceService

9. Error Handling

9.1. Circuit Breaker

The gateway includes circuit breaker patterns for fault tolerance:

Configuration:

resilience4j:
  circuitbreaker:
    instances:
      admin-service:
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
        slidingWindowSize: 10
        minimumNumberOfCalls: 5

Behavior:

  • Closed - Normal operation, requests flow through

  • Open - Too many failures, requests immediately fail with 503

  • Half-Open - Testing if service recovered, limited requests allowed

9.2. Retry Logic

filters:
  - name: Retry
    args:
      retries: 3
      statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
      methods: GET,POST,PUT,DELETE
      backoff:
        firstBackoff: 10ms
        maxBackoff: 50ms
        factor: 2

Retry Scenarios:

  • 502 Bad Gateway - Backend unreachable

  • 504 Gateway Timeout - Backend slow to respond

  • Exponential backoff between retries

9.3. Timeout Configuration

HTTP Interceptor:

export class TimeoutInterceptor {
  static getHeadersWithTimeout(timeout: number): HttpHeaders {
    return new HttpHeaders({
      'X-Timeout': timeout.toString()
    });
  }
}

// Usage in FormService
const headers = TimeoutInterceptor.getHeadersWithTimeout(15000);
return this.formResourceService.next(data, 'body', false, { headers });

Default Timeout: 2 minutes Form Submission Timeout: 15 seconds (extended for processing)

10. Security Integration

10.1. OAuth2 Token Flow

oauth-flow

10.2. Request Headers

Outbound Headers (Angular → Gateway):

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Accept-Language: en
Content-Type: application/json

Gateway → Admin Service:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
X-Forwarded-For: 192.168.1.100
X-Forwarded-Proto: https
X-Forwarded-Host: app.example.com

11. Performance Considerations

11.1. Connection Pooling

spring:
  cloud:
    gateway:
      httpclient:
        pool:
          max-connections: 100
          max-idle-time: 30s
          acquire-timeout: 45s

11.2. Response Caching

Cache-Control Headers:

  • GET requests for static data (membership periods, events)

  • Short TTL (5 minutes) for frequently accessed data

  • No caching for user-specific data

11.3. Load Balancing

Future Configuration:

spring:
  cloud:
    gateway:
      routes:
        - id: admin-service
          uri: lb://admin-service  # Load balanced
          predicates:
            - Path=/api/**

Load Balancing Strategies:

  • Round-robin across service instances

  • Weighted distribution based on capacity

  • Health check integration

12. Monitoring and Observability

12.1. Metrics

Gateway Metrics:

  • Request rate per route

  • Response time per route

  • Error rate per route

  • Circuit breaker status

  • Connection pool utilization

Endpoint: /management/metrics

Example Metrics:

gateway.requests{route=admin-service,status=200} 1523
gateway.requests{route=admin-service,status=500} 12
gateway.requests.duration{route=admin-service} 145ms

12.2. Health Checks

Endpoint: /management/health

Response:

{
  "status": "UP",
  "components": {
    "gateway": {
      "status": "UP"
    },
    "admin-service": {
      "status": "UP",
      "details": {
        "responseTime": "45ms"
      }
    },
    "diskSpace": {
      "status": "UP"
    }
  }
}