Skip to content

Health Aggregation — Complete Implementation Guide

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Health Aggregation. We cover key concepts, practical examples, and best practices to help you master this topic.

Health aggregation combines health status from multiple services, dependencies, and components into a single coherent view, enabling operators to understand system health at a glance.

What You'll Learn

By the end of this tutorial, you will know how to aggregate health checks across multiple services, build hierarchical health reports, and integrate with API gateways and dashboards.

Why It Matters

In a microservice architecture, individual service health is not enough. You need to understand how service health relates to system health, and which failures affect end users.

Real-World Use

DodaTech's API Gateway has a /health endpoint that aggregates health from 20 Microservices, 5 databases, 3 caches, and 2 Message Queues, returning a single summary status.

Health Aggregation Learning Path

flowchart LR
  A[Custom Health Indicators] --> B[Health Aggregation]
  B --> C[Service Level]
  B --> D[System Level]
  B --> E[Gateway Level]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Simple Health Aggregation

Combine multiple health results with status precedence logic.

class HealthAggregator {
  static aggregateStatus(statuses) {
    if (statuses.includes("DOWN")) return "DOWN";
    if (statuses.includes("DEGRADED")) return "DEGRADED";
    if (statuses.includes("OUT_OF_SERVICE")) return "OUT_OF_SERVICE";
    if (statuses.includes("UNKNOWN")) return "UNKNOWN";
    return "UP";
  }

  static aggregate(results) {
    const statuses = results.map(r => r.status || "UNKNOWN");
    return {
      status: this.aggregateStatus(statuses),
      components: results,
      timestamp: new Date().toISOString()
    };
  }
}

const results = [
  { name: "database", status: "UP", latencyMs: 10 },
  { name: "cache", status: "UP", latencyMs: 2 },
  { name: "api", status: "DOWN", error: "Connection refused" },
  { name: "queue", status: "UP", latencyMs: 5 }
];

const aggregated = HealthAggregator.aggregate(results);
console.log("Aggregated status:", aggregated.status);
// Aggregated status: DOWN

Hierarchical Health Aggregation

Group components by layer for organized health reporting.

class HierarchicalHealthAggregator {
  constructor() {
    this.layers = {
      infrastructure: [],
      data: [],
      services: [],
      external: []
    };
  }

  addToLayer(layer, name, checkFn) {
    if (this.layers[layer]) {
      this.layers[layer].push({ name, check: checkFn });
    }
  }

  async checkLayer(layer) {
    const results = await Promise.allSettled(
      this.layers[layer].map(item => item.check())
    );

    const checks = this.layers[layer].map((item, i) => {
      const r = results[i];
      return {
        name: item.name,
        status: r.status === "fulfilled" ? "UP" : "DOWN",
        ...(r.status === "fulfilled" ? r.value : { error: r.reason.message })
      };
    });

    const allUp = checks.every(c => c.status === "UP");
    return { layer, status: allUp ? "UP" : "DOWN", checks };
  }

  async aggregate() {
    const layerResults = [];
    let overall = "UP";

    for (const layer of Object.keys(this.layers)) {
      if (this.layers[layer].length > 0) {
        const result = await this.checkLayer(layer);
        layerResults.push(result);
        if (result.status === "DOWN") overall = "DOWN";
      }
    }

    return {
      status: overall,
      layers: layerResults,
      timestamp: new Date().toISOString()
    };
  }
}

const hierarchy = new HierarchicalHealthAggregator();
hierarchy.addToLayer("infrastructure", "disk", async () => ({ free: "50%" }));
hierarchy.addToLayer("data", "postgres", async () => ({ connected: true }));
hierarchy.addToLayer("data", "redis", async () => ({ connected: true }));
hierarchy.addToLayer("services", "user-service", async () => ({ reachable: true }));

hierarchy.aggregate().then(r => {
  console.log("Hierarchical status:", r.status);
  r.layers.forEach(l => console.log(`  ${l.layer}: ${l.status}`));
});
// Hierarchical status: UP
//   infrastructure: UP
//   data: UP
//   services: UP

Circuit Breaker Aware Aggregation

Respect circuit breaker state when aggregating health.

class CircuitBreakerAwareAggregator {
  constructor() {
    this.circuits = new Map();
  }

  register(serviceName, circuitBreaker) {
    this.circuits.set(serviceName, circuitBreaker);
  }

