ADR-010: Split base_http_client.py into Three Concerns
Status: Superseded — plan abandoned in favor of composite client pattern Date: 2026-05-21 Deciders: (to be assigned)
This ADR documents a plan that was NOT implemented. The three-module split described below was abandoned in favor of a composite client pattern that achieved the same goals (separation of transport bounds, circuit breaking, health checking) without breaking vendor client API compatibility.
Context (Historical)
integrations/base_http_client.py was 635 lines — the third-largest file in the project and the largest file outside models/ and admin/. It mixed three distinct concerns in a single class hierarchy:
| Concern | Approx. Lines | What It Contains |
|---|---|---|
| HTTP Transport | ~250 | Connection pooling (httpx.Client), TLS verification, auth configuration template method, request dispatch, and typed retries |
| Polling Orchestration | ~200 | Former polling mixin coordinating request sequencing and response aggregation |
| Health Tracking | ~100 | Health recording methods, status computation, health metadata |
The class diagram was:
BaseHTTPClient (HTTP transport + health tracking) └── former polling mixin (mixed in via cooperative inheritance) ├── ShureSystemAPIClient └── SennheiserSystemAPIClientAdditionally, the file lived at integrations/ root rather than in integrations/common/, which is inconsistent with the rest of the shared integration infrastructure (common/base.py, common/rate_limiter.py, etc.).
Actual Implemented Architecture (Superseding This ADR)
Instead of the three-module split, the team adopted a composite client pattern in micboard/services/common/base/client.py (392 lines):
-
BaseHTTPClientcomposes:BoundedHTTPTransport(streaming response size enforcement)CircuitBreaker(failure threshold + recovery timeout)
It probes the health endpoint itself and formats the result with the module-level
standardize_health_response. -
Vendor clients (
ShureSystemAPIClient,SennheiserSystemAPIClient) inherit from this compositeBaseHTTPClientand implement abstract methods for auth, endpoints, and exception classes -
Sub-clients for discovery/devices are composed on the vendor client (e.g.,
ShureDiscoveryClient,ShureDeviceClient)
This achieves the same goals (transport bounds, circuit breaking, health checking) without the three-module extraction that would have required breaking vendor client public APIs.
Correction (2026-09-22): health checking was described above as a composed concern, but it
arrived as inheritance: BaseHTTPClient extended HealthCheckMixin from
services/monitoring/, pulling a monitoring module into the transport’s base classes. Of the
mixin’s four methods, check_health and is_healthy were overridden by the client,
_parse_health_response had no caller at all, and only _standardize_health_response was
reachable — from two places in the same file. The mixin is deleted and that helper is now
standardize_health_response beside its callers, so the transport composes its collaborators
and no longer depends on the monitoring domain.
Original Plan (Abandoned)
The original decision was to split into three modules behind the same composite class:
integrations/ common/ http_transport.py (~250L) — Connection pooling, retry, auth configuration. health_tracker.py (~100L) — Health recording, status computation, metadata. services/shared/ polling_orchestrator.py (~200L) — Coordination logic: when to poll, sequencing, aggregation.BaseHTTPClient becomes a thin composition class (~20 lines boilerplate) instantiating and delegating to all three. Call sites move directly to the new domain modules in the same change. Move shared behavior directly into canonical domain modules. No re-export shim or compatibility module retained.
Each concern defines its own test seam:
http_transport.pytests: connection pooling configuration, retry behavior withhttpx.MockTransport, and the auth template method.health_tracker.pytests: pure computation — feed timestamps, assert health status. No mocks needed.polling_orchestrator.pytests: inject fake transport, verify polling sequence. No network required.
Why the Plan Was Abandoned
The cooperative multiple inheritance used by the original BaseHTTPClient (with vendor clients as subclasses) could not be cleanly decomposed into three separate modules without:
- Breaking vendor client API compatibility (would require all callers to update)
- Circular import risk with
polling_orchestratordepending on transport seam - Loss of method resolution order for callers that overrode specific mixin methods
The composite pattern achieved the same architectural goals (bounded transport, circuit breaker, health mixin as separate concerns) while preserving the inheritance-based vendor client API.
References
- See ADR-004 for manufacturer plugin architecture using this composite client
- Current implementation:
micboard/services/common/base/client.py
