ADR 0003 — Facade + mixin pattern for the public API¶
- Status: Superseded by ADR 0005
- Date: 2026-06-02
- Deciders: @javierpa95
Superseded (v0.6.0). The mixin layer described here (
ModularGlucoseData,ModularGlucoseMetrics, the*Metricsmixin classes) has been removed. The public API is now pure functions + composition: standalone metric functions plus theGlucoseAnalysisfacade. See ADR 0005. This document is kept for historical context.
Context and Problem Statement¶
CGMPy has three distinct user-facing concerns: loading/processing data,
computing clinical metrics, and generating plots. Early versions exposed
a flat namespace where every function was importable directly
(e.g., cgmpy.mean_glucose(df)). This approach had three problems:
- No discoverability: users had to read the source code to find what functions existed.
- Tight coupling: data loaders, metric calculators, and plot functions all depended on each other implicitly through shared assumptions about data format.
- No state management: every call required passing the full DataFrame and parameters, making multi-step analysis repetitive.
We need a user-friendly API that groups operations by concern, manages state, and remains extensible without breaking backward compatibility.
Considered Options¶
- Facade + mixin pattern — three facade classes
(
GlucoseData,GlucoseMetrics,GlucosePlot) that compose modular mixins internally. Users interact only with the facades. - Service-oriented — standalone functions in namespaced modules
(e.g.,
cgmpy.metrics.basic.mean()). No classes, no state. - Monolithic class — a single
CGMclass that does everything: loading, metrics, plotting.
Decision Outcome¶
Chosen option: Facade + mixin pattern, because:
- Facades provide a clean entry point for users:
GlucoseData(file)→GlucoseMetrics(data)→GlucosePlot(data). - Mixins allow internal code organization without exposing internals.
ModularGlucoseDataandModularGlucoseMetricsare mixins; they exist incgmpy.dataandcgmpy.metricsbut are composed by the facades. - The pattern is extensible: adding a new metric means adding a method to the relevant mixin; the facade picks it up automatically.
- State is managed naturally:
GlucoseDataholds the validated DataFrame,GlucoseMetricsreferences it through the mixin.
Consequences¶
- Good, because users only need to learn three class names.
- Good, because internal refactors (e.g., splitting a large mixin) do not affect the public API.
- Good, because
GlucoseAnalysis(a higher-level facade) can compose the three sub-facades into a single workflow. - Bad, because the mixin hierarchy is non-trivial to understand for
new contributors (
ModularGlucoseMetricsinherits from three separate mixin classes). Mitigated by AGENTS.md explaining the pattern.
Pros and Cons of the Options¶
Facade + mixin¶
- Good, clean public API surface (three classes).
- Good, encourages separation of concerns.
- Good, easy to test (each mixin in isolation).
- Bad, internal complexity is higher than service-oriented approach.
Service-oriented (flat functions)¶
- Good, simple mental model.
- Bad, no discoverability — users must know which module to import.
- Bad, every call repeats the same DataFrame and parameter arguments.
- Bad, harder to extend without breaking imports.
Monolithic class¶
- Good, single import.
- Bad, violates Single Responsibility Principle.
- Bad, impossible to test in isolation.
- Bad, grows unboundedly as features are added.