  async aggregate() {
    const results = [];

    for (const [name, circuit] of this.circuits) {
      const state = circuit.getState();
      let status;

      switch (state) {
        case "closed":
          status = "UP";
          break;
        case "half-open":
          status = "DEGRADED";
          break;
        case "open":
          status = "DOWN";
          break;
        default:
          status = "UNKNOWN";
      }

      results.push({
        name,
        status,
        circuitState: state,
        failureCount: circuit.failureCount
      });
    }

    const statuses = results.map(r => r.status);
    return {
      status: statuses.includes("DOWN") ? "DOWN"
        : statuses.includes("DEGRADED") ? "DEGRADED"
        : "UP",
      services: results,
      timestamp: new Date().toISOString()
    };
  }
}

const aggregator = new CircuitBreakerAwareAggregator();
aggregator.register("payment-service", {
  getState: () => "closed",
  failureCount: 0
});
aggregator.register("notification-service", {
  getState: () => "open",
  failureCount: 5
});

aggregator.aggregate().then(r => {
  console.log("Circuit-aware status:", r.status);
  r.services.forEach(s => console.log(`  ${s.name}: ${s.status} (${s.circuitState})`));
});
// Circuit-aware status: DOWN
//   payment-service: UP (closed)
//   notification-service: DOWN (open)

API Gateway Health Aggregation

API gateways often aggregate downstream service health.

class GatewayHealthAggregator {
  constructor() {
    this.upstreamServices = new Map();
    this.httpClient = null;
  }

  registerService(name, healthUrl, timeout = 3000) {
    this.upstreamServices.set(name, { url: healthUrl, timeout });
  }

  async checkService(name, config) {
    const start = Date.now();
    try {
      const response = await fetch(config.url, {
        signal: AbortSignal.timeout(config.timeout)
      });

      const data = await response.json();
      return {
        name,
        status: response.ok ? (data.status || "UP") : "DOWN",
        latencyMs: Date.now() - start,
        details: data
      };
    } catch (err) {
      return {
        name,
        status: "DOWN",
        error: err.message,
        latencyMs: Date.now() - start
      };
    }
  }

  async aggregate() {
    const checks = Array.from(this.upstreamServices.entries())
      .map(([name, config]) => this.checkService(name, config));

    const results = await Promise.allSettled(checks);
    const services = results.map(r => r.status === "fulfilled" ? r.value : {
      name: "unknown", status: "DOWN", error: "check failed"
    });

    const allUp = services.every(s => s.status === "UP");
    return {
      gateway: "api-gateway",
      status: allUp ? "UP" : "DOWN",
      services,
      summary: {
        total: services.length,
        healthy: services.filter(s => s.status === "UP").length,
        unhealthy: services.filter(s => s.status !== "UP").length
      }
    };
  }
}

const gateway = new GatewayHealthAggregator();
gateway.registerService("users", "http://users:8080/healthz");
gateway.registerService("payments", "http://payments:8080/healthz");
gateway.registerService("notifications", "http://notifications:8080/healthz");

gateway.aggregate().then(r => {
  console.log("Gateway status:", r.status);
  console.log("Summary:", r.summary);
});

Health Aggregation with Weighting

Not all services are equally important. Weight health to reflect criticality.

class WeightedHealthAggregator {
  constructor() {
    this.services = [];
  }

  addService(name, weight, checkFn) {
    this.services.push({ name, weight, check: checkFn });
  }

  async aggregate() {
    const results = await Promise.allSettled(
      this.services.map(s => s.check())
    );

    let weightedScore = 0;
    let totalWeight = 0;
    const details = [];

    this.services.forEach((s, i) => {
      const r = results[i];
      const up = r.status === "fulfilled";
      weightedScore += up ? s.weight : 0;
      totalWeight += s.weight;

      details.push({
        name: s.name,
        status: up ? "UP" : "DOWN",
        weight: s.weight
      });
    });

    const healthPercent = (weightedScore / totalWeight) * 100;
    const status = healthPercent >= 90 ? "UP"
      : healthPercent >= 50 ? "DEGRADED"
      : "DOWN";

    return {
      status,
      healthPercent: Math.round(healthPercent),
      weightedScore,
      totalWeight,
      services: details
    };
  }
}

const weighted = new WeightedHealthAggregator();
weighted.addService("database", 40, async () => true);
weighted.addService("payment-api", 30, async () => { throw new Error("down"); });
weighted.addService("notification-api", 20, async () => true);
weighted.addService("analytics", 10, async () => true);

weighted.aggregate().then(r => {
  console.log("Weighted health:", r.status, `(${r.healthPercent}%)`);
});
// Weighted health: DEGRADED (70%)

