NJ Municipality Lookup
CodebaseSrcAdapters

Metrics

Implementations of the `MetricsPort` interface for observability and monitoring.

Metrics Adapters

Implementations of the MetricsPort interface for observability and monitoring.

Purpose

Provides metrics adapters for recording application telemetry including API performance, cache effectiveness, and rate limit detection. Implements hexagonal architecture (FR-034) for swappable external dependencies.

Key Concepts

  • Port Implementation: All metrics adapters implement MetricsPort from domain/ports/metrics
  • Async Publishing: Non-blocking metric recording
  • Rate Limit Detection: Special tracking for ArcGIS API rate limiting (FR-033)
  • Batching: Efficient metric publishing in batches
  • Error Handling: Failed metrics do not crash the application

Available Adapters

CloudWatchMetrics

AWS CloudWatch metrics adapter for production observability.

Features:

  • Automatic batching (max 20 metrics per API call)
  • Periodic flushing (every 60 seconds by default)
  • Rate limit spike detection with CloudWatch alarms
  • Error handling prevents application crashes if CloudWatch unavailable
  • Configurable namespace and region

Example:

import { createCloudWatchMetrics } from "@/adapters/metrics/cloudwatch-metrics";

const metrics = createCloudWatchMetrics({
  namespace: "NJGeocodingApp",
  region: "us-east-1",
  enabled: process.env.NODE_ENV === "production",
});

// Record API request
await metrics.recordCount("ApiRequests", 1, [
  { name: "Operation", value: "geocode" },
  { name: "StatusCode", value: "200" },
]);

// Record latency
await metrics.recordTiming("GeocodeDuration", 150, [
  { name: "CacheStatus", value: "miss" },
]);

// Record rate limiting (FR-033)
await metrics.recordRateLimit("arcgis-geocode", "429", {
  retryAfter: "60",
  endpoint: "/geocode",
});

NullMetrics

No-op metrics adapter for local development and testing.

All operations complete successfully but perform no actions. Allows code to use metrics without requiring external services.

Example:

import { createNullMetrics } from "@/adapters/metrics/null-metrics";

const metrics = createNullMetrics();

// Safe to call - does nothing
await metrics.recordCount("ApiRequests", 1);
await metrics.recordTiming("GeocodeDuration", 150);

Integration with Application

Metrics are injected into adapters and use cases through dependency injection:

import { createCloudWatchMetrics } from "@/adapters";
import { createFetchClient } from "@/adapters";

const metrics = createCloudWatchMetrics({
  namespace: "NJGeocodingApp",
  region: "us-east-1",
});

const httpClient = createFetchClient({
  timeout: 10000,
  metrics, // Inject metrics for HTTP tracking
});

Metric Types

Counter Metrics

Cumulative values that increase over time (e.g., request counts, error counts).

Timing Metrics

Duration measurements in milliseconds (e.g., API latency, geocode duration).

Gauge Metrics

Point-in-time values (e.g., cache size, active connections).

Rate Limit Metrics

Special metric for detecting API rate limiting (FR-033). Records both HTTP 429 status codes and redirect-based rate limiting from ArcGIS.

Adding New Metrics Adapters

To add a new metrics adapter (e.g., for Prometheus or Datadog):

  1. Create new file implementing MetricsPort
  2. Export factory function
  3. Add to src/adapters/index.ts

Example:

import type { MetricsPort, MetricDimension } from "@/domain/ports/metrics";

export class PrometheusMetrics implements MetricsPort {
  async recordCount(
    name: string,
    value: number,
    dimensions?: MetricDimension[],
  ): Promise<void> {
    // Push to Prometheus pushgateway
  }
  // ... implement other methods
}

export function createPrometheusMetrics(config: PrometheusConfig): MetricsPort {
  return new PrometheusMetrics(config);
}

Observability Requirements (FR-033)

The metrics adapters support rate limiting observability:

  • Track ArcGIS API rate limiting (HTTP 429 and redirects)
  • Log to CloudWatch metrics for spike detection
  • Enable CloudWatch alarms for operational awareness
  • Include metadata (endpoint, retry count, redirect URL)

On this page