docs(initial-implementation): add feature plan and 10 phase subplans
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# 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. -->
|
||||
Reference in New Issue
Block a user