Common Mistakes

  1. Aggregating without timeout -- If one service's health endpoint hangs, the entire aggregate hangs. Set per-service timeouts.

  2. Treating all services as equally critical -- A cache failure is less critical than a database failure. Use weighting or tiered aggregation.

  3. Not handling partial results -- If one service is unreachable, the aggregate should still include data from reachable services.

  4. Aggregating too frequently -- Aggregating 50 service health endpoints every 5 seconds creates 600 requests per minute. Cache aggregate results.

  5. Not including gateway's own health -- The gateway's aggregate endpoint should include its own health plus the health of downstream services.

Practice Questions

  1. What status code should an aggregate endpoint return when some services are down? 503 Service Unavailable if critical services are down. 200 with DEGRADED status if only non-critical services are down.

  2. How does circuit breaker state affect health aggregation? An open circuit breaker indicates the service is DOWN. A half-open circuit indicates DEGRADED.

  3. What is weighted health aggregation? Each service gets a weight based on its criticality. The aggregate health is calculated from the percentage of healthy weight.

  4. Challenge: Implement a health aggregator that supports both polling and push-based health reporting.

class HybridHealthAggregator {
  constructor() {
    this.pushClients = new Map();
    this.pollClients = new Map();
  }

  pushRegister(name, sendHealth) {
    this.pushClients.set(name, { lastSeen: Date.now(), health: "UNKNOWN" });
    setInterval(async () => {
      const health = await sendHealth();
      this.pushClients.set(name, { ...health, lastSeen: Date.now() });
    }, 10000);
  }

  pollRegister(name, url) {
    this.pollClients.set(name, { url, lastHealth: "UNKNOWN" });
  }

  async aggregate() {
    const results = [];

    for (const [name, data] of this.pushClients) {
      const stale = Date.now() - data.lastSeen > 30000;
      results.push({ name, status: stale ? "STALE" : data.health });
    }

    for (const [name, config] of this.pollClients) {
      try {
        const resp = await fetch(config.url, { signal: AbortSignal.timeout(2000) });
        results.push({ name, status: resp.ok ? "UP" : "DOWN" });
      } catch {
        results.push({ name, status: "DOWN" });
      }
    }

    return HealthAggregator.aggregate(results);
  }
}

FAQ

Should the API gateway aggregate health from all downstream services?

Yes, but with timeouts and caching. The gateway's health endpoint should return quickly even if some services are slow.

How do I handle circular health check dependencies?

Service A checks service B, which checks service A. This creates deadlock. Use a service mesh or health registry to avoid circular checks.

What is the best format for aggregated health responses?

JSON with a top-level status, a map of components, and a timestamp. Include summary counts for quick scanning.

How often should I re-aggregate health?

Every 10-15 seconds for real-time monitoring. Cache the aggregate result for the duration between checks.

Can I use service mesh for health aggregation?

Yes. Service mesh solutions like Istio provide built-in health aggregation and traffic routing based on service health.

Mini Project

Build a health aggregation service that polls multiple microservices, aggregates their health with weighting, respects circuit breaker states, and exposes both a detailed and summary endpoint.

class HealthAggregationService {
  constructor() {
    this.services = [];
    this.cache = null;
    this.cacheTTL = 10000;
    this.lastFetch = 0;
  }

  addService(name, url, weight = 10) {
    this.services.push({ name, url, weight });
  }

  async getAggregate() {
    if (this.cache && Date.now() - this.lastFetch < this.cacheTTL) {
      return this.cache;
    }

    const results = await Promise.allSettled(
      this.services.map(s =>
        fetch(s.url, { signal: AbortSignal.timeout(2000) })
          .then(r => ({ name: s.name, weight: s.weight, status: r.ok ? "UP" : "DOWN" }))
          .catch(() => ({ name: s.name, weight: s.weight, status: "DOWN" }))
      )
    );

    const healthy = results.filter(r => r.value?.status === "UP").length;
    this.cache = { total: this.services.length, healthy, unhealthy: this.services.length - healthy };
    this.lastFetch = Date.now();
    return this.cache;
  }
}

const aggregator = new HealthAggregationService();
aggregator.addService("users", "http://users:8080/healthz", 20);
aggregator.addService("payments", "http://payments:8080/healthz", 40);
aggregator.getAggregate().then(r => console.log("Aggregate:", r));

What's Next

Now that you understand health aggregation, learn about monitoring and alerting based on health check results. Then build the complete health check project.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro