Skip to content

CometServer Services

samatstarion edited this page May 4, 2026 · 1 revision

CometServer Services

Sub-page of CometServer. Read that first for the overall picture.

The Services layer sits between the Carter modules (see CometServer Modules) and the auto-generated DAOs in CDP4-ORM. It is where the ECSS-E-TM-10-25 Annex A model is interpreted: cross-cutting business logic, request authorization, container resolution, revision tracking, JSON-exchange import/export, change-log composition, and the orchestration of every Create/Update/Delete operation.

CometServer/AutoGenServices/ is the data-access half of this layer. Each generated XxxService derives from ServiceBase and forwards calls to the matching IXxxDao while applying authorization. The generated services are scanned and registered by Autofac in Startup.cs:

builder.RegisterAssemblyTypes(typeof(ServiceBase).Assembly)
    .Where(x => typeof(ServiceBase).IsAssignableFrom(x))
    .AsImplementedInterfaces()
    .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies)
    .InstancePerLifetimeScope();

The folders under CometServer/Services/ are everything that wraps, complements or coordinates that flow.

Folder map

Folder Responsibility
Operations/ The CUD pipeline: IOperationProcessor walks the posted operation, dispatches to services, and runs the SideEffects.
Operations/SideEffects/ The per-Thing lifecycle hooks. Detail in [[CometServer SideEffects
BusinessLogic/ Algorithmic services that are not bound to a single DAO: parameter value-set computation, RDL chain resolution, file binary handling, finite-state logic.
Supplemental/ Hand-written services that complement the generated services where extra Annex A behaviour is needed (e.g. EngineeringModelService, IterationService, ParameterService, PersonService).
Authorization/ The ISecurityContext carried through the request and used by the SideEffects.
Resolve/ IResolveService — fills in container info and missing types when an operation references a Thing not in the post payload.
Revision/ Revision marking, registry writes, and time-travel reads.
Cache/ The ICacheService that maintains the *_Cache JSONB tables.
ChangeLog/ Builds ModelLogEntry rows so audit trails appear under SiteDirectory.
CherryPick/ Selective iteration reads filtered by category and class kind.
Copy/ Iteration and engineering model copy logic.
DataStore/ Restore / seed / import controllers used by ExchangeFileImportApi.
Email/ Outbound SMTP via MailKit.
JsonExchangeFile/ Reading and writing the Annex C .zip exchange files.
ModelInfo/ Chain-of-RDL helpers (IChainOfRdlComputationService).
Protocol/ Query-string parsing (QueryParameters) and content negotiation.
Headers/ CDP4-COMET-specific HTTP headers (HeaderInfoProvider).
Cache/, Revision/, Resolve/ Together with the matching DAOs in CDP4-ORM they implement the three storage shapes (per-class tables, *_Revision, *_Cache).

The generic IServiceProvider defined in Services/Supplemental/IServiceProvider.cs (note: not the BCL System.IServiceProvider) is the registry that maps a Thing type name to its IServiceBase instance — used by the operation processor and by SideEffects when they need to look up a service by name rather than by compile-time type.

ServiceBase

Every generated service and every Supplemental service derives from Services/ServiceBase:

public abstract class ServiceBase
{
    public const string CreateOperation = "create";
    public const string UpdateOperation = "update";
    public const string DeleteOperation = "delete";

    public IPermissionService PermissionService { get; set; }
    public ICredentialsService CredentialsService { get; set; }
    // …
}

The string constants are used by both the operation processor and the SideEffects to label the in-flight operation. PermissionService and CredentialsService are property-injected, which is why every Autofac registration in Startup.cs for these services uses .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).

Operations: IOperationProcessor

Defined in Services/Operations/IOperationProcessor.cs:

public interface IOperationProcessor
{
    Task ProcessAsync(PostOperation operation, NpgsqlTransaction transaction, string partition, Dictionary<string, Stream> fileStore = null);
    IReadOnlyList<Thing> OperationOriginalThingCache { get; }
}

OperationProcessor (Services/Operations/OperationProcessor.cs) is the orchestrator that turns one HTTP POST into many DAO calls. Its responsibilities, in order:

  1. Validate the envelope. Reject operations that target unknown classes, that lack Iid, RevisionNumber or ClassKind, or that touch the read-only baseProperties (Iid, RevisionNumber, ClassKind).
  2. Resolve missing context. Populate the internal operationThingCache (typed Dictionary<DtoInfo, DtoResolveHelper>) with containers and original Things pulled from the database using IResolveService. This is what allows a client to post a delta without re-sending every container above the modified Thing.
  3. Order operations. Containers before contained, and dependencies before dependants — derived from the metadata in IMetaInfoProvider (which itself is fed by CDP4Common.MetaInfo).
  4. Run the SideEffects. For each create/update/delete, invoke the corresponding IOperationSideEffect.BeforeXxxAsync, dispatch to the service, then invoke AfterXxxAsync. See CometServer SideEffects.
  5. Track originals. OperationOriginalThingCache exposes the set of Things as they were before the transaction so downstream consumers (the change-log composer, change notifications) can diff old vs. new.
  6. Record the operation in the revision history by calling into IRevisionService and the *_Revision DAOs.

top container types are hard-coded as SiteDirectory and EngineeringModel (OperationProcessor.cs:93), matching the partition layout described in CDP4-ORM.

Authorization

Two folders cover authorization, with a deliberate split:

  • CometServer/Authorization/ — the identity layer. ICredentialsService resolves the calling principal into a Credentials object holding the Person, the EngineeringModelSetup for the request (when applicable), and the ParticipantRole. IPermissionService evaluates PersonPermission and ParticipantPermission against AccessRightKind. IAccessRightKindService reads the static defaults declared on each generated DTO. IObfuscationService strips out fields the caller is not allowed to see. IOrganizationalParticipationResolverService filters by OrganizationalParticipant.

  • CometServer/Services/Authorization/ — the per-request security context:

    public interface ISecurityContext
    {
        bool ContainerReadAllowed  { get; set; }
        bool ContainerWriteAllowed { get; set; }
    }

    An ISecurityContext is created when the module begins handling a request and is passed into every DAO call and SideEffect. It carries the cached ContainerReadAllowed / ContainerWriteAllowed decisions so that authorization is computed once per container per request, not on every property check.

When the operation processor processes a child Thing it expects the parent's permission decisions to already be on the security context — the read/write decisions cascade down the containment tree.

Resolve, Revision, Cache

These three folders are the service-side complements to the storage shapes documented in CDP4-ORM and ORM Dao.

IResolveService

Services/Resolve/IResolveService.cs:

public interface IResolveService
{
    Task ResolveItemsAsync(NpgsqlTransaction transaction, string partition, Dictionary<DtoInfo, DtoResolveHelper> resolvableInfo);
    Task<string> ResolveTypeNameByGuidAsync(NpgsqlTransaction transaction, string partition, Guid iid);
}

A POST may reference a Thing only by Iid plus ClassKind. The operation processor builds a Dictionary<DtoInfo, DtoResolveHelper> of the references it cannot evaluate from the request body alone and asks IResolveService to fill them in. Internally it uses the ResolveDao and ContainerDao from CDP4Orm.

IRevisionService and IRevisionResolver

Services/Revision/IRevisionService.cs exposes GetAsync(transaction, partition, revision, useDefaultContext) for time-travel reads and a write side that registers the new revision against the partition's top container. Revisions are partition-scoped: the SiteDirectory and each EngineeringModel/Iteration carry their own monotonically increasing revision counter.

IRevisionResolver translates the revisionNumber, revisionFrom, revisionTo query-string parameters (defined in Services/Protocol/QueryParameters.cs) into the actual revision values the DAOs need. It also handles the special head token used by the cache path.

ICacheService

Services/Cache/CacheService.cs keeps the per-class *_Cache tables in sync. Each row contains Iid, Actor, and a Jsonb column holding the latest serialised form of the Thing. Reads served from the cache path are an order of magnitude faster than reading per-class tables and joining containers, but cannot answer historical queries — see the description of MapJsonbToDto in ORM Dao.

The cache is rebuilt as part of every successful CUD transaction. It is invalidated lazily for non-fatal failures during a transaction; for fatal failures the transaction rolls back and the cache is untouched.

BusinessLogic

Services/BusinessLogic/ is for algorithmic concerns that span more than one DAO. The most important members:

Service Role
IDefaultValueArrayFactory Builds default ValueArray<string> instances for new ParameterValueSets.
IParameterValueSetFactory, IParameterOverrideValueSetFactory, IParameterSubscriptionValueSetFactory Build the set of value sets that match the parameter's ActualFiniteStateList/Option dimensionality.
IFiniteStateLogicService Reconciles ParameterValueSet instances when the underlying ActualFiniteStateList changes.
IStateDependentParameterUpdateService Coordinates value-set updates across state-dependent parameters.
IOptionBusinessLogicService Adds/removes value sets when Options are added or removed from an Iteration.
IChainOfRdlComputationService Resolves the chain of ReferenceDataLibrarys required to dereference a model's parameter types.
ICachedReferenceDataService Memoises chain-of-RDL lookups for the duration of a request.
IFileArchiveService, IFileBinaryService Manage the on-disk representation of FileRevision content uploaded as multipart.
IOldParameterContextProvider Snapshot of the parameter graph as it was before the operation, used by SideEffects that need to detect changes.

Supplemental services

Services/Supplemental/ holds hand-written services that complement (and in some cases override) the generated AutoGenServices. They are needed when the per-Thing behaviour cannot be expressed by simple DAO delegation alone — e.g. when one create on the wire fans out to many DAO writes, or when a service has to enforce a containment invariant that the SideEffects can't see.

Examples:

  • EngineeringModelService and IterationService — top-container creation involves seeding default ParameterValueSets, propagating Option and Publication defaults, and registering the new partition.
  • ParameterService, ParameterOverrideService, ParameterSubscriptionService — coordinate value-set creation and migration alongside the generated DAOs.
  • PersonService — extra logic around password handling and authentication-person resolution.
  • FileService, FolderService, CommonFileStoreService, DomainFileStoreService — bridge file binaries on disk with the metadata in the database.
  • ActualFiniteStateListService, PublicationService — manage cascading updates that a single SideEffect couldn't.

These services follow the same pattern as the generated ones (deriving from ServiceBase) and are picked up by the same Autofac assembly scan.

JsonExchangeFile

Services/JsonExchangeFile/JsonExchangeFileReader.cs and JsonExchangeFileWriter.cs implement the round-trip between a CDP4 zip exchange archive and the database. The reader is what ExchangeFileImportApi's /Data/Exchange and /Data/Import invoke; the writer feeds ExchangeFileExportApi's /export. ZipArchiveWriter packages the JSON streams (one per partition) plus referenced file binaries into a single archive.

JsonExchangeFileReader does not go through IOperationProcessor. It writes directly through the DAOs because the exchange file is canonical input — there are no client deltas to validate against the existing state — and because it sets revision numbers explicitly to preserve the source history.

DataStore

Services/DataStore/DataStoreController.cs orchestrates the destructive operations exposed at /Data/Exchange, /Data/Import and /Data/Restore. The cdp4server and cdp4serverrestore databases described in Configuration are the two databases this controller toggles between. DataStoreConnectionChecker is used by HealthModule's /ready probe to confirm the backtier is reachable.

ChangeNotification and ChangeLog

CometServer/ChangeNotification/ (sibling to Services/) composes the weekly change e-mail. The Hangfire registration is in Startup.cs:520-523:

appLifetime.ApplicationStarted.Register(() =>
{
    RecurringJob.AddOrUpdate<ChangeNoticationService>(
        "ChangeNotificationService.Execute",
        notificationService => notificationService.ExecuteAsync(),
        Cron.Weekly(DayOfWeek.Monday, 0, 15));
});

Services/ChangeLog/ChangeLogService writes per-operation ModelLogEntry rows during a POST. ChangelogBodyComposer later turns those entries into the e-mail body.

CherryPick

Services/CherryPick/CherryPickService and ContainmentService together implement the cherryPick query parameter accepted by EngineeringModelApi. Cherry-picking is a read-side filter: the server walks the iteration's containment tree and returns only the Things whose Category and ClassKind match the requested predicate. It is used by integration tools that want a typed slice of an iteration without paying for the full graph.

Where to look next

  • For the lifecycle hooks the operation processor calls into, continue with CometServer SideEffects.
  • For the SQL produced by the services' DAO calls, see ORM Dao.
  • For the API surface that the modules expose to clients, see REST API - ICD.

Clone this wiki locally