Skip to content

ADR-00xx: Template Layering and Submodules

  • Status: accepted
  • Deciders: ConnectSoft Architecture Team
  • Date: 2026-01-15

Context and Problem Statement

ConnectSoft maintains multiple specialized templates (Identity, Auth, Audit, Worker, etc.) that share significant common infrastructure. Without a structured approach, we face:

  • Code Duplication - Common infrastructure duplicated across templates
  • Maintenance Burden - Changes to base infrastructure require updates in multiple places
  • Inconsistency - Templates drift apart over time
  • Build Complexity - Templates become difficult to build and test independently

We need a way to share common infrastructure while allowing specialized templates to add domain-specific functionality without modifying the base.

Decision Drivers

  • Need to avoid code duplication across templates
  • Requirement that templates remain buildable as normal .NET solutions
  • Need for base infrastructure changes to propagate automatically
  • Requirement for specialized templates to add domain logic without modifying base
  • Need to support both build-time development and generation-time composition

Considered Options

Option 1: Copy-Paste Base Code

Approach: Copy base template code into each specialized template repository.

Pros:

  • Simple to understand
  • Each template is self-contained

Cons:

  • Massive code duplication
  • Changes require updates in multiple places
  • Templates drift apart over time
  • Maintenance nightmare

Option 2: NuGet Package for Base Template

Approach: Package base template as NuGet package, specialized templates reference it.

Pros:

  • Base changes propagate via package updates
  • No code duplication

Cons:

  • Templates can't be built as normal solutions (NuGet packages don't contain source)
  • Difficult to develop and test base + specialized together
  • Version management complexity
  • Doesn't solve documentation reuse

Option 3: Git Submodules (Chosen)

Approach: Base template as git submodule in specialized template repositories.

Pros:

  • Base code shared without duplication
  • Templates buildable as normal .NET solutions
  • Base changes propagate via submodule updates
  • Supports both build-time and generation-time workflows
  • Documentation can reference base docs via submodule
  • Clear separation of concerns

Cons:

  • Requires understanding of git submodules
  • Submodule updates require explicit steps
  • Slightly more complex repository structure

Option 4: Template Inheritance / Composition at Generation Time Only

Approach: Base template + overlays composed only at generation time, not at build time.

Pros:

  • Clean separation at generation time
  • No build-time complexity

Cons:

  • Templates not buildable as normal solutions
  • Difficult to develop and test specialized templates
  • Poor developer experience

Decision Outcome

Chosen option: Option 3: Git Submodules, because it provides the best balance of:

  • Zero code duplication
  • Buildable templates (normal .NET solutions)
  • Automatic propagation of base changes
  • Support for both build-time development and generation-time composition
  • Clear separation between base infrastructure and domain-specific logic

Architecture

We adopt a three-layer model:

  1. Layer 1: Shared Libraries (ConnectSoft.Extensions.*)

    • Generic, cross-cutting infrastructure
    • Delivered as NuGet packages
    • Used by all templates and services
  2. Layer 2: Base Service Template (MicroserviceTemplate.Base)

    • Canonical microservice "kernel"
    • Solution layout, bootstrapping, common infrastructure
    • No domain-specific logic
    • Included as git submodule in specialized templates
  3. Layer 3: Specialized Templates (Identity, Auth, Audit, Worker, etc.)

    • Each template is its own repository
    • Includes base as git submodule (base-template/)
    • Adds domain-specific projects, tests, docs
    • Fully buildable as normal .NET solution

Build Time vs Generation Time

We distinguish two modes:

  • Build Time: Specialized template repo includes base as submodule. Solution includes projects from both. Developer works on real, concrete application.
  • Generation Time: Base template + overlays composed to produce final template artifact. Overlays are "pure" composition applied on top of base.

Overlays

At generation time, overlays are applied:

  • Add files (domain code, docs)
  • Patch existing files (Program.cs, pipelines)
  • Insert between markers in code
  • Token replacements
  • Merge template metadata

Overlays can be stacked: base → base + identity → base + identity + worker

Positive Consequences

  • Zero Duplication - Base code exists in one place only
  • Buildable Templates - All templates build as normal .NET solutions
  • Automatic Propagation - Base changes propagate to all templates via submodule updates
  • Clear Separation - Base is infrastructure-only, specialized templates add domain logic
  • Developer Experience - Developers work on real solutions, not abstract templates
  • Flexibility - Overlays enable recipe-based template composition
  • Maintainability - Changes to base infrastructure benefit all templates

Negative Consequences

  • Submodule Complexity - Team must understand git submodules
  • Submodule Updates - Requires explicit git submodule update steps
  • Repository Structure - Slightly more complex than single-repo approach
  • Learning Curve - New team members need to understand three-layer model

Implementation Notes

Base Template Structure

MicroserviceTemplate.Base/
├── src/
│   ├── Host/
│   ├── Domain/
│   ├── Application/
│   └── Infrastructure/
├── tests/
│   └── Base.Testing.Infrastructure/
├── docs/
│   ├── overview.md
│   └── architecture.md
└── template/
    └── template.json

Specialized Template Structure

IdentityBackendTemplate/
├── base-template/              # Git submodule
├── src/
│   ├── Identity.Api/
│   ├── Identity.Domain/
│   └── Identity.Infrastructure/
├── tests/
│   └── Identity.AcceptanceTests/
├── docs/
│   └── identity-*.md
└── template/
    └── identity.template.extend.json

Workflow

  1. Base Template Changes:

    • Update MicroserviceTemplate.Base repository
    • Commit and push changes
    • Update submodule reference in specialized templates
  2. Specialized Template Development:

    • Clone specialized template repository
    • Run git submodule update --init --recursive
    • Open solution, build, test as normal .NET solution
  3. Template Generation:

    • Load base template
    • Apply overlays (Identity, Worker, etc.)
    • Resolve tokens
    • Output final template artifact

Alternatives Considered

See "Considered Options" section above. We evaluated copy-paste, NuGet packages, and generation-time-only composition, but git submodules provided the best balance of benefits.