Portal Community

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 logic
  • PackageBundle — ZIP structure representation
  • ArtifactEntry — single artifact within a bundle
  • DependencyGraph — resolved dependency graph for an export or import
  • ImportContext — execution context for an in-progress import
  • ConflictResolutionStrategy — enum: Replace, Merge, Skip
  • ConflictReport — 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 records
  • ExportHistoryRepository — tracks export audit records
  • PackageSerializer — serializes each artifact type to/from JSON
  • BundleWriter / BundleReader — ZIP assembly and extraction
  • ChecksumComputer — 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.

  • IExportServiceExportAsync(request)ExportResult
  • IImportServiceImportAsync(zipBytes, options)ImportResult (Phase 2)
  • IDependencyResolver — recursive artifact dependency traversal
  • IIDRemappingService — maps source IDs to target tenant IDs (Phase 2)
  • IConflictResolutionService — detects and resolves artifact conflicts (Phase 2)
  • IPackageSecurityScanner — pre-install security analysis
  • IMarketplaceService — 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 / ExportResponse
  • ImportRequest / ImportResponse / ImportOptions
  • SecurityScanResult
  • ConflictReportDto

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/export
  • GET /api/installhub/packages/history
  • POST /api/installhub/packages/import (Phase 2)
  • GET /api/installhub/packages/{id}/scan
  • POST /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)
Service Layer as Integration Boundary Extended projects (App Studio, Flow Studio, CI/CD pipelines) must call the 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.