Skip to content

Migrate mediation from MediatR to Medino#5616

Draft
mikaelweave wants to merge 31 commits into
mainfrom
mikaelweave/medino-migration
Draft

Migrate mediation from MediatR to Medino#5616
mikaelweave wants to merge 31 commits into
mainfrom
mikaelweave/medino-migration

Conversation

@mikaelweave

@mikaelweave mikaelweave commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Migrates in-repo mediation usage from MediatR to Medino 3.0.3, including package references, DI registration, request/notification call sites, handlers, pipeline behaviors, validator behaviors, and exception actions.
  • Removes redundant non-generic IRequest markers and updates Medino-specific tests/mocks.
  • Includes the approved .NET 10 + Medino planning artifacts that split the overall effort into PR 1 (this Medino migration) and PR 2 (.NET 10 upgrade).

Validation

  • dotnet build Microsoft.Health.Fhir.sln -c Release --no-restore --nologo — passed, 0 warnings, 0 errors.
  • dotnet test src\Microsoft.Health.Fhir.Core.UnitTests\Microsoft.Health.Fhir.Core.UnitTests.csproj -c Release -f net9.0 --no-build --nologo -- --filter "FullyQualifiedName~Validation" --report-trx — passed, 9/9.
  • Child-session project-by-project unit verification passed 18/19 unit projects on both net9.0 and net8.0; Microsoft.Health.Fhir.Azure.UnitTests failed on both frameworks in AzureContainerRegistryAccessTokenProviderTests.GivenARegistry_WithoutMIEnabled_WhenGetToken_TokenException_ShouldBeThrown due to local Managed Identity unavailability, which appears environmental/pre-existing.

Known external dependency boundary

The original strict rg MediatR -> empty target cannot be fully satisfied until Microsoft.Health.SqlServer drops its MediatR API surface. The remaining MediatR references are constrained to that external boundary:

  • src\Microsoft.Health.Fhir.SqlServer\Features\Storage\SchemaUpgradedHandler.cs uses MediatR because SchemaUpgradedNotification is still a MediatR notification from Microsoft.Health.SqlServer 10.0.68.
  • Three SQL integration test helpers still substitute MediatR.IMediator for SchemaInitializer(..., MediatR.IMediator, ...).

ADO: AB#195645, AB#195647

mikaelweave and others added 26 commits June 15, 2026 15:20
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…R2 .NET 10)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- BundleRequest.cs
- CreateResourceRequest.cs
- UpsertResourceRequest.cs
- ValidateOperationRequest.cs
- MemberMatchRequest.cs

These classes already implement IRequest<TResponse> (generic).
The bare IRequest marker is MediatR-specific and not needed in Medino.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace IRequestPreProcessor<BundleRequest> with IPipelineBehavior<BundleRequest, BundleResponse>

- Convert Process() method to HandleAsync() pipeline behavior pattern

- Change Task.CompletedTask to next() for valid bundle path

- Auto-registered by AddMedino (no manual registration needed)

- Invalid bundle validation throw behavior unchanged

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace IRequestPreProcessor<TRequest> with IPipelineBehavior<TRequest, TResponse>
- Rename Process() to HandleAsync() with proper pipeline signature
- Update directly coupled tests to use RequestHandlerDelegate<TResponse> delegates
- Add notnull constraint to TRequest type parameter per IPipelineBehavior requirement
- Verify next handler called only for allowed capabilities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rename Handle method to HandleAsync to match Medino interface
- Remove cancellationToken argument from next() delegate calls

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rename Handle to HandleAsync in both method overloads
- Remove cancellationToken argument from all next() calls
- Update to Medino IPipelineBehavior interface compliance

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rename Handle method to HandleAsync

- Remove cancellationToken parameter from next() delegate calls

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add 'where TRequest : notnull' constraint
- Rename Execute() to ExecuteAsync()
- Update to implement Medino IRequestExceptionAction interface

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update ExecuteAsync() calls in SqlExceptionActionProcessorTests
- Four test methods now call ExecuteAsync instead of Execute

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rename IMediator.Send()/Publish() -> SendAsync()/PublishAsync() on all
  mediator-typed variables in production source (40 files via script)
- Rename Send<T>( -> SendAsync<T>( for generic-typed call sites in
  resource handlers, behaviors, and extension methods (10 more files)
- Rename IRequestHandler Handle() -> HandleAsync() in all production
  handlers (49 files via script)
- Rename INotificationHandler Handle() -> HandleAsync() in all
  production notification handlers
- Rename IPipelineBehavior Handle() -> HandleAsync() and fix
  next(cancellationToken) -> next() in ProvenanceHeaderBehavior and
  ProfileResourcesBehaviour
- Fix ValidateBundlePreProcessor: replace Task alias with proper
  using System.Threading.Tasks to resolve CS0307
- Fix GetOperationVersionsHandler: convert explicit interface impl
  to public method to satisfy CA1033
- Update ISearchParameterStatusManager.Handle -> HandleAsync to match
  implementation rename
- Keep SchemaUpgradedHandler on MediatR.INotificationHandler since
  SchemaUpgradedNotification (Microsoft.Health.SqlServer) implements
  MediatR.INotification, not Medino.INotification

Production source builds clean. Remaining 60 errors are in test
files (direct handler invocations) — deferred to Task 14.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
84 files touched by the Task 13 scripted renames (Set-Content -NoNewline)
lost their UTF-8 BOM. Re-prepend EF BB BF to each affected file; content
is otherwise unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update stale XML doc comments (ExceptionNotification, GeoReplicationLagNotification,
  CosmosStorageRequestMetricsNotification, CreateImportRequestHandler,
  CreateExportRequestHandler): replace 'MediatR message/notification' wording with 'mediator'
- Rename test methods in ApiNotificationMiddlewareTests and
  ExceptionNotificationMiddlewareTests that contained 'MediatR' in their names
- Update comment in ExportControllerTests (dispatch through MediatR -> mediator)
- Fix ExceptionNotificationMiddlewareTests.WhenMediatorFails_OriginalExceptionStillThrown:
  MediatR had two Publish overloads (generic + non-generic object); the DidNotReceiveWithAnyArgs
  assertion accidentally passed because the stub targeted the wrong overload. With Medino's
  single PublishAsync<T>, the stub fires on the actual call; changed assertion to
  ReceivedWithAnyArgs(1) which correctly validates the middleware tried to publish.
- Fix ValidateRequestPreProcessorTests: ValidateRequestPreProcessor was migrated from
  IRequestPreProcessor (no 'next' delegate) to IPipelineBehavior (calls next()); the test
  next-delegate called Substitute.For<SaveOutcome>() which fails (no parameterless ctor);
  replaced with Task.FromResult<UpsertResourceResponse>(null) since the test only checks
  nextCalled==true and validator invocations, not the return value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update stale Mediatr wording in ApiResponseNotification XML doc comment,
consistent with other notification classes cleaned in the previous commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reword stale inline comment in FhirServerBuilderSqlServerRegistrationExtensions
(line 212): 'Mediatr registers handlers as Transient by default' ->
'Handler registrations are Transient by default; ...' to remove framework-
specific wording consistent with other doc cleanups in this series.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mikaelweave and others added 2 commits June 16, 2026 19:29
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mikaelweave mikaelweave added this to the FY26\Q4\2Wk\2Wk26 milestone Jun 17, 2026
@mikaelweave mikaelweave added Dependencies Pull requests that update a dependency file Azure API for FHIR Label denotes that the issue or PR is relevant to the Azure API for FHIR Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs No-PaaS-breaking-change No-ADR ADR not needed labels Jun 17, 2026
var snapshot = hashSet.ToList();
return snapshot.Count;
var count = 0;
foreach (var item in hashSet)
var snapshot = list.ToList();
return snapshot.Count;
var count = 0;
foreach (var item in list)
mikaelweave and others added 3 commits June 16, 2026 21:06
…ino DI resolution

- Added TDD test file FhirModuleRegistrationTests.cs to verify all closed generic interfaces are registered
- Test checks both ProvenanceHeaderBehavior (4 variants) and ProfileResourcesBehaviour (5 variants)
- Implemented explicit factory-based registrations in FhirModule.cs to bypass Medino's .AsImplementedInterfaces() limitation
- Each closed generic IPipelineBehavior<TRequest,TResponse> registered separately with factory delegate
- All 1325 unit tests pass in net8.0 and net9.0

Root cause: Medino's .AsImplementedInterfaces() does not expand all closed generic interfaces when a class implements multiple variants. This caused DI resolution failures at runtime when Medino's pipeline engine tried to fetch behaviors for specific request/response type pairs, resulting in HTTP 500s on resource creation.

Fix: Explicit factory registrations ensure all variants are available to Medino's pipeline engine.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…FhirModule.cs

This fixes the missing factory registration for the 5th closed generic interface
variant of ProfileResourcesBehaviour: IPipelineBehavior<DeleteResourceRequest, DeleteResourceResponse>.

Root cause: Medino's .AsImplementedInterfaces() extension does not automatically
expand all closed generic IPipelineBehavior<TRequest, TResponse> interface variants
when a class implements multiple. This requires explicit factory-based registrations.

ProfileResourcesBehaviour implements 5 closed generics:
1. IPipelineBehavior<CreateResourceRequest, UpsertResourceResponse> ✓
2. IPipelineBehavior<UpsertResourceRequest, UpsertResourceResponse> ✓
3. IPipelineBehavior<ConditionalCreateResourceRequest, UpsertResourceResponse> ✓
4. IPipelineBehavior<ConditionalUpsertResourceRequest, UpsertResourceResponse> ✓
5. IPipelineBehavior<DeleteResourceRequest, DeleteResourceResponse> ← ADDED

Without this registration, DELETE operation handlers cannot resolve their required
pipeline behaviors, causing HTTP 500 errors in E2E tests (BulkUpdate, Reindex,
integration tests).

Verified: All 1325 unit tests pass locally, confirming the fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ions

Root Cause:
The BulkUpdate E2E tests were timing out because background job handlers
(BulkUpdateOrchestratorJob, BulkUpdateProcessingJob, etc.) defined in
Microsoft.Health.Fhir.Core were not registered in the DI container. The
registration code in both SQL Server and Cosmos DB only scanned their own
assemblies for IJob implementations, leaving Core jobs unregistered.

When BulkUpdate requests enqueued jobs to the background queue, the JobFactory
failed to resolve the job type, throwing NotSupportedException and causing the
E2E test polling loop to timeout at 300 seconds.

Fix:
Modified DI registration in both backends to register BOTH backend-specific AND
Core IJob implementations:
- FhirServerBuilderSqlServerRegistrationExtensions.cs: Added scan of Core assembly
  alongside SqlServer assembly for IJob types
- FhirServerBuilderCosmosDbRegistrationExtensions.cs: Added scan of Core assembly
  alongside CosmosDb assembly for IJob types

This ensures all job types are available in the DI container:
- BulkUpdateOrchestratorJob (JobTypeId = 8)
- BulkUpdateProcessingJob (JobTypeId = 7)
- BulkDeleteOrchestratorJob (JobTypeId = 10)
- BulkDeleteProcessingJob (JobTypeId = 9)
- ExportProcessingJob (JobTypeId = 4)
- ReindexOrchestratorJob (JobTypeId = 2)
- ReindexProcessingJob (JobTypeId = 1)

Testing:
- Both registration files compile with 0 warnings, 0 errors
- Core unit tests pass (959 tests)
- Unit tests verify MediatR->Medino DI migration is working correctly

Fixes: BulkUpdate E2E test timeouts in sqlE2eTests_BulkUpdate jobs (R4, R5, STU3)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Azure API for FHIR Label denotes that the issue or PR is relevant to the Azure API for FHIR Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs Dependencies Pull requests that update a dependency file No-ADR ADR not needed No-PaaS-breaking-change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants