Skip to main content

Metadata System

The metadata system in Intent Architect represents the models created in designers (like Domain Designer, Services Designer, etc.). Templates and decorators query this metadata to generate appropriate code.

IMetadataModel Interface

All metadata models implement the IMetadataModel interface:
public interface IMetadataModel
{
    string Id { get; }
    string Name { get; }
    string Comment { get; }
    IEnumerable<IStereotype> Stereotypes { get; }
    bool HasStereotype(string stereotypeName);
    IStereotype GetStereotype(string stereotypeName);
}
Id
string
Unique identifier for the model element (GUID)
Name
string
The name of the model element as shown in the designer
Comment
string
Documentation/comments added to the element
Stereotypes
IEnumerable<IStereotype>
Collection of stereotypes applied to this element

Stereotypes

Stereotypes extend metadata models with additional properties and configuration. They’re similar to attributes/annotations in programming languages.

Checking for Stereotypes

public class EntityTemplate : IntentTemplateBase<ClassModel>
{
    public override string TransformText()
    {
        var isPersistent = Model.HasStereotype("Persistent");
        var isAuditable = Model.HasStereotype("Auditable");
        
        if (isPersistent)
        {
            // Generate persistence code
        }
        
        if (isAuditable)
        {
            // Generate audit fields
        }
        
        return GenerateClass();
    }
}

Accessing Stereotype Properties

public interface IStereotype
{
    string Name { get; }
    string GetProperty(string propertyName);
    T GetProperty<T>(string propertyName);
    bool TryGetProperty(string propertyName, out string value);
}

Example: Table Name Override

public string GetTableName()
{
    if (Model.HasStereotype("Table"))
    {
        var tableStereotype = Model.GetStereotype("Table");
        var tableName = tableStereotype.GetProperty<string>("Name");
        
        if (!string.IsNullOrWhiteSpace(tableName))
        {
            return tableName;
        }
    }
    
    // Default to class name
    return Model.Name;
}

Extension Methods for Stereotypes

Intent Architect generates strongly-typed extension methods for stereotypes:
// Generated extension methods
public static class StereotypeExtensions
{
    public static bool HasAuditableStereotype(this IMetadataModel model)
    {
        return model.HasStereotype("Auditable");
    }

    public static AuditableSettings GetAuditableSettings(this IMetadataModel model)
    {
        var stereotype = model.GetStereotype("Auditable");
        return stereotype != null ? new AuditableSettings(stereotype) : null;
    }
}

public class AuditableSettings
{
    private readonly IStereotype _stereotype;

    public AuditableSettings(IStereotype stereotype)
    {
        _stereotype = stereotype;
    }

    public bool TrackCreated => _stereotype.GetProperty<bool>("Track Created");
    public bool TrackModified => _stereotype.GetProperty<bool>("Track Modified");
    public bool TrackDeleted => _stereotype.GetProperty<bool>("Track Deleted");
}
Usage in templates:
if (Model.HasAuditableStereotype())
{
    var settings = Model.GetAuditableSettings();
    
    if (settings.TrackCreated)
    {
        // Add CreatedDate, CreatedBy properties
    }
    
    if (settings.TrackModified)
    {
        // Add ModifiedDate, ModifiedBy properties
    }
}

Type References

Metadata models use ITypeReference to represent type information:
public interface ITypeReference
{
    IElement Element { get; }
    bool IsNullable { get; }
    bool IsCollection { get; }
    IEnumerable<ITypeReference> GenericTypeParameters { get; }
}

Working with Type References

public class PropertyTemplate : IntentTemplateBase<PropertyModel>
{
    public string GetPropertyType()
    {
        // Use template's type resolution system
        var typeName = GetTypeName(Model.TypeReference);
        
        // Type resolution handles:
        // - Finding the correct template that outputs this type
        // - Adding it as a dependency
        // - Applying collection formatters (List<T>, IEnumerable<T>, etc.)
        // - Applying nullable formatters (T?, Nullable<T>)
        
        return typeName;
    }
}

Querying Metadata

From Templates

Templates access metadata through their Model property:
public class ServiceTemplate : IntentTemplateBase<ServiceModel>
{
    public string GetServiceMethods()
    {
        var methods = new StringBuilder();
        
        foreach (var operation in Model.Operations)
        {
            var returnType = GetTypeName(operation.ReturnType);
            var parameters = string.Join(", ", 
                operation.Parameters.Select(p => $"{GetTypeName(p.TypeReference)} {p.Name}"));
            
            methods.AppendLine($"{returnType} {operation.Name}({parameters});";
        }
        
        return methods.ToString();
    }
}

From Factory Extensions

Factory extensions access metadata through IApplication.MetadataManager:
protected override void OnAfterMetadataLoad(IApplication application)
{
    // Get all domain classes
    var domainClasses = application.MetadataManager
        .Domain(application)
        .GetClassModels();
    
    foreach (var domainClass in domainClasses)
    {
        ValidateDomainClass(domainClass);
    }
}

private void ValidateDomainClass(ClassModel model)
{
    // Check for required stereotypes
    if (model.Attributes.Any() && !model.HasStereotype("Table"))
    {
        Logging.Log.Warning($"Class '{model.Name}' has properties but no Table stereotype");
    }
    
    // Validate properties
    foreach (var attribute in model.Attributes)
    {
        if (attribute.TypeReference.IsCollection && attribute.Name.EndsWith("Id"))
        {
            Logging.Log.Warning($"Property '{model.Name}.{attribute.Name}' is a collection but has 'Id' suffix");
        }
    }
}

Metadata Associations

Elements can be associated with each other:
public class ClassModel : IMetadataModel
{
    public ClassModel BaseType { get; }
    public IEnumerable<ClassModel> Interfaces { get; }
    public IEnumerable<AttributeModel> Attributes { get; }
    public IEnumerable<AssociationModel> AssociatedClasses { get; }
}

public class AssociationModel : IMetadataModel
{
    public ClassModel TargetClass { get; }
    public string Multiplicity { get; } // "1", "*", "0..1", etc.
    public bool IsNullable { get; }
    public bool IsCollection { get; }
}
public string GetNavigationProperties()
{
    var properties = new StringBuilder();
    
    foreach (var association in Model.AssociatedClasses)
    {
        var targetType = GetTypeName(association.TargetClass);
        var propertyName = association.Name;
        
        if (association.IsCollection)
        {
            properties.AppendLine($"public ICollection<{targetType}> {propertyName} {{ get; set; }}");
        }
        else if (association.IsNullable)
        {
            properties.AppendLine($"public {targetType}? {propertyName} {{ get; set; }}");
        }
        else
        {
            properties.AppendLine($"public {targetType} {propertyName} {{ get; set; }}");
        }
    }
    
    return properties.ToString();
}

Metadata Packages

Designers can define reusable metadata packages containing stereotypes and settings:
<?xml version="1.0" encoding="utf-8"?>
<package>
  <id>Intent.Metadata.RDBMS</id>
  <version>1.0.0</version>
  <stereotypes>
    <stereotype name="Table" appliesTo="Class">
      <property name="Name" type="string" />
      <property name="Schema" type="string" />
    </stereotype>
    <stereotype name="Primary Key" appliesTo="Attribute">
      <property name="Identity" type="boolean" default="true" />
    </stereotype>
    <stereotype name="Index" appliesTo="Attribute">
      <property name="Unique" type="boolean" default="false" />
    </stereotype>
  </stereotypes>
</package>

Common Metadata Patterns

Conditional Code Generation

if (Model.HasStereotype("Entity"))
{
    // Generate entity-specific code
}

if (Model.HasStereotype("Value Object"))
{
    // Generate value object-specific code
}
public IEnumerable<ClassModel> GetInheritanceHierarchy()
{
    var hierarchy = new List<ClassModel> { Model };
    var current = Model.BaseType;
    
    while (current != null)
    {
        hierarchy.Add(current);
        current = current.BaseType;
    }
    
    return hierarchy;
}

Aggregating Metadata

public IEnumerable<AttributeModel> GetAllAttributes()
{
    // Include attributes from base classes
    return GetInheritanceHierarchy()
        .SelectMany(c => c.Attributes)
        .ToList();
}

Tags vs Stereotypes

Stereotypes

Structured metadata with properties. Defined in metadata packages. Strongly typed.

Tags

Simple string markers. Ad-hoc categorization. Lightweight.

Using Tags

public interface IHasTags
{
    IEnumerable<string> Tags { get; }
    bool HasTag(string tag);
}

// Example usage
if (Model.HasTag("Deprecated"))
{
    // Add obsolete attribute
}

if (Model.HasTag("Internal"))
{
    // Make class internal instead of public
}

Metadata Model Providers

Modules can provide typed access to metadata:
public static class MetadataManagerExtensions
{
    public static IDomainMetadata Domain(this IMetadataManager manager, IApplication application)
    {
        return new DomainMetadata(manager, application);
    }
}

public interface IDomainMetadata
{
    IEnumerable<ClassModel> GetClassModels();
    IEnumerable<EnumModel> GetEnumModels();
    ClassModel GetClassModel(string id);
}
Usage:
var domainClasses = application.MetadataManager
    .Domain(application)
    .GetClassModels();

Best Practices

Use Extension Methods

Generate strongly-typed stereotype accessors

Validate Metadata

Use factory extensions to validate models after load

Document Stereotypes

Provide clear hints in stereotype definitions

Type Resolution

Always use GetTypeName() for type references

Templates

Use metadata in templates

Designers

Configure designers and metadata

Decorators

Query metadata for conditional decoration

Build docs developers (and LLMs) love