A production .NET data extraction and orchestration platform that pulls historical SCADA data out of CygNet's ESENT-backed historian and lands it in SQL Server, where it can be consumed by engineers, data architects, and downstream analytical systems.
Built at Targa Resources to serve internal teams and a large compressor optimization initiative. Hand-coded in C# on ASP.NET Core using an Onion / Clean Architecture, predating the era of AI-assisted development.
CygNet stores high-frequency SCADA history in ESENT — an embedded database that's fast for the historian but a dead end for anyone who wants to do real analytical work. CygnetServices bridges that gap:
- Extracts time-series and event data from CygNet into a relational SQL Server warehouse on a schedule
- Schedules recurring extract jobs with configurable cadence, scope, and retry behavior
- Notifies operators and engineers when extracts fail, fall behind, or breach thresholds
- Monitors its own worker services with health and life-check endpoints
- Exposes an HTTP API and a Razor web UI for engineers to configure jobs, inspect status, and trigger ad-hoc pulls
At Targa, this fed daily analytical work by engineers and data architects, and was the data backbone for a large compressor optimization project.
CygnetServices follows an Onion Architecture: the domain sits at the center, infrastructure points inward through abstractions, and no inner layer ever depends on an outer one. The result is a codebase where the business rules don't know or care that data happens to come from CygNet's ESENT store and land in SQL Server — those are swappable details on the edge.
┌─────────────────────────────────────────────┐
│ Presentation │
│ ┌──────────────┐ ┌────────────────┐ │
│ │ WebApi │ │ CygnetServices │ │
│ │ (REST API) │ │ Web (Razor) │ │
│ └──────┬───────┘ └────────┬───────┘ │
└──────────┼─────────────────────┼────────────┘
│ │
┌──────────▼─────────────────────▼────────────┐
│ Services (Use Cases) │
│ orchestration, application logic │
└──────────┬──────────────────────────────────┘
│ depends only on ↓
┌──────────▼──────────────────────────────────┐
│ Services.Abstractions + Contracts │
│ interfaces, DTOs, the seam between │
│ the inside and the outside world │
└──────────▲──────────────────────────────────┘
│ implemented by ↑
┌──────────────────────┼────────────────────────┐
│ │ │
┌────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Domain │ │ Persistence │ │ Workers │
│ entities │ │ EF Core / │ │ ScadaExtract│
│ + rules │ │ SQL Server │ │ Scheduler │
└──────────┘ └─────────────┘ │ Notification│
│ HealthCheck │
│ JobManager │
│ CygnetAccess│
└─────────────┘
- An engineer defines a recurring extract through the Razor UI or WebApi
ScheduleJobManagerpersists the job definition;SchedulerServicetriggers it on cadenceScadaExtractServicecallsCygnetAccessService, which wraps the CygNet API and pulls data from ESENT- Extracted points land in SQL Server via the
Persistencelayer HealthLifeCheckServicereports worker liveness;NotificationServiceraises alerts on failure or lag
Each worker is a separate process — they can be deployed, scaled, and restarted independently, which mattered when one slow extract shouldn't block the rest.
| Project | Onion Layer | Role |
|---|---|---|
Domain |
Core | Entities, value objects, domain rules — no dependencies |
Services.Abstractions |
Application | Interfaces defining what the application can do |
Contracts |
Application | DTOs crossing the boundary in and out |
Services |
Application | Use case implementations |
Persistence |
Infrastructure | EF Core, SQL Server, repository implementations |
CygnetAccessService |
Infrastructure | Worker wrapping the CygNet API for ESENT reads |
ScadaExtractService |
Infrastructure | Worker performing scheduled extracts |
SchedulerService |
Infrastructure | Worker firing jobs on cadence |
ScheduleJobManager |
Infrastructure | Worker managing job lifecycle |
NotificationService |
Infrastructure | Worker dispatching alerts |
HealthLifeCheckService |
Infrastructure | Worker reporting liveness |
WebApi |
Presentation | ASP.NET Core REST API |
CygnetServicesWeb |
Presentation | Razor UI for engineers |
Presentation |
Presentation | Shared presentation concerns |
- C# / .NET — ASP.NET Core, Web API, Razor Pages
- Worker Services — long-running background processes (
IHostedService) - Entity Framework Core — SQL Server persistence
- CygNet API — proprietary SCADA historian (ESENT-backed)
- SQL Server — target warehouse for extracted SCADA data
A few decisions worth calling out, since they're the reason I'm proud of this codebase:
-
Onion over N-tier. The domain has zero references to EF Core, ASP.NET, or CygNet. Persistence and CygNet access are infrastructure details behind interfaces. This was deliberate: the CygNet API surface changed across versions, and the SQL schema evolved as analytical needs grew — neither change required touching the core.
-
Workers as separate projects, not background tasks in the API. Each long-running concern is its own process. A stuck extract on one well shouldn't take down notifications or scheduling. Operationally this also meant ops could restart one worker without bouncing the whole platform.
-
Hand-coded, pre-AI. Every line, every interface, every dependency graph was a deliberate choice. Worth noting in a portfolio where most modern code is co-authored with an LLM — this one demonstrates the architectural thinking from scratch.
Committed appsettings*.json files use placeholders only (YOUR_SQL_SERVER, YOUR_OIDC_CLIENT_SECRET, and so on). For local development:
- Copy values into User Secrets or environment variables.
- Set
WebApi→Authentication:JwtPublicKey,Cors:AllowedOrigins, and connection strings. - Set
CygnetServicesWeb→ApiSettings:BaseUrl,Oidc:*, andSyncfusion:LicenseKey(optional; omit or leave placeholder to skip license registration).
WebApi listens on https://localhost:44387 and CygnetServicesWeb on https://localhost:7113 per launchSettings.json.
The repo does not include EF Core migrations. Schema mapping lives under Persistence/Configurations/; point ConnectionStrings:ScadaExtracts at an existing SQL Server database (or generate fresh migrations locally with dotnet ef).
Internal Targa Resources system, no longer actively maintained by me (left Targa May 2024). Published as a portfolio artifact illustrating C#/.NET architecture, worker service orchestration, and industrial data integration patterns. CygNet API references are abstracted; no proprietary CygNet code or credentials are included.
Jerrod Tuck — SCADA engineer and founder of narya, building AI-native software for industrial operators.