Contracted Service Event Scheduler - Project Overview
Solution Structure
| Project |
Type |
Framework |
Description |
| TELS.ContractedServices.ContractedServiceEventScheduler |
Console App (Batch) |
.NET 8.0 |
Scheduled batch job — schedules pending service events and retries failed ticket generation |
| TELS.ContractedServices.ContractedServiceEventScheduler.Tests |
xUnit (.NET 8.0) |
.NET 8.0 |
Unit tests (Moq, xUnit) |
Architecture
Purpose
ContractedServiceEventScheduler is a scheduled Nomad batch job that runs daily at 2 AM UTC to drive the core scheduling loop for contracted service events. It is a satellite service to the main Contracted Services system, and unlike the notification-only batch jobs, this one orchestrates actual workflow progression.
How It Works — Dual Workflow
Workflow 1: Schedule Services Pending Next Event
1. Queries the Contracted Services API for services awaiting their next scheduled event (paginated)
2. For each service ID, calls POST /commands/scheduleNextServiceEvent on the API
3. Collects failed service IDs for exit code reporting
Workflow 2: Retry Failed Ticket Generation
1. Queries the Contracted Services API for events with status=4 (TicketGenerationFailed) (paginated)
2. For each failed event, calls POST /commands/generateTicket to retry
3. Failures are logged but do not affect the exit code
Exit Code: Returns -1 if any services in workflow 1 failed; 0 if all succeeded.
Error Handling
- 401 Unauthorized: Automatically clears token cache and retries once via
ExecuteWithRetryOnUnauthorized()
- Scheduling failures: Collected and reported via non-zero exit code
- Ticket generation failures: Logged silently — these will be retried on the next daily run
Key Technology Stack
| Category |
Technology |
| Runtime |
.NET 8.0 |
| Orchestration |
Nomad (Platypus framework) |
| Auth |
JWT token refresh via Auth Service (5-minute cache) |
| Resilience |
Polly 8.6.0 (3 retries, exponential backoff: 8ms, 64ms, 512ms) |
| HTTP |
Raw HttpClient with custom BaseProxy (no BusProxy — calls API directly) |
| Configuration |
Vault (secrets) + Consul (config) via envconsul |
| Logging |
JSON to AWS CloudWatch (platypus log group) |
| Testing |
xUnit 2.9.3, Moq 4.20.72 |
External Dependencies
| Integration |
Mechanism |
Description |
| Contracted Services API |
HTTP (direct, via ContractedServicesServiceProxy) |
Queries pending services, schedules events, retries ticket generation |
| Auth Service |
HTTP (via AuthServiceProxy) |
Token refresh (5-minute cache, auto-retry on 401) |
API Calls Made
| Method |
Endpoint |
Purpose |
| GET |
/v2/services/queries/pendingNextServiceEvent?pageKey= |
Paginated list of services needing scheduling |
| POST |
/v2/services/{id}/commands/scheduleNextServiceEvent |
Schedule the next event for a service |
| GET |
/v2/serviceEvents?status=4&pageKey= |
Paginated list of events with failed ticket generation |
| POST |
/v2/serviceEvents/{id}/commands/generateTicket |
Retry ticket generation for a failed event |
Deployment
- Schedule:
0 2 * * * (daily at 2 AM UTC, overlap prohibited)
- Container:
tels/contracted-services/contractedserviceeventscheduler (master branch)
- Resources: 100 MHz CPU, 300 MB memory
- No database, no API endpoints, no MQ consumers, no BusProxy
Environments
| Environment |
Contracted Services Endpoint |
| DEV |
https://devservices.tels.net/contractedServices/ |
| QA |
https://qaservices.tels.net/contractedServices/ |
| PROD |
https://services.tels.net/contractedServices/ |
Notes
- More substantial than notifier jobs: This scheduler drives actual workflow progression (scheduling events and generating tickets), not just notifications.
- No BusProxy: Unlike the other satellite jobs, this one calls the Contracted Services REST API directly — it doesn't publish messages to the queue.
- Dual workflow: Combines event scheduling with failed ticket retry in a single daily run.
- Exit code signaling: Returns non-zero on scheduling failures, enabling Nomad alerting for operational issues.
- Master branch: Uses
master (not main) as default branch — older repo in the subgroup.