Moeen Chat Service - v1.0.0
    Preparing search index...

    Implements

    • OnApplicationShutdown
    Index

    Constructors

    Properties

    CACHE_TTL_MS: 30000
    CB_RESET_TIMEOUT: 10000

    Try again after 10 seconds

    CB_THRESHOLD: 5

    Open circuit after 5 failures

    consulService: ConsulService
    failureCount: number = 0

    Failure count

    lastFailureTime: number = 0

    Last failure time

    logger: Logger = ...
    roundRobinCounters: Map<string, number> = ...
    serviceCache: Map<ClientsType, ServiceCacheEntry> = ...
    shutdownController: AbortController = ...

    Accessors

    • get consul(): Consul

      Returns Consul

    Methods

    • Discovers a healthy service instance from Consul's service registry.

      Implements a resilient discovery flow with multiple layers of fault tolerance:

      1. Circuit Breaker — short-circuits to cache when Consul is repeatedly unreachable.
      2. Retry with Exponential Backoff — retries transient network failures up to 3 times.
      3. Cache-Aside — returns stale cached nodes as a last-resort fallback.

      Parameters

      • targetServiceName: ClientsType

        The registered Consul service name (e.g. 'file-manager' or 'auth-service').

      • OptionalfetchType: FetchType = FetchType.FIRST

        The node selection strategy:

        • FetchType.FIRST — always returns the first healthy node.
        • FetchType.ROUND_ROBIN — cycles through nodes sequentially per service.
        • FetchType.RANDOM — picks a random healthy node.

      Returns Promise<ServiceNode>

      The selected service node containing address, port, and url.

      If Consul is unreachable and no cached nodes exist for the requested service.

      // Basic usage with default strategy (FIRST)
      const node = await this.serviceDiscoveryService.discoverService('file-manager');
      // => { address: '10.0.1.5', port: 3001, url: 'http://10.0.1.5:3001' }
      // Round-robin load balancing across instances
      const node = await this.serviceDiscoveryService.discoverService(
      'auth-service',
      FetchType.ROUND_ROBIN,
      );
      // Handling discovery failure
      try {
      const node = await this.serviceDiscoveryService.discoverService('auth-service');
      } catch (error) {
      // Consul is down and no cache is available
      console.error(error.message);
      }
    • Attempts to return a node from the local cache (even if stale) as a resilience fallback when Consul is unreachable.

      This is the last line of defence — if the cache is empty or has never been populated for the requested service, the method throws, causing the caller to surface a hard failure.

      Parameters

      Returns ServiceNode

      A node selected from the (potentially stale) cache.

      "Service Discovery Failed: Consul is down and no local cache available for <serviceName>" when no cached nodes exist at all.

      try {
      const node = this.getFromCacheOrThrow('auth-service', FetchType.FIRST);
      // node served from stale cache
      } catch {
      // no cache at all — total failure
      }
    • Fetches healthy service nodes from Consul with automatic retry and exponential backoff.

      Retry schedule (default 8 attempts):

      Attempt Backoff delay
      1 400 ms
      2 800 ms
      3 1600 ms
      4 3200 ms
      5 6400 ms
      6 12800 ms
      7 25600 ms
      8 throws

      Before hitting the network, checks the local cache. If the cache entry is still within its TTL (CACHE_TTL_MS), the cached nodes are returned immediately — avoiding unnecessary Consul round-trips.

      The raw Consul response is mapped into ServiceNode objects with a pre-built url field for convenience.

      Parameters

      • serviceName: ClientsType

        The registered Consul service name to query.

      • Optionalretries: number = 8

        Maximum number of fetch attempts before giving up.

      Returns Promise<ServiceNode[]>

      An array of healthy nodes for the service.

      If all retry attempts are exhausted. The thrown error is the last error encountered (network failure or zero-instance response).

      const nodes = await this.getHealthyNodesWithRetry('file-manager');
      // => [
      // { address: '10.0.1.5', port: 3001, url: 'http://10.0.1.5:3001' },
      // { address: '10.0.1.6', port: 3001, url: 'http://10.0.1.6:3001' },
      // ]
      // With a custom retry count
      const nodes = await this.getHealthyNodesWithRetry('auth-service', 5);
    • Checks whether the cached entry for a given service is still within its time-to-live window (CACHE_TTL_MS, default 30 000 ms).

      Parameters

      • serviceName: ClientsType

        The service name to look up in the cache.

      Returns boolean

      true if a cache entry exists and its age is less than CACHE_TTL_MS; false otherwise.

      if (this.isCacheValid('auth-service')) {
      // safe to use cached nodes
      }
    • Evaluates whether the circuit breaker is currently in the Open state.

      The circuit transitions through three states:

      • Closed — fewer than CB_THRESHOLD (5) consecutive failures; requests pass through normally.
      • Open — threshold reached and less than CB_RESET_TIMEOUT (10 s) since the last failure; all requests are short-circuited.
      • Half-Open — threshold reached but the reset timeout has elapsed; one request is allowed through to probe Consul's health (returns false).

      Returns boolean

      true if the circuit is Open (callers should skip Consul and fall back to cache); false otherwise.

      if (this.isCircuitOpen()) {
      return this.getFromCacheOrThrow(serviceName, strategy);
      }
    • Returns void

    • Records a Consul interaction failure by incrementing the failure counter and updating the last-failure timestamp.

      Once failureCount reaches CB_THRESHOLD, the circuit breaker trips to the Open state (see isCircuitOpen).

      Returns void

      try {
      await this.getHealthyNodesWithRetry(serviceName);
      } catch {
      this.recordFailure();
      }
    • Resets the circuit breaker back to the Closed state by zeroing the failure counter.

      Called after a successful Consul interaction to indicate the service registry is healthy again.

      Returns void

      const nodes = await this.getHealthyNodesWithRetry(serviceName);
      this.resetCircuitBreaker(); // Consul responded — reset failures
    • Selects a single ServiceNode from a list of healthy nodes using the specified load-balancing strategy.

      Strategy Behaviour
      FetchType.FIRST Always returns nodes[0] (deterministic, no state).
      FetchType.ROUND_ROBIN Cycles through nodes via a per-service counter.
      FetchType.RANDOM Uniform random selection across all available nodes.

      Parameters

      • nodes: ServiceNode[]

        The list of healthy service nodes to choose from. Must contain at least one element.

      • serviceName: string

        The service name, used as the key for the round-robin counter map.

      • strategy: FetchType

        The selection strategy to apply.

      Returns ServiceNode

      A single node chosen according to the strategy.

      If nodes is empty ("No nodes available for <serviceName>").

      const nodes: ServiceNode[] = [
      { address: '10.0.0.1', port: 3000, url: 'http://10.0.0.1:3000' },
      { address: '10.0.0.2', port: 3000, url: 'http://10.0.0.2:3000' },
      ];
      const selected = this.selectNode(nodes, 'auth-service', FetchType.ROUND_ROBIN);
      // First call => nodes[0]
      // Second call => nodes[1]
      // Third call => nodes[0] (wraps around)
    • Stores (or overwrites) a service's healthy nodes in the in-memory cache, stamping the entry with the current time for TTL validation.

      Parameters

      • serviceName: ClientsType

        The service name used as the cache key.

      • nodes: ServiceNode[]

        The list of healthy nodes to cache.

      Returns void

      this.updateCache('file-manager', [
      { address: '10.0.1.5', port: 3001, url: 'http://10.0.1.5:3001' },
      ]);
      // serviceCache now holds: { nodes: [...], timestamp: <now> }