Introduce MeasureEnvironment: consolidate endpoint params, resolve at construction#981
Merged
Introduce MeasureEnvironment: consolidate endpoint params, resolve at construction#981
Conversation
Replace Either3<CanonicalType, IdType, Measure> and the three-separate-lists
pattern (List<IdType>, List<String>, List<String>) with a single sealed type:
sealed interface MeasureReference {
record ById(IIdType id)
record ByIdentifier(String identifier)
record ByCanonicalUrl(String url)
}
HAPI providers convert at the boundary via fromOperationParams().
R4MeasureServiceUtils.getMeasures() dispatches on the sealed type.
R4CareGapsParameters uses List<MeasureReference> instead of 3 fields.
R4MultiMeasureService, R4CareGapsProcessor accept List<MeasureReference>.
R4CareGapsService.liftMeasureParameters() deleted -- boundary conversion
now happens once in the transport layer.
R4CollectDataService reads Measure directly via repository.read() instead
of wrapping in Either3.
The type is self-documenting: a measure can be referenced by ID,
identifier, or canonical URL. Compiler enforces exhaustive handling.
contentEndpoint, terminologyEndpoint, dataEndpoint, and additionalData
were passed as four separate parameters through every layer of the
measure evaluation stack -- both public interfaces and internal methods.
Replace all four with MeasureEnvironment.
record MeasureEnvironment(
IBaseResource contentEndpoint,
IBaseResource terminologyEndpoint,
IBaseResource dataEndpoint,
IBaseBundle additionalData)
Per the pipeline architecture, environment resolution is step 2 --
separate from the operation parameters. HAPI providers (the transport
boundary) now construct MeasureEnvironment from the resolved endpoint
params before calling the service layer. All downstream code receives
a single typed object.
R4MultiMeasureService.evaluateToListOfList() now delegates repository
construction to a private resolveRepository(MeasureEnvironment) helper,
making the step explicit and testable in isolation.
MeasureEnvironment.EMPTY is used by R4CareGapsBundleBuilder (which has
no endpoint configuration) and by test helpers that only set additionalData.
IBaseResource / IBaseBundle carry the endpoints version-agnostically,
consistent with the domain-core invariant (no version-specific types).
federateWithAdditionalData(IRepository, MeasureEnvironment) is now a named private method alongside resolveRepository(MeasureEnvironment), making the two-step environment setup explicit in evaluateToListOfList: var effectiveRepo = resolveRepository(environment); // endpoint proxy var subjectRepo = federateWithAdditionalData(effectiveRepo, environment); // bundle overlay Previously the FederatedRepository construction was buried inside getSubjectsForEvaluateSingle, which also hardcoded this.repository as the federation base instead of effectiveRepo. That meant additional-data subjects were resolved against the base repository even when an endpoint proxy was active. getSubjectsForEvaluateSingle now takes the already-prepared subjectRepo directly — one job, one level of abstraction.
All environment config is resolved upfront in resolveRepository(): - endpoint proxy (all three endpoints → Repositories.proxy) - additional data federation (bundle → FederatedRepository overlay) The CQL engine receives resolvedRepo and null for additionalData — the bundle is already accessible through the federated repository. Passing it twice was wrong: the repo is the resolved environment.
Once resolveRepository() runs, there is one repo. No switching. The pre-cached standard processor and utils fields were a remnant of the old conditional — delete them and construct directly from resolvedRepo every time.
…al signatures The resolved IRepository is now a constructor parameter, not a method parameter threaded through the call stack. MeasureEnvironment.resolve(IRepository) composes the final repository: endpoint proxy (when all three endpoints present) then additional-data federation. This is the single place that knows how to build the repo. Operation providers (HAPI transport boundary) resolve environment before constructing the service via the factory: factory.create(requestDetails, environment) → environment.resolve(repositoryFactory.create(requestDetails)) → new R4MultiMeasureService(resolvedRepo, ...) R4MeasureEvaluatorSingle, R4MeasureEvaluatorMultiple, and Dstu3MeasureEvaluatorSingle no longer carry MeasureEnvironment. R4MultiMeasureService.resolveRepository() is deleted. The service receives a repo that is already fully configured; it just uses it.
|
Formatting check succeeded! |
39d8d1a to
b866169
Compare
lukedegruchy
approved these changes
Mar 26, 2026
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Consolidates
contentEndpoint,terminologyEndpoint,dataEndpoint, andadditionalDatainto a singleMeasureEnvironmentrecord, then moves resolution to the right place in the pipeline.MeasureEnvironment record
Version-agnostic (
IBaseResource/IBaseBundle) so it lives cleanly inmeasure/commonwithout importing version-specific FHIR types.MeasureEnvironment.resolve()is the single place that composes the repository: endpoint proxy if all three endpoints are set, then additional-data federation on top.Environment resolved at construction, not at evaluation
Per the operation pipeline (
Architecture.md):Environment config must happen before the runtime, not inside it. Previously
contentEndpoint,terminologyEndpoint,dataEndpoint,additionalDataflowed through every layer as four separate parameters and were resolved deep insideR4MultiMeasureService.evaluateToListOfList().After this change:
MeasureEnvironmentfrom@OperationParaminputsfactory.create(requestDetails, environment)environment.resolve(repositoryFactory.create(requestDetails))IRepositoryevaluate()methods take only domain parameters — noMeasureEnvironmentMeasureEnvironmentis completely absent fromR4MeasureEvaluatorSingle,R4MeasureEvaluatorMultiple,Dstu3MeasureEvaluatorSingle, and both service implementations.resolveRepository lifecycle
Environment resolution builds up the repo in two steps, now made explicit:
Repositories.proxy()when all three endpoints are configuredFederatedRepositoryoverlay whenadditionalDatais non-nullBoth steps happen once, at construction. The CQL engine receives the already-federated
resolvedRepo(not the bundle again separately).Test plan
🤖 Generated with Claude Code