Architecture
InstallHub is a 7-project C# solution following clean architecture principles. The domain is infrastructure-agnostic; the service layer is the sole integration boundary for external callers.
7-Project Solution Structure
InstallHub.Domain — Domain Layer
Entities, value objects, domain interfaces, and domain events. No infrastructure dependencies. Key types:
PackageManifest— manifest entity with validation logicPackageBundle— ZIP structure representationArtifactEntry— single artifact within a bundleDependencyGraph— resolved dependency graph for an export or importImportContext— execution context for an in-progress importConflictResolutionStrategy— enum: Replace, Merge, SkipConflictReport— list of detected conflicts with resolution decisions
InstallHub.Infrastructure — Infrastructure Layer
Database access, ZIP I/O, checksum computation, and external service adapters. Implements the repository interfaces defined in Domain.
PackageRepository— EF Core repository for package recordsExportHistoryRepository— tracks export audit recordsPackageSerializer— serializes each artifact type to/from JSONBundleWriter/BundleReader— ZIP assembly and extractionChecksumComputer— SHA-256 hash computation over artifact bytes
InstallHub.Service — Service Layer
Orchestrates export, import, and marketplace operations. This is the only layer that external callers (App Studio, Flow Studio, CI/CD scripts) are permitted to invoke.
IExportService—ExportAsync(request)→ExportResultIImportService—ImportAsync(zipBytes, options)→ImportResult(Phase 2)IDependencyResolver— recursive artifact dependency traversalIIDRemappingService— maps source IDs to target tenant IDs (Phase 2)IConflictResolutionService— detects and resolves artifact conflicts (Phase 2)IPackageSecurityScanner— pre-install security analysisIMarketplaceService— publish, search, and install from PublicHub (Phase 3)
InstallHub.Api.Base — API Contracts
Request and response DTOs shared between the API project and external callers. Versioned API contracts ensure backward compatibility.
ExportRequest/ExportResponseImportRequest/ImportResponse/ImportOptionsSecurityScanResultConflictReportDto
InstallHub.Api — HTTP Layer
ASP.NET Core controllers exposing the REST API surface. Controllers delegate immediately to the service layer — no business logic lives here.
POST /api/installhub/packages/exportGET /api/installhub/packages/historyPOST /api/installhub/packages/import(Phase 2)GET /api/installhub/packages/{id}/scanPOST /api/marketplace/submit(Phase 3)
InstallHub.Tests — Unit Tests
xUnit tests for domain logic and service orchestration. Mocks all infrastructure dependencies using NSubstitute. Target: 86% coverage on service and domain layers.
InstallHub.IntegrationTests — Integration Tests
End-to-end pipeline tests running against a real database (SQL Server via TestContainers). Validates the full export → import cycle and security scan pipeline.
Dependency Rule
Dependencies flow strictly inward:
Api → Api.Base → Service → Domain
Infrastructure → Domain
Tests → Service, Domain (mocked Infrastructure)
IntegrationTests → Api (full stack)
InstallHub.Service layer only — never the controller or infrastructure layers directly. This ensures all operations are audited, validated, and subject to security scanning.
Service Interface Contracts
// Phase 1 — COMPLETE
public interface IExportService
{
Task<ExportResult> ExportAsync(ExportRequest request, CancellationToken ct = default);
Task<IReadOnlyList<ExportHistoryRecord>> GetHistoryAsync(string tenantId, CancellationToken ct = default);
}
// Phase 2 — PENDING
public interface IImportService
{
Task<ImportResult> ImportAsync(byte[] zipBytes, ImportOptions options, CancellationToken ct = default);
Task<DryRunResult> DryRunAsync(byte[] zipBytes, ImportOptions options, CancellationToken ct = default);
}
// Phase 3 — PENDING
public interface IMarketplaceService
{
Task<SubmissionResult> SubmitPackageAsync(byte[] zipBytes, SubmissionMetadata metadata, CancellationToken ct = default);
Task<PagedResult<PackageListing>> SearchAsync(MarketplaceSearchQuery query, CancellationToken ct = default);
Task<InstallResult> InstallFromMarketplaceAsync(string packageId, string version, ImportOptions options, CancellationToken ct = default);
}
Multi-Tenant Data Isolation
Every database query in InstallHub is scoped by TenantId. Package records, export history, and import history are tenant-isolated. The database schema uses TenantId as a partition key and all repository methods require it as a parameter — it cannot be omitted by accident.