13 KiB
Deployment Model Architecture
Purpose
The portal should become a generic configuration composition platform. It must support on-premises workloads such as Active Directory, SQL Server, SharePoint, and Exchange, as well as cloud workloads such as Teams, Azure, and M365.
The database should not know SharePoint-specific concepts such as farm accounts or service application pools. Those details belong inside versioned template documents. The database should know how templates, environments, domains, targets, deployments, jobs, and artifacts relate to each other.
Core Concepts
The model separates reusable catalog data from concrete execution data:
Catalog:
Environment
Domain
Service
Target
Template
TemplateVersion
Design:
DeploymentGroup
Deployment
DeploymentTemplateSelection
DeploymentParameterValue
DeploymentTarget
Execution:
QueueJob
QueueJobStep
QueueJobTarget
DeploymentArtifact
Environment
An environment is a logical target context, for example Prod A, Test A, Prod B, Test B, or QA B.
Suggested fields:
IdKeyNameStage, for exampleDevelopment,Test,QA,ProductionHostingType, for exampleOnPrem,Azure,M365,HybridProviderTypeTenantIdSubscriptionIdMetadataJson
Domain
A domain is reusable catalog data and configuration. A domain can be linked to multiple environments.
Example:
Central-Management
-> Test A
-> Prod A
-> QA B
The goal is to change the Central-Management domain configuration once and then decide which linked environments should consume the new version.
Suggested relationship:
EnvironmentDomain
- EnvironmentId
- DomainId
- Role
- SortOrder
Service
A service represents a workload family, for example:
active-directorysql-serversharepointexchangeteamsazure-network
Suggested fields:
IdKeyNameDescriptionWorkloadType, for exampleOnPrem,Azure,M365,HybridIconKeyIsActive
Target
A target is anything a deployment can act on. It should not be limited to virtual machines.
Suggested target types:
VirtualMachineTenantSubscriptionResourceGroupUserGroupSitePolicyScope
Suggested fields:
IdNameTargetTypeEnvironmentIdDomainIdExternalIdMetadataJson
For current VM usage, VirtualMachineModel can either remain as a specialized target table during migration or be folded into a generic target model later.
Templates
Template
Template should be the stable catalog entry. It should not contain mutable deployment content.
Suggested fields:
IdKey, for exampleservice.sharepoint.defaultNameTemplateType, for exampleDomain,Environment,Service,Stage,TargetSet,PolicyWorkloadType, for exampleOnPrem,Azure,M365,HybridServiceIdTemplateCategoryIdDeploymentRuleIdIsActive
Template is mutable catalog metadata. It can be renamed, categorized, activated, or deactivated without changing the immutable template document history.
TemplateVersion
TemplateVersion should contain the immutable template document.
Suggested fields:
IdTemplateIdVersionJsonDataJsonHashSchemaVersionIsPublishedPublishedAtPublishedByCreatedCreatedBy
Recommended constraints:
- Unique:
TemplateId + Version - Optional unique filtered index:
TemplateIdwhereIsPublished = true, if only one current published version should exist.
Template version content is immutable after publication. If the document changes, create a new TemplateVersion.
JsonData Strategy
Keep JsonData as text in SQL Server, but do not treat it as an unstructured dumping ground.
The canonical document shapes and validation rules are defined in Docs/Architecture/JsonDocumentContract.md.
Database storage:
JsonData nvarchar(max)
JsonHash nvarchar(64)
SchemaVersion nvarchar(32)
EF model storage can remain a string:
public string JsonData { get; private set; } = "{}";
public string JsonHash { get; private set; } = string.Empty;
public string SchemaVersion { get; set; } = "1.0";
API DTOs should expose real JSON instead of escaped JSON strings where practical:
public JsonElement JsonData { get; set; }
The service layer should canonicalize JSON, validate it, calculate the hash, and store it as a string.
Recommended approach:
- Store the canonical template/deployment document as JSON.
- Version every template document.
- Validate JSON before saving.
- Store a content hash beside the document.
- Store schema version beside the document.
- Project frequently queried values into normal columns or helper tables.
Do not store binary .NET serialization, base64 payloads, or provider-specific object graphs. The stored document should remain plain JSON so it can be returned by the API, diffed, hashed, migrated, and rendered to DSC v3 documents later.
This gives us flexibility for DSC v2, DSC v3, and cloud workloads without turning every possible parameter/resource into relational tables.
Why Keep JSON?
The configuration document is naturally hierarchical:
Metadata
Parameters
Variables
Resources
Trying to fully normalize this would make the database rigid and painful for new services. SharePoint, Exchange, Teams, and Azure do not share the same resource shape.
What Should Be Normalized?
Normalize stable catalog and workflow data:
- templates
- template versions
- environments
- domains
- services
- targets
- deployment groups
- deployments
- template selections
- parameter overrides
- jobs
- artifacts
Keep service-specific configuration inside JsonData.
Optional Projection Tables
For GUI performance and validation, create projection tables derived from JsonData.
Example:
TemplateParameters
- TemplateVersionId
- Name
- Type
- Required
- Sensitive
- Sealed
- DescriptionJson
- AllowedValuesJson
The source of truth stays TemplateVersion.JsonData. Projection tables can be rebuilt when a template version changes.
Deployments
DeploymentGroup
A deployment group represents a deployable environment composition, for example:
Contoso Test A Full Farm
- AD Deployment
- SQL Deployment
- SharePoint Deployment
Suggested fields:
IdEnvironmentIdNameStatusDeploymentRuleIdCreatedCreatedBy
In the current model, DeploymentGroupModel maps to the DeploymentBatches table and still has a direct TemplateId. In the target model this direct TemplateId becomes legacy. The selected templates move to DeploymentTemplateSelection.
Deployment
A deployment is one deployable workload slice inside a group.
Examples:
ADSQLSharePointExchangeTeams Policies
Suggested fields:
IdDeploymentGroupIdServiceIdNameSortOrderStatusDeploymentDataJsonMergedConfigurationDataJsonResolvedPreviewJsonRendererTarget, for examplePowerShellDscV2,DscV3Json,CloudApi
In the current model, DeploymentModel maps to DeploymentExecutions and is keyed by VirtualMachineId + DeploymentGroupId. This is closer to a target execution than to a design-level deployment. During migration, keep it as compatibility data and introduce the new composition tables beside it.
DeploymentTemplateSelection
A deployment selects multiple template versions.
Suggested fields:
IdDeploymentIdTemplateVersionIdTemplateRole, for exampleDomain,Environment,Service,Stage,TargetSet,PolicySortOrderAlias
Example SharePoint deployment:
Domain/Central-Management
Environment/Test A
Service/SharePoint/Contoso
Stage/Install
Targets/SharePointNodes
DeploymentParameterValue
User overrides should be stored separately from the generated full deployment document.
Suggested fields:
IdDeploymentIdDeploymentTemplateSelectionIdNameValueJsonIsSecretReferenceIsOverride
DeploymentTarget
Suggested fields:
IdDeploymentIdTargetIdRoleKey, for exampleDomainController,SqlServer,SharePointServer,TeamsPolicyScopeSortOrderNodeDataJson
Queue And Worker
The existing QueueJobModel, QueueJobStepModel, and QueueJobTargetModel are a good foundation.
Recommended additions:
CorrelationIdPriorityScheduledAtHeartbeatAtWorkerNameRowVersion
Recommended indexes:
(Status, LockedUntil, Created)(Type, Status)(CorrelationId)
The worker should claim jobs atomically and update LockedBy, LockedUntil, and HeartbeatAt.
The existing queue tables can remain. The first improvements should be additive so the current Web/API flow keeps working.
Artifacts
Generated output should be stored as artifacts.
Suggested fields:
IdDeploymentIdQueueJobIdArtifactType, for exampleMergedJson,ResolvedPreviewJson,PowerShellDataFile,Mof,DscV3Json,LogPathContentHashContentJsonContentTextCreated
Worker Pipeline
Current DSC v2 flow:
Load template versions
Convert JSON to ordered PowerShell hashtables
Merge-DSCConfigurationData
Resolve-DSCConfigurationData
Render PowerShell DSC v2
Compile MOF
Store artifacts and status
Future DSC v3 flow:
Load template versions
Merge/resolve
Render DSC v3 JSON/YAML document
dsc config test/set
Store artifacts and status
The worker should therefore have a renderer abstraction:
PowerShellDscV2RendererDscV3JsonRenderer- future cloud/action renderers
Migration Strategy
- Add
TemplateVersionswhile keepingTemplates.JSONData. - Create one initial
TemplateVersionper existingTemplate. - Update API reads to expose the current published version.
- Add deployment composition tables next to the existing deployment batch/execution model.
- Migrate Web and Worker to the new model.
- Remove legacy fields only after the new flow is stable.
Current Model Mapping
The current model maps to the target model as follows:
TemplateModel
-> Template
-> TemplateVersion
TemplateCategoryModel
-> TemplateCategory
ServiceModel
-> Service
EnvironmentModel
-> Environment
DomainModel
-> Domain
EnvironmentDomainsModel
-> EnvironmentDomain
VirtualMachineModel
-> Target where TargetType = VirtualMachine
DeploymentGroupModel / DeploymentBatches
-> DeploymentGroup
DeploymentModel / DeploymentExecutions
-> legacy deployment target/execution record
-> later replaced or complemented by Deployment + DeploymentTarget
QueueJobModel / DeploymentJobs
-> QueueJob
QueueJobStepModel / DeploymentJobSteps
-> QueueJobStep
QueueJobTargetModel / DeploymentJobTargets
-> QueueJobTarget
Compatibility Rules
During migration:
- Keep existing endpoints working.
- Keep
TemplateModel.JSONDatauntil API reads and writes useTemplateVersion. - New code should prefer
TemplateVersion.JsonData. - Existing
DeploymentGroup.TemplateIdremains legacy and should be populated for old clients. - New deployments should use
DeploymentTemplateSelection. - Do not remove old database columns until Web and Worker are migrated.
- Worker code should be written against the target composition model as soon as the new tables exist.
Planned API Shape
Initial compatibility endpoint:
GET /api/Template/{id}
should continue to return the existing DTO shape, but its JsonData should come from the current published TemplateVersion when available.
New endpoints:
GET /api/templates/{templateId}/versions
GET /api/templates/{templateId}/versions/{versionId}
POST /api/templates/{templateId}/versions
POST /api/templates/{templateId}/versions/{versionId}/publish
Deployment composition endpoints later:
GET /api/deployment-groups/{id}/composition
POST /api/deployment-groups/{id}/deployments
POST /api/deployments/{id}/template-selections
POST /api/deployments/{id}/targets
POST /api/deployments/{id}/parameter-values
First Database Change
The first concrete database change should be additive:
TemplateVersions
- Id
- TemplateId
- Version
- JsonData
- JsonHash
- SchemaVersion
- IsPublished
- PublishedAt
- PublishedBy
- Modified
- ModifiedBy
- Created
- CreatedBy
TemplateVersions.TemplateId references Templates.Id.
For existing data, create one version per current template:
Version = Templates.Version
JsonData = Templates.JSONData
IsPublished = true
SchemaVersion = '1.0'
After this step, new code can begin reading from TemplateVersions while old code can still read Templates.JSONData.