Factory Extensions
Factory Extensions allow modules to participate in the Software Factory execution lifecycle by hooking into specific phases of code generation. They enable cross-cutting concerns like logging, file manipulation, validation, and custom processing.
FactoryExtensionBase Class
All factory extensions inherit from FactoryExtensionBase:
using Intent . Engine ;
using Intent . Plugins ;
public abstract class FactoryExtensionBase : IExecutionLifeCycle ,
IFactoryExtension ,
ISupportsConfiguration
{
public abstract string Id { get ; }
public virtual int Order { get ; set ; }
public virtual void Configure ( IDictionary < string , string > settings )
{
if ( settings . ContainsKey ( nameof ( Order )) &&
! string . IsNullOrWhiteSpace ( settings [ nameof ( Order )]))
{
Order = int . Parse ( settings [ nameof ( Order )]);
}
}
public virtual void OnStep ( IApplication application , string step ) { }
protected virtual void OnStart ( IApplication application ) { }
protected virtual void OnBeforeMetadataLoad ( IApplication application ) { }
protected virtual void OnAfterMetadataLoad ( IApplication application ) { }
protected virtual void OnBeforeTemplateRegistrations ( IApplication application ) { }
protected virtual void OnAfterTemplateRegistrations ( IApplication application ) { }
protected virtual void OnBeforeTemplateExecution ( IApplication application ) { }
protected virtual void OnAfterTemplateExecution ( IApplication application ) { }
protected virtual void OnBeforeCommitChanges ( IApplication application ) { }
protected virtual void OnAfterCommitChanges ( IApplication application ) { }
}
Lifecycle Phases
The Software Factory executes in distinct phases, each with a corresponding hook:
Start
OnStart() - First phase, before any metadata is loaded
Before Metadata Load
OnBeforeMetadataLoad() - Before loading designer files
After Metadata Load
OnAfterMetadataLoad() - Metadata models are available
Before Template Registrations
OnBeforeTemplateRegistrations() - Before templates are registered
After Template Registrations
OnAfterTemplateRegistrations() - All templates are registered and discoverable
Before Template Execution
OnBeforeTemplateExecution() - Before templates generate code
After Template Execution
OnAfterTemplateExecution() - All code has been generated
Before Commit Changes
OnBeforeCommitChanges() - Before files are written to disk
After Commit Changes
OnAfterCommitChanges() - Final phase, files have been written
Creating a Factory Extension
Basic Implementation
public class LoggingFactoryExtension : FactoryExtensionBase
{
public override string Id => "MyCompany.LoggingFactoryExtension" ;
protected override void OnStart ( IApplication application )
{
Logging . Log . Info ( "Software Factory execution started" );
}
protected override void OnAfterTemplateExecution ( IApplication application )
{
var templateCount = application . OutputTargets
. SelectMany ( x => x . Templates )
. Count ();
Logging . Log . Info ( $"Generated { templateCount } files" );
}
protected override void OnAfterCommitChanges ( IApplication application )
{
Logging . Log . Info ( "Software Factory execution completed" );
}
}
Real-World Examples
File Builder Factory Extension
This example from the source code shows how to work with file builders:
public class CSharpFileBuilderFactoryExtension : FactoryExtensionBase
{
public override string Id => "Intent.Common.CSharp.CSharpFileBuilderFactoryExtension" ;
public override int Order => - 100 ; // Execute early
protected override void OnAfterTemplateRegistrations ( IApplication application )
{
// Access all templates that implement ICSharpFileBuilderTemplate
var templates = application . OutputTargets
. SelectMany ( x => x . Templates )
. OfType < ICSharpFileBuilderTemplate >()
. ToList ();
foreach ( var template in templates )
{
// Configure file builder for each template
ConfigureCSharpFile ( template . CSharpFile , application );
}
}
private void ConfigureCSharpFile ( ICSharpFile file , IApplication application )
{
// Apply application-wide C# style settings
var settings = application . Settings . GetCSharpStyleSettings ();
file . ApplySettings ( settings );
}
}
Decorator Execution Hooks
From Intent.Common, this extension manages decorator lifecycle:
public class DecoratorExecutionHooksFactoryExtension : FactoryExtensionBase
{
public override string Id => "Intent.Common.DecoratorExecutionHooks" ;
protected override void OnBeforeTemplateExecution ( IApplication application )
{
var templatesWithDecorators = application . OutputTargets
. SelectMany ( x => x . Templates )
. OfType < IHasDecorators >()
. ToList ();
foreach ( var template in templatesWithDecorators )
{
var decorators = template . GetDecorators ()
. OfType < IDecoratorExecutionHooks >()
. ToList ();
foreach ( var decorator in decorators )
{
decorator . BeforeTemplateExecution ( template );
}
}
}
protected override void OnAfterTemplateExecution ( IApplication application )
{
var outputs = application . OutputTargets
. SelectMany ( x => x . Templates )
. OfType < IHasDecorators >()
. Select ( t => new { Template = t , Output = t . RunTemplate () })
. ToList ();
foreach ( var output in outputs )
{
var decorators = output . Template . GetDecorators ()
. OfType < IDecoratorExecutionHooks >()
. ToList ();
foreach ( var decorator in decorators )
{
decorator . AfterTemplateExecution ( output . Template , output . Output );
}
}
}
}
Accessing Application Services
The IApplication parameter provides access to:
application.MetadataManager
Access to all loaded metadata models
application.OutputTargets
IEnumerable<IOutputTarget>
All output targets (projects) and their templates
Application and module settings
application.EventDispatcher
ISoftwareFactoryEventDispatcher
Publish and subscribe to events
Working with Templates
protected override void OnAfterTemplateRegistrations ( IApplication application )
{
// Get all templates of a specific type
var serviceTemplates = application . OutputTargets
. SelectMany ( x => x . Templates )
. Where ( t => t . Id == "Intent.Application.ServiceTemplate" )
. ToList ();
// Access template metadata
foreach ( var template in serviceTemplates )
{
var fileConfig = template . GetTemplateFileConfig ();
Logging . Log . Info ( $"Template will generate: { fileConfig . FileName } " );
}
}
protected override void OnAfterMetadataLoad ( IApplication application )
{
// Access domain models
var domainModels = application . MetadataManager
. Domain ( application )
. GetDomainModels ();
// Validate metadata
foreach ( var model in domainModels )
{
if ( string . IsNullOrWhiteSpace ( model . Comment ))
{
Logging . Log . Warning ( $"Domain class ' { model . Name } ' has no documentation" );
}
}
}
Execution Order
Factory extensions execute in order determined by their Order property:
public class HighPriorityExtension : FactoryExtensionBase
{
public override string Id => "MyCompany.HighPriorityExtension" ;
public override int Order => - 100 ; // Executes early
}
public class LowPriorityExtension : FactoryExtensionBase
{
public override string Id => "MyCompany.LowPriorityExtension" ;
public override int Order => 100 ; // Executes late
}
Lower order values execute first. Default order is 0.
Configuration
Factory extensions can be configured in the module’s .imodspec file:
< factoryExtensions >
< factoryExtension id = "Intent.Common.CSharp.CSharpFileBuilderFactoryExtension" />
< factoryExtension id = "MyCompany.CustomExtension" externalReference = "guid-here" >
< config >
< add key = "Order" value = "50" />
< add key = "EnableLogging" value = "true" />
</ config >
</ factoryExtension >
</ factoryExtensions >
Access configuration in the extension:
public class CustomExtension : FactoryExtensionBase
{
private bool _enableLogging ;
public override string Id => "MyCompany.CustomExtension" ;
public override void Configure ( IDictionary < string , string > settings )
{
base . Configure ( settings );
if ( settings . ContainsKey ( "EnableLogging" ))
{
_enableLogging = bool . Parse ( settings [ "EnableLogging" ]);
}
}
protected override void OnStart ( IApplication application )
{
if ( _enableLogging )
{
Logging . Log . Info ( "Custom extension started" );
}
}
}
Common Use Cases
Validation
protected override void OnAfterMetadataLoad ( IApplication application )
{
var validator = new MetadataValidator ( application );
var errors = validator . Validate ();
if ( errors . Any ())
{
foreach ( var error in errors )
{
Logging . Log . Error ( error );
}
throw new Exception ( "Metadata validation failed" );
}
}
File Generation
protected override void OnAfterTemplateExecution ( IApplication application )
{
// Generate a summary file
var summary = new StringBuilder ();
summary . AppendLine ( "Generated Files:" );
foreach ( var target in application . OutputTargets )
{
foreach ( var template in target . Templates )
{
var config = template . GetTemplateFileConfig ();
summary . AppendLine ( $" - { config . FileName } " );
}
}
File . WriteAllText ( "generation-summary.txt" , summary . ToString ());
}
Post-Processing
protected override void OnAfterCommitChanges ( IApplication application )
{
// Run code formatters
var csharpFiles = Directory . GetFiles (
application . RootLocation ,
"*.cs" ,
SearchOption . AllDirectories );
foreach ( var file in csharpFiles )
{
FormatCSharpFile ( file );
}
}
Template Lifecycle Hooks Extension
Factory extensions can invoke template lifecycle hooks:
public class TemplateLifeCycleHooksFactoryExtension : FactoryExtensionBase
{
public override string Id => "Intent.Common.TemplateLifeCycleHooks" ;
protected override void OnAfterTemplateRegistrations ( IApplication application )
{
var templates = application . OutputTargets
. SelectMany ( x => x . Templates )
. OfType < IAfterTemplateRegistrationExecutionHook >()
. ToList ();
foreach ( var template in templates )
{
template . AfterTemplateRegistration ();
}
}
protected override void OnBeforeTemplateExecution ( IApplication application )
{
var templates = application . OutputTargets
. SelectMany ( x => x . Templates )
. OfType < ITemplateBeforeExecutionHook >()
. ToList ();
foreach ( var template in templates )
{
template . BeforeTemplateExecution ();
}
}
}
Best Practices
Use Appropriate Hooks Choose the right lifecycle phase for your operation
Set Order Carefully Consider dependencies when setting execution order
Handle Errors Wrap operations in try-catch to provide clear error messages
Log Activities Use logging to help users understand what’s happening
Factory extensions execute for every Software Factory run. Keep operations efficient, especially in frequently-called hooks like OnAfterTemplateExecution.
Optimization Tips
Cache expensive computations
Filter templates early to avoid unnecessary processing
Use parallel processing for independent operations
Avoid file I/O in early hooks when possible
protected override void OnAfterTemplateExecution ( IApplication application )
{
// Bad: Processes every template
foreach ( var template in application . OutputTargets . SelectMany ( x => x . Templates ))
{
// Expensive operation
}
// Good: Filter first
var relevantTemplates = application . OutputTargets
. SelectMany ( x => x . Templates )
. OfType < IRequiresProcessing >()
. ToList ();
foreach ( var template in relevantTemplates )
{
// Expensive operation only on relevant templates
}
}
Templates Work with templates in factory extensions
Modules Package factory extensions in modules
Decorators Coordinate with decorator execution