Skip to main content

Overview

Template dependencies define relationships between templates, ensuring they execute in the correct order and enabling templates to reference each other. The Intent Architect Software Factory uses these dependencies to build an execution graph and resolve references during code generation.

Why Template Dependencies?

Template dependencies solve several key problems:
  • Execution Order - Ensure templates execute in the correct sequence
  • Cross-Template References - Enable templates to reference types/code from other templates
  • Type Resolution - Automatically resolve type names from dependent templates
  • Validation - Detect missing templates or circular dependencies early

ITemplateDependency Interface

public interface ITemplateDependency
{
    string? TemplateId { get; }
    bool IsMatch(ITemplate template);
    IOutputTarget? AccessibleTo { get; }
}
TemplateId
string?
The identifier of the template or role being depended upon. Can be null for type-based dependencies.
AccessibleTo
IOutputTarget?
Optional. If specified, the dependency must be accessible to this output target.

Creating Template Dependencies

The TemplateDependency class provides static factory methods for creating dependencies:

OnTemplate (by Template ID)

public static ITemplateDependency OnTemplate(string templateIdOrRole, IOutputTarget? accessibleTo = null)
Creates a dependency on a template by its ID or role.
templateIdOrRole
string
required
The unique template identifier or role name.
accessibleTo
IOutputTarget?
The output target that must be able to access the template.
Example:
AddTemplateDependency(
    TemplateDependency.OnTemplate("MyModule.EntityTemplate")
);

OnTemplate (by Template Instance)

public static ITemplateDependency OnTemplate(ITemplate template, IOutputTarget? accessibleTo = null)
Creates a dependency on a specific template instance.
template
ITemplate
required
The template instance to depend on.
Example:
var entityTemplate = GetTemplate<IEntityTemplate>("MyModule.EntityTemplate", Model);
AddTemplateDependency(
    TemplateDependency.OnTemplate(entityTemplate)
);

OnModel (by Template ID and Model)

public static ITemplateDependency OnModel(
    string templateIdOrName, 
    IMetadataModel metadataModel, 
    IOutputTarget? accessibleTo = null)
Creates a dependency on a template bound to a specific model.
templateIdOrName
string
required
The template identifier.
metadataModel
IMetadataModel
required
The model instance the template must be bound to.
Example:
AddTemplateDependency(
    TemplateDependency.OnModel("MyModule.EntityTemplate", Model)
);

OnModel (with Predicate)

public static ITemplateDependency OnModel<TModel>(
    string templateIdOrName, 
    Func<TModel, bool> isMatch, 
    IOutputTarget? accessibleTo = null)
Creates a dependency on templates where the model matches a predicate.
templateIdOrName
string
required
The template identifier.
isMatch
Func<TModel, bool>
required
Predicate to match template models.
Example:
AddTemplateDependency(
    TemplateDependency.OnModel<ClassModel>(
        "MyModule.EntityTemplate",
        model => model.Name.EndsWith("Aggregate")
    )
);

OfType (Type-Based Dependency)

public static ITemplateDependency OfType<TTemplate>(IOutputTarget? accessibleTo = null)
Creates a dependency on all templates of a specific type.
TTemplate
Type
required
The template type to depend on.
Example:
AddTemplateDependency(
    TemplateDependency.OfType<IEntityTemplate>()
);

Adding Dependencies

In Template Constructor

Add dependencies in the template constructor to ensure they’re registered early:
public class RepositoryTemplate : IntentTemplateBase<ClassModel>
{
    public const string TemplateId = "MyModule.RepositoryTemplate";

    public RepositoryTemplate(IOutputTarget outputTarget, ClassModel model)
        : base(TemplateId, outputTarget, model)
    {
        // Add dependency on entity template for the same model
        AddTemplateDependency(
            TemplateDependency.OnModel("MyModule.EntityTemplate", model)
        );
    }
}

Using AddTemplateDependency

// Add by template ID
AddTemplateDependency("MyModule.EntityTemplate");

// Add by template ID and model
AddTemplateDependency("MyModule.EntityTemplate", Model);

// Add using ITemplateDependency
AddTemplateDependency(
    TemplateDependency.OnModel("MyModule.EntityTemplate", Model)
);

Implicit Dependencies via Type Sources

Adding type sources automatically creates dependencies:
public RepositoryTemplate(IOutputTarget outputTarget, ClassModel model)
    : base(TemplateId, outputTarget, model)
{
    // Automatically adds dependencies when types are resolved
    AddTypeSource("MyModule.EntityTemplate");
}
When GetTypeName() resolves a type from this source, the dependency is automatically tracked.

Retrieving Dependencies

GetTemplateDependencies

public virtual IEnumerable<ITemplateDependency> GetTemplateDependencies()
Returns all dependencies registered for this template. Example:
public override void AfterTemplateRegistration()
{
    var dependencies = GetTemplateDependencies();
    foreach (var dependency in dependencies)
    {
        Logging.Log.Info($"Dependency: {dependency.TemplateId}");
    }
}

Dependency Resolution and Execution Order

The Software Factory builds a dependency graph and executes templates in topological order:
  1. Templates with no dependencies execute first
  2. Templates with dependencies execute after their dependencies
  3. Circular dependencies are detected and cause errors

Example Execution Order

EntityTemplate (no dependencies)

RepositoryInterface (depends on Entity)

RepositoryImplementation (depends on Entity and RepositoryInterface)

DependencyInjection (depends on all repositories)

Cross-Output-Target Dependencies

Templates can depend on templates in different output targets (e.g., different projects):
public class DtoTemplate : IntentTemplateBase<ClassModel>
{
    public DtoTemplate(IOutputTarget outputTarget, ClassModel model)
        : base(TemplateId, outputTarget, model)
    {
        // Depend on entity in domain project from contracts project
        AddTemplateDependency(
            TemplateDependency.OnModel(
                "MyModule.EntityTemplate",
                model,
                accessibleTo: outputTarget
            )
        );
    }
}

Fast Lookup Dependencies

The framework optimizes dependency resolution using IFastLookupTemplateDependency. Dependencies created via TemplateDependency factory methods automatically use this optimization.

Benefits

  • Caching - Template lookups are cached
  • Performance - Reduces search time for large template sets
  • Automatic - No additional code required

Common Patterns

Entity and Repository Pattern

public class RepositoryTemplate : IntentTemplateBase<ClassModel>
{
    private readonly ITemplate _entityTemplate;

    public RepositoryTemplate(IOutputTarget outputTarget, ClassModel model)
        : base(TemplateId, outputTarget, model)
    {
        AddTypeSource("MyModule.EntityTemplate");
    }

    public override string TransformText()
    {
        var entityType = GetTypeName(Model);
        
        return $@"
public interface I{Model.Name}Repository
{{
    {entityType} GetById(int id);
    void Add({entityType} entity);
}}";
    }
}

Service and DTO Pattern

public class ServiceTemplate : IntentTemplateBase<ServiceModel>
{
    public ServiceTemplate(IOutputTarget outputTarget, ServiceModel model)
        : base(TemplateId, outputTarget, model)
    {
        // Add dependencies on all DTOs used by this service
        foreach (var operation in model.Operations)
        {
            if (operation.ReturnType?.Element != null)
            {
                AddTemplateDependency(
                    TemplateDependency.OnModel(
                        "MyModule.DtoTemplate",
                        operation.ReturnType.Element
                    )
                );
            }
        }

        AddTypeSource("MyModule.DtoTemplate");
    }
}

Aggregate Root Dependencies

public class AggregateTemplate : IntentTemplateBase<ClassModel>
{
    public AggregateTemplate(IOutputTarget outputTarget, ClassModel model)
        : base(TemplateId, outputTarget, model)
    {
        // Add dependencies on all value objects
        foreach (var property in model.Properties)
        {
            if (property.TypeReference?.Element?.HasStereotype("Value Object") == true)
            {
                AddTemplateDependency(
                    TemplateDependency.OnModel(
                        "MyModule.ValueObjectTemplate",
                        property.TypeReference.Element
                    )
                );
            }
        }

        AddTypeSource("MyModule.ValueObjectTemplate");
    }
}

Troubleshooting

Circular Dependency Error

Problem: Two templates depend on each other, creating a cycle. Solution: Refactor to remove the circular dependency, often by:
  • Using interfaces
  • Creating a third template both can depend on
  • Using late binding (string references instead of type resolution)

Template Not Found

Problem: Dependency references a template that doesn’t exist. Solution:
  • Verify the template ID is correct
  • Ensure the module containing the template is installed
  • Check if the template should be in a different output target

Wrong Execution Order

Problem: Template executes before its dependencies are ready. Solution:
  • Add explicit dependencies using AddTemplateDependency()
  • Ensure type sources are configured before resolving types
  • Check that dependencies are added in the constructor, not later lifecycle hooks

Best Practices

Add Dependencies Early

Add dependencies in the template constructor to ensure they’re registered before execution planning.

Use Type Sources

Use AddTypeSource() for type resolution - it automatically manages dependencies.

Specify Accessibility

Use the accessibleTo parameter for cross-output-target dependencies.

Avoid Circular Dependencies

Design template relationships to avoid circular dependencies.

Use Model-Based Dependencies

Prefer OnModel() over OnTemplate() when templates are model-bound.

IntentTemplateBase

Base class providing dependency management methods

Type Resolution

Using type sources and GetTypeName with dependencies

Output Target

Understanding output target accessibility

Template Registration

How templates are instantiated and registered

Build docs developers (and LLMs) love