Infrastructure Split Plan

Updated: 2026-08-08 07:01 ET · Audited: 2026-06-26

TL;DR: Plan (Epic #2081 / WI-2086, Phase 4 of the N-Tier refactor) to decompose the DataManager (~2,403 lines) and RestService (~2,073 lines) singletons into 8 focused Infrastructure services, one per domain. The Application-layer interfaces and DI aliases already exist, so each migration is an alias swap in DI — callers see no change. Work runs in 4 batches ordered low → high risk, ending with DataManager/RestService deletion; completing WI-2086 unblocks WI-2087 (Presentation Extraction).

Epic #2081 / WI-2086 (Phase 4)

Overview

DataManager.cs (~2,403 lines) and RestService.cs (~2,073 lines) are both registered as application-wide singletons and together handle every cache-aside operation and HTTP call in the app. This plan decomposes them into 8 focused Infrastructure service implementations, each owning one domain's caching logic and its corresponding HTTP calls.

Why now: All Application-layer interfaces are already extracted (ALPAMobile.Application/Abstractions/). The DI aliases (IDocumentsQueries → IDataManager, etc.) are in place in ALPAMobile/HeadServiceCollectionExtensions.cs. The split is a substitution: remove the alias, register a real implementation. No callers need to change; they already depend on the interface.

Target state:

Service Decomposition

#Service classInterface(s)Key domain
1ContentServiceIContentQueriesPage banners, app config, company info, resource links, call-to-action, in-case-of-accident, app update status
2MemberServiceIMemberQueriesMember profile, member number lookup, PAC member info
3MenuServiceIMenuQueriesMenu items (including mock items)
4JumpseatServiceIJumpseatQueriesAirline jumpseat policies, jumpseat airlines, KCM airports, KCM airlines
5MECServiceIMECQueriesMEC/LEC events, representatives, hotel airports, committees
6NotificationsServiceINotificationsQueries, ISubscriptionsQueriesNotification list, push token registration, subscriptions toggle
7DocumentsServiceIDocumentsQueries, IDocumentsCommandsDocument list, scoped docs, categories, download, local DB cache
8FlightSearchServiceIFlightSearchQueriesAirports, flight search, saved flights refresh, flight status
Note: IFTDTQueries (GetAllDutyPeriodsList, GetSoftDeletedDutyPeriodsList) reads from FTDTDataBase (local SQLite), not DataManager's HTTP cache. Those two methods stay in FTDTDataSyncHelper / FTDTDataBase. The FTDT remote calls (CreateDutyPeriodAsync, UpdateDutyPeriodAsync, SoftDeleteDutyPeriodAsync) migrate with RestService's internals but are not a new Infrastructure service.

Migration Approach

Principle: interface-by-interface, alias swap

The DI aliases in ALPAMobile/HeadServiceCollectionExtensions.cs are the seam. Remove one alias at a time and register its real implementation:

// Before
services.AddSingleton<IDataManager, DataManager>();
services.AddSingleton<IContentQueries>(sp => sp.GetRequiredService<IDataManager>());

// After (one interface at a time)
services.AddSingleton<IContentQueries, ContentService>();
// IDataManager alias for IContentQueries removed

Callers (ViewModels, Pages) already inject IContentQueries; they see no change.

Branch strategy

Base: imp/blazor-hybrid (active target, WI-2086 is Phase 4). Four batches, each merging back before the next starts:

imp/blazor-hybrid
  └── infra/split-batch-1   (ContentService + MemberService)
  └── infra/split-batch-2   (MenuService + JumpseatService)
  └── infra/split-batch-3   (MECService + NotificationsService)
  └── infra/split-batch-4   (DocumentsService + FlightSearchService + DataManager teardown)

Auth context cache clearing

Each new service implements IAuthContextCache. Authentication.cs already resolves IEnumerable<IAuthContextCache> and calls ClearCache() on all of them on sign-out. Verify this wiring is in place before starting Batch 2.

Code Examples

ContentService implementation (cache-aside pattern)

// ALPAMobile.Infrastructure/Content/ContentService.cs
internal sealed class ContentService : IContentQueries, IAuthContextCache
{
    private readonly IRequestProvider _http;

    private ObservableCollection<PageBanner>? _pageBanners;
    private AppConfigSettings? _appConfig;
    // ... other cache fields

    public ContentService(IRequestProvider http) => _http = http;

    public async Task<ObservableCollection<PageBanner>?> GetPageBannersAsync(bool refresh = false)
    {
        if (!refresh && _pageBanners is not null)
            return _pageBanners;

        _pageBanners = await _http.GetAsync<ObservableCollection<PageBanner>>(ApiEndpoints.PageBanners);
        return _pageBanners;
    }

    // IAuthContextCache — called by Authentication on sign-out
    public void ClearCache()
    {
        _pageBanners = null;
        _appConfig = null;
        // ... clear all fields
    }
}

DI registration — final state (after Batch 4)

// ALPAMobile/HeadServiceCollectionExtensions.cs — AFTER Batch 4 (IDataManager and IRestService removed entirely)
builder.Services.AddSingleton<IContentQueries, ContentService>();
builder.Services.AddSingleton<IMemberQueries, MemberService>();
builder.Services.AddSingleton<IMenuQueries, MenuService>();
builder.Services.AddSingleton<IJumpseatQueries, JumpseatService>();
builder.Services.AddSingleton<IMECQueries, MECService>();
// NotificationsService implements two interfaces (shared instance trick):
builder.Services.AddSingleton<NotificationsService>();
builder.Services.AddSingleton<INotificationsQueries>(sp => sp.GetRequiredService<NotificationsService>());
builder.Services.AddSingleton<ISubscriptionsQueries>(sp => sp.GetRequiredService<NotificationsService>());
builder.Services.AddSingleton<IDocumentsQueries, DocumentsService>();
builder.Services.AddSingleton<IDocumentsCommands>(sp => sp.GetRequiredService<DocumentsService>());
builder.Services.AddSingleton<IFlightSearchQueries, FlightSearchService>();

InfrastructureServiceCollectionExtensions

Each batch adds registrations to ALPAMobile.Infrastructure/InfrastructureServiceCollectionExtensions.cs (not MauiProgram.cs). IAuthContextCache is registered multiple times intentionally — IEnumerable<IAuthContextCache> resolution calls ClearCache() on all implementations.

Finding remaining IDataManager direct injections

grep -rn "IDataManager" ALPAMobile/ViewModels/ ALPAMobile/Pages/

Any ViewModel still injecting IDataManager directly (bypassing the interface) must be updated before the DataManager alias for that interface can be removed.

Batch Breakdown

Batch 1 — ContentService + MemberService (2–3 days)

Risk: Low. Pure HTTP GET + in-memory cache; no local DB writes; no cross-service deps.

  1. Create ALPAMobile.Infrastructure/Content/ContentService.cs
  2. Create ALPAMobile.Infrastructure/Member/MemberService.cs
  3. Extract endpoint constants from RestService.cs URL strings → ApiEndpoints static class
  4. Add registrations to InfrastructureServiceCollectionExtensions
  5. Remove DI aliases for the two interfaces from ALPAMobile/HeadServiceCollectionExtensions.cs
  6. Remove interface implementations from DataManager.cs
  7. Run dotnet build ALPAMobile.sln — confirm 0 errors
  8. Run dotnet test UnitTest/UnitTest.csproj
  9. Deploy to simulator, smoke test: Home screen banners, Member profile page

Batch 2 — MenuService + JumpseatService (2 days)

Risk: Low–Medium. Menu has a large response body with recursive category tree; verify IAuthContextCache wiring in Authentication.cs before tagging services as implementors. Smoke test: Menu navigation, KCM Airports, Jumpseat Policies.

Batch 3 — MECService + NotificationsService (2–3 days)

Risk: Medium. Subscription toggle is a POST that invalidates the cache — verify ToggleSubscriptionAsync clears _subscriptions before returning. Handle dual-registration pattern for NotificationsService. Smoke test: Advocacy page, Notifications center, MEC events.

Batch 4 — DocumentsService + FlightSearchService + DataManager teardown (3–4 days)

Risk: High. Documents has local DB reads/writes + file download + scope-indexed in-memory cache. Test download resumption and scope invalidation paths explicitly.

  1. Create DocumentsService (depends on IDocumentDatabase / LiteDB + IRequestProvider)
  2. Create FlightSearchService
  3. Find and update all remaining IDataManager direct injections
  4. Remove IDataManager registration from ALPAMobile/HeadServiceCollectionExtensions.cs
  5. Delete DataManager.cs and RestService.cs
  6. Remove IRestService registration
  7. Run full build + all tests
  8. Deploy to simulator — test Documents download, Flight Search, FTDT list

Architecture Test Gate

Layer-dependency rules already live in UnitTest/Architecture/LayerDependencyRulesTests.cs. Add a test there for each migrated batch to prevent regression:

// UnitTest/Architecture/LayerDependencyRulesTests.cs — add after each batch lands
[Test]
public void ContentService_must_not_depend_on_Application_implementations()
{
    Types.InAssembly(typeof(ContentService).Assembly)
         .That().HaveNameEndingWith("Service")
         .Should().NotHaveDependencyOn("ALPADocs.Services")
         .GetResult().IsSuccessful.Should().BeTrue();
}

Definition of Done (WI-2086)

Open Questions for Team Review

  1. IRestEndpointProviderDataManager currently implements this (exposes GetRestServiceBaseUrl() etc.). Should this move to a dedicated EndpointConfigService, or is it better placed in ISettingsService?
  2. FTDTDataSyncHelper — currently calls IRestService directly for create/update/delete DutyPeriods. After RestService is deleted, decide whether it calls IFTDTRemoteService or a method injected from FlightSearchService. Decide ownership before Batch 4.
  3. CacheDatabase vs in-memory — Some services cache in CacheDatabase (serialized to SQLite), others in in-memory fields. Recommendation: in-memory for session-only data (notifications, menus), CacheDatabase for data that must survive cold-start (documents list, member profile).
  4. Error handling / offline fallbackDataManager has retry logic from RestService.RetryTransientAsync (see MauiProgram.cs comment AB#2039). RequestProvider already handles retry via Polly. Confirm no duplicate retry wrapping in new services.

Cross-References

DocumentLocation
N-Tier Architecture Refactor overviewn-tier-orchestration-handoff.html
Blazor Hybrid Migration Guide (service extraction roadmap)blazor-hybrid-migration-guide.html