Skip to content

Providers & Repositories

Domain Services is storage and technology-agnostic. The actual persistence or data source is handled by providers, via repository implementations.

Common patterns:

  • File-based (JSON/CSV/Directory) usually comes within Domain Services package itself.
  • Database providers (e.g., PostgreSQL, MCLite) — install the specific provider package. Some providers don't support all modules yet.
  • Remote/data API providers (e.g., USGS/DS) for external sources.
  • DFS file-based (dfs0/dfs2/dfsu, etc.) for hydrodynamic and environmental model files.

A service (e.g., ScalarService, TimeSeriesService, JobService) is constructed with a repository that comes from a provider. The same service can work with different repositories—only the repository type and connection string change.


Imperative style (code) vs Declarative style (connections.json)

You can register services:

  1. Imperatively in code with ServiceLocator.Register(...), or
  2. Declaratively in connections.json (lazy-loaded at first use).

Both styles support switching providers (JSON ↔ PostgreSQL ↔ external APIs) without changing your controllers.


Example: Scalars with JSON vs PostgreSQL (Imperative)

JSON persistence (file-based)

ServiceLocator.Register(
    new DHI.Services.Scalars.GroupedScalarService<string, int>(
        new DHI.Services.Scalars.ScalarRepository("[AppData]scalars.json".Resolve()),
        new SimpleLogger("[AppData]scalars.log".Resolve())
    ),
    "json-scalars"
);

PostgreSQL persistence (production)

// Tip: alias the repository to make the assembly source explicit
using PgScalarRepository = DHI.Services.Provider.PostgreSQL.ScalarRepository;

var postgreSqlScalarsConnectionString = "[env:POSTGRES_SCALARS]";

ServiceLocator.Register(
    new ScalarService<int>(
        new PgScalarRepository(postgreSqlScalarsConnectionString),
        new SimpleLogger("[AppData]scalars.log".Resolve())
    ),
    "pg-scalars"
);

Why alias? Repository types live in different assemblies/namespaces. Being explicit (or aliasing) avoids confusion and makes reviews/maintenance safer.


Example: Scalars with JSON vs PostgreSQL (Declarative via connections.json)

Place these entries in App_Data/connections.json. The RepositoryType must be assembly-qualified and match the NuGet package you installed.

JSON provider

{
  "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[DHI.Services.IConnection, DHI.Services]], mscorlib",
  "json-scalars": {
    "$type": "DHI.Services.Scalars.WebApi.ScalarServiceConnection, DHI.Services.Scalars.WebApi",
    "RepositoryType": "DHI.Services.Scalars.ScalarRepository, DHI.Services.Scalars",
    "ConnectionString": "[AppData]scalars.json",
    "Name": "Scalars (JSON file)",
    "Id": "json-scalars"
  }
}

PostgreSQL provider

{
  "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[DHI.Services.IConnection, DHI.Services]], mscorlib",
  "pg-scalars": {
    "$type": "DHI.Services.Scalars.WebApi.ScalarServiceConnection, DHI.Services.Scalars.WebApi",
    "RepositoryType": "DHI.Services.Provider.PostgreSQL.ScalarRepository, DHI.Services.Provider.PostgreSQL",
    "ConnectionString": "[env:POSTGRES_SCALARS]",
    "Name": "Scalars (PostgreSQL)",
    "Id": "pg-scalars"
  }
}

Running multiple providers side-by-side

You can expose several repositories at once—each with a unique connectionId:

{
  "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[DHI.Services.IConnection, DHI.Services]], mscorlib",
  "json-scalars": {
    "$type": "DHI.Services.Scalars.WebApi.ScalarServiceConnection, DHI.Services.Scalars.WebApi",
    "RepositoryType": "DHI.Services.Scalars.ScalarRepository, DHI.Services.Scalars",
    "ConnectionString": "[AppData]scalars.json",
    "Name": "Scalars (JSON file)",
    "Id": "json-scalars"
  },
  "pg-scalars": {
    "$type": "DHI.Services.Scalars.WebApi.ScalarServiceConnection, DHI.Services.Scalars.WebApi",
    "RepositoryType": "DHI.Services.Provider.PostgreSQL.ScalarRepository, DHI.Services.Provider.PostgreSQL",
    "ConnectionString": "[env:POSTGRES_SCALARS]",
    "Name": "Scalars (PostgreSQL)",
    "Id": "pg-scalars"
  },
  "pg-scalars-eu": {
    "$type": "DHI.Services.Scalars.WebApi.ScalarServiceConnection, DHI.Services.Scalars.WebApi",
    "RepositoryType": "DHI.Services.Provider.PostgreSQL.ScalarRepository, DHI.Services.Provider.PostgreSQL",
    "ConnectionString": "[env:POSTGRES_SCALARS_EU]",
    "Name": "Scalars (PostgreSQL EU)",
    "Id": "pg-scalars-eu"
  }
}

At runtime you can call, for example, /api/scalars/pg-scalars/... or /api/scalars/json-scalars/... and each will hit its own backing store.


Choosing a provider

Scenario Repository type Pros Cons
Local dev / demos JSON/CSV (file) Zero infra, easy to inspect Not scalable, no concurrency control
Production DB PostgreSQL, MCLite, etc. Durable, scalable, transactional Requires DB, connection management
External data source USGS, DS, etc. No ETL needed, live data Rate limits, upstream availability

In all cases, the service stays the same; only the repository changes.

For a decision guide with per-provider details, see Choosing a Provider.


Package & assembly notes

  • If you use code registrations, consider type aliasing to make intent explicit:
using JsonScalarRepository = DHI.Services.Scalars.ScalarRepository;
using PgScalarRepository   = DHI.Services.Provider.PostgreSQL.ScalarRepository;

Placeholders & secrets

Use placeholders to keep config flexible and safe:

  • [AppData] – local files next to your app (great for dev)
  • [env:VAR_NAME] – inject secrets at deploy time

Example:

"ConnectionString": "[env:POSTGRES_SCALARS]"

Then set POSTGRES_SCALARS in your environment (K8s secret, Azure App Settings, etc.).


Troubleshooting

  • ”Could not load type …” – The RepositoryType string doesn’t match the installed assembly. Confirm the exact assembly name in your package.
  • DB errors – Ensure the database exists and credentials are correct. If you don’t have the table, it will be created automatically.
  • Multiple providers, same ID – Each connectionId must be unique across connections.json and ServiceLocator.Register(...).
  • Auth – If endpoints return 401, use a valid JWT or your dev bypass from earlier sections.

Next: Core Concepts