Skip to content

ADR-0002: 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.*)
  2. Generic, cross-cutting infrastructure
  3. Delivered as NuGet packages
  4. Used by all templates and services

  5. Layer 2: Base Service Template (MicroserviceTemplate.Base)

  6. Canonical microservice "kernel"
  7. Solution layout, bootstrapping, common infrastructure
  8. No domain-specific logic
  9. Included as git submodule in specialized templates

  10. Layer 3: Specialized Templates (Identity, Auth, Audit, Worker, etc.)

  11. Each template is its own repository
  12. Includes base as git submodule (base-template/)
  13. Adds domain-specific projects, tests, docs
  14. 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:
  2. Update MicroserviceTemplate.Base repository
  3. Commit and push changes
  4. Update submodule reference in specialized templates

  5. Specialized Template Development:

  6. Clone specialized template repository
  7. Run git submodule update --init --recursive
  8. Open solution, build, test as normal .NET solution

  9. Template Generation:

  10. Load base template
  11. Apply overlays (Identity, Worker, etc.)
  12. Resolve tokens
  13. 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.