Files
maraphon-app/plans/initial-implementation/phase-4-application-and-workers.md
T

95 lines
4.3 KiB
Markdown

# Phase 4: Application Layer + Background Workers
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
**Depends on:** Phase 1 (Domain), Phase 2 (Storage), Phase 3 (Scraping)
## Objective
Wire scraping + storage together via use-case orchestrators in the Application layer
and background services that execute pollers on configurable intervals.
## Tasks
- [ ] Implement use cases in `Marathon.Application/UseCases/`:
- `PullUpcomingEventsUseCase(IOddsScraper, IEventRepository, ISnapshotRepository)`
- `ExecuteAsync(CancellationToken)` → fetch upcoming events, persist new ones,
capture initial pre-match snapshots for each
- `PullLiveOddsUseCase(IOddsScraper, IEventRepository, ISnapshotRepository)`
- `ExecuteAsync(CancellationToken)` → for each currently-live event, fetch a
fresh snapshot, persist it
- `PullResultsUseCase(IOddsScraper, IEventRepository, IResultRepository)`
- `ExecuteAsync(DateRange range, IReadOnlyList<EventId>? selection, CancellationToken)`
→ fetch results for completed events (all or selected)
- `ExportToExcelUseCase(IExcelExporter, IEventRepository)`
- `ExecuteAsync(DateRange, ExportKind, CancellationToken)`
- [ ] Implement background services in `Marathon.Infrastructure/Workers/`:
- `UpcomingEventsPoller : BackgroundService` — runs `PullUpcomingEventsUseCase` on
a configurable cron-like schedule (default: every 6 hours)
- `LiveOddsPoller : BackgroundService` — runs `PullLiveOddsUseCase` every
`Scraping:PollingIntervalSeconds` seconds
- Both honor `CancellationToken`, log via `ILogger<T>`, and skip cycles gracefully
on errors (don't crash the host)
- [ ] Add `WorkerOptions` POCO bound to `Workers:*` config:
```csharp
public sealed class WorkerOptions {
public string UpcomingScheduleCron { get; init; } = "0 0 */6 * * *"; // every 6h
public bool LivePollerEnabled { get; init; } = true;
public bool UpcomingPollerEnabled { get; init; } = true;
}
```
Use `Cronos` package or simple TimeSpan for upcoming schedule.
- [ ] Add DI extension `AddMarathonApplication(IServiceCollection, IConfiguration)`
in `Marathon.Application/DependencyInjection.cs`:
- Registers all use cases
- [ ] Update `Marathon.Infrastructure/DependencyInjection.cs` to also register
`BackgroundService`s under `services.AddHostedService<T>()`.
- [ ] Tests in `Marathon.Application.Tests`:
- Mock `IOddsScraper` + repos with NSubstitute
- Test: `PullUpcomingEventsUseCase` persists new events, skips duplicates
- Test: `PullLiveOddsUseCase` writes a snapshot per live event
- Test: `PullResultsUseCase` respects `selection` filter (when null, fetches all)
- Test: `ExportToExcelUseCase` invokes `IExcelExporter.ExportAsync` with correct
date range
- [ ] Tests in `Marathon.Infrastructure.Tests/Workers/`:
- Test: `LiveOddsPoller` invokes use case at configured interval (use FakeTimeProvider)
- Test: poller continues after a use-case exception (logs, doesn't propagate)
## Files to Modify/Create
- `src/Marathon.Application/UseCases/*.cs`
- `src/Marathon.Application/DependencyInjection.cs`
- `src/Marathon.Infrastructure/Workers/UpcomingEventsPoller.cs`
- `src/Marathon.Infrastructure/Workers/LiveOddsPoller.cs`
- `src/Marathon.Infrastructure/Configuration/WorkerOptions.cs`
- `tests/Marathon.Application.Tests/UseCases/**`
- `tests/Marathon.Infrastructure.Tests/Workers/**`
## Acceptance Criteria
- Compiles (Big Bang).
- Use cases depend only on Application abstractions (no Infrastructure refs).
- Workers honor cancellation and don't crash on transient errors.
- All variable timing/enabling is configurable.
## Notes
- Use `IHostedService` from `Microsoft.Extensions.Hosting` — works in WPF host via
`Host.CreateApplicationBuilder()` pattern (Phase 5 will expose this).
- For the cron-style upcoming poller, prefer the `Cronos` package (small, mature)
over hand-rolled scheduling.
- Big Bang: compile-only smoke check.
## Review Checklist
- [ ] Use cases have no Infrastructure dependencies
- [ ] Both pollers configurable (interval, enable/disable)
- [ ] Cancellation propagated correctly
- [ ] Errors logged, not propagated out of `ExecuteAsync`
## Handoff to Next Phase
<!-- Filled by Phase 4 implementer. Phase 5 needs to know how to start the host
including these BackgroundServices. -->