Skip to content

Common Library

Sergio Hernandez edited this page Jul 24, 2026 · 1 revision

Common Library

TrackHubCommon is the shared foundation every backend service builds on. It ships as four local NuGet packages, not project references:

Package Contents
TrackHubCommon.Domain Constants, enums, cryptography extensions, localization, domain-event primitives
TrackHubCommon.Application The mediator, the behavior pipeline, attributes, testing helpers
TrackHubCommon.Infrastructure EF conventions and interceptors, the GraphQL client factory, IdentityService
TrackHubCommon.Web GraphQL server registration, error filters, security scheme transformers

The structure follows Jason Taylor's Clean Architecture template, adapted to TrackHub's needs.

Related pages: Architecture · Inter-Service Communication


Domain layer

Area What it provides
Constants Resources, Actions, Roles, Policies, Clients, FeatureKeys, BackgroundJobKeys, Reports, SchemaMetadata, TableMetadata, ColumnMetadata, ViewMetadata, GroupMetadata
Enums ProtocolType, AccountStatus, AnnouncementSeverity, and the rest of the cross-service enums
Cryptography BCrypt for user and driver passwords; server-certificate encryption for third-party secrets such as GPS provider credentials. Two mechanisms, chosen per data type.
Localization ResourceLocalizerGetString(key) against the ambient request culture, GetString(key, locale) for background or per-recipient rendering, and NormalizeLanguage("es-CO") → "es"
Json UTC DateTimeOffset converters
Events BaseEvent, BaseAuditableEntity, AddDomainEvent()

The constant catalogs are the contract. Resource, action, feature-key, schema and table names are never string literals at a call site — a typo there becomes a silent authorization or mapping failure. Adding a constant means repacking (see below).


Application layer

The mediator

Common.Mediator is TrackHub's own CQRS dispatcher — MediatR is not used and is forbidden. It provides IRequest<TResult> / IRequest, IRequestHandler<,>, MediatorDispatcher : ISender, IPublisher, and Unit.

Behaviors

Registered by AddApplicationServices(assembly). AddBasicApplicationServices registers validation and exception handling only, for hosts that do not need the full chain.

Behavior Role
LoggingBehavior Request lifecycle at Debug; caller name resolution gated on logger.IsEnabled(Debug) and on the principal being a user
ValidationBehavior / GraphQLValidationBehavior FluentValidation
AuthorizationBehavior [Authorize] enforcement through IIdentityService
AccountScopeBehavior Fail-closed multi-tenant isolation
CachingBehavior [Caching] over IDistributedCache
RateLimitingBehavior [RateLimiting] token bucket
UnhandledExceptionBehavior Catch-all logging

See Architecture for the ordering and the rules each one enforces.

Testing helpers

Common.Application.Testing.AccountScopeCoverage is wired as AccountScopeCoverageTests in every service's Application.UnitTests project. It fails the build when a request type is neither account-bearing nor explicitly marked, and when [Caching] is combined with a caller-scoped or handler-enforced request.

The gate only sees wire keys. Keyless service-called surfaces are invisible to it and must be checked by enumerating the consumers' GraphQL documents. See Inter-Service Communication.


Infrastructure layer

Component Role
AuditableEntityInterceptor Maintains created/modified columns automatically
DispatchDomainEventsInterceptor Publishes domain events after successful persistence
UseUtcTimestamps() The EF convention that keeps every timestamp column timestamp with time zone
IGraphQLClientFactory The single registration path for every inter-service client — timeouts, header propagation, resilience
IdentityService Centralized identity and permission checks against the Security API, with the 30 s decision cache

Web layer

AddTrackHubGraphQLServer<TQuery, TMutation>(isDevelopment) is the single definition of the platform's GraphQL server configuration: authorization, AddMaxExecutionDepthRule(15), TrackHubGraphQLErrorFilter, request options, and the query/mutation types. Per-service extras chain onto the returned builder.

TrackHubGraphQLErrorFilter maps the platform's typed exceptions onto stable GraphQL error codes — see Architecture.


Versioning

TrackHubCommon/Directory.Build.props is the source of truth for the version, applied in lockstep to all four packages. The current version is 1.0.12.

Every consumer tracks the current version:

  • the eight service repositories — Manager, Telemetry, Security, Router, Geofencing, Reporting, AuthorityServer, TripManagement — through their Directory.Packages.props;
  • the ServiceContracts harness, through a direct PackageReference in TrackHub.ServiceContracts.Harness.csproj. It has no Directory.Packages.props, so a props-only sweep misses it and the contract suite then fails to restore.

Bump every consumer together, and never pin one back.


Repacking the shared packages

When any TrackHubCommon.* project changes — a new constant, a new behavior, a contract change — the local packages must be rebuilt and every consumer bumped.

  1. Bump <Version> in TrackHubCommon/Directory.Build.props.
  2. Build, do not pack. The projects set GeneratePackageOnBuild=true, so packages are produced during dotnet build. dotnet pack can package a stale DLL, or fail with NU5026 after a clean — it does not reliably recompile.
  3. Copy the .nupkg files from each src/Common.*/bin/Debug/ to the local feed and to TrackHubCommon/NugetPackages/.
  4. Purge the global cache when repacking the same version:
    rm -rf ~/.nuget/packages/trackhubcommon.*/<version>
    Otherwise consumers restore the previously extracted copy and you get confusing CS0117 'Resources' does not contain … errors against code you just wrote.
  5. Bump and restore every consumer, including the ServiceContracts harness.
  6. If the change added an authorization resource, also add it to TrackHubSecurity's ApplicationDbContextInitializer DefaultResources — and remember that adding a resource-action row and granting it to a role are two separate steps.

Docker image builds pack the packages automatically in a common stage, so a deployment does not need a pre-populated feed. A local dotnet ef run does — see Deployment and Operations.


Gotchas

  • Common.Domain.Enums must not be added to Manager's Infrastructure GlobalUsings — its TransporterType collides with the Infrastructure.Entities table entity of the same name. Import it per file there.
  • Common's default IFeatureFlagService is fail-open (AlwaysEnabledFeatureFlagService, TryAddScoped). A service that does not register its own silently ignores every [RequireFeature].
  • Common's AccountScopeBehavior is fail-closed. The two defaults pull in opposite directions on purpose: a missing tenant scope is a security failure, a missing feature registration is a service-configuration failure that its own tests should catch.
  • Verifying that a constant landed in a built DLL is best done with grep -a; strings is not always available, and UTF-16 metadata literals defeat it anyway.

Clone this wiki locally