Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable FluentMigrator extensibility with new IRunnerFactory #287

Closed
wants to merge 7 commits into from
Closed

Conversation

Pathoschild
Copy link
Contributor

These changes let developers extend FluentMigrator in their application by creating an instance of IRunnerFactory, which constructs loader instances for FluentMigrator. If no factory is provided, FluentMigrator will automatically use the DefaultRunnerFactory which constructs the same objects as the current code does.

Example

We have a multitenant application which uses one schema per tenant, but FluentMigrator stores its version information once per database (in dbo.versioninfo). With the new code, we can easily override this default behaviour.

First, create a custom factory which overrides the behaviour we're interested in (the version loader). In this example, we use the application context to set the version table's schema.

    /// <summary>Constructs implementations of FluentMigrator interfaces for the FluentMigrator runner.</summary>
    public class CustomRunnerFactory : DefaultRunnerFactory
    {
        /// <summary>Get an object which reads and writes database versions to the database.</summary>
        /// <param name="runner">The migration runner used to create the version table.</param>
        /// <param name="assembly">The assembly to scan for migrations.</param>
        /// <param name="conventions">The default rules for migration mappings.</param>
        /// <param name="applicationContext">The arbitrary application context passed to the task runner.</param>
        public override IVersionLoader GetVersionLoader(IMigrationRunner runner, Assembly assembly, IMigrationConventions conventions, object applicationContext)
        {
            string schema = (applicationContext as IRepositoryMetadata).Schema;
            IVersionTableMetaData tableMetadata = new ExplicitVersionTableMetaData
            {
                SchemaName = schema,
                TableName = "repository.schemaversion",
                ColumnName = "version",
                UniqueIndexName = "uc_repository.schemaversion_version"
            };
            return new ExplicitVersionLoader(runner, assembly, conventions, tableMetadata);
        }
    }

Then just give the factory to the runner:

    IRunnerContext context = new RunnerContext(this.Announcer)
    {
        ...,
        Task = "migrate:up",
        ApplicationContext = this.Metadata,
        Factory = new CustomRunnerFactory()
    };
    new TaskExecutor(context).Execute();

And now FluentMigrator handles migrations on a per-schema basis. Whee!

Effects on current uses

This shouldn't affect any existing behaviour; by default the same objects are constructed, but they're done through the DefaultRunnerFactory instead of manually. We're successfully using the example above in our application, and all relevant unit tests pass. (A few tests fail due to missing dependencies like MySQL.)

The only significant change is that the VersionLoader now lazily initializes the version table, instead of doing it immediately in the constructor. This is needed to let custom implementations override the default behaviour in their own constructors. This shouldn't affect any existing behaviour, since the version information is accessed (and therefore initialized) early by the runners.

@tommarien
Copy link
Contributor

@Pathoschild What is difference between this and using a versionmetadata per namespace ? (not sure if the versionmetadata is supported allready per namespace)

@Pathoschild
Copy link
Contributor Author

Hi @tommarien. I didn't see any per-namespace version metadata; the schema name is hardcoded to an empty string. The current version loader scans one assembly for the first type matching the TypeIsVersionTableMetaData convention, and defaults to new DefaultVersionTableMetaData(). There's no way to set custom conventions nor to pass the application context to the table metadata.

The factory enables extensibility beyond this particular problem. FluentMigrator currently uses interfaces and Func everywhere, but hardcodes the implementations so we can't actually use this extensibility. For example, the factory would let us create custom conventions (IMigrationConventions), or create a custom migration loader that selects migrations based on tenant configuration (IMigrationLoader).

Conflicts:
	src/FluentMigrator.Runner/FluentMigrator.Runner.csproj
	src/FluentMigrator.Runner/Initialization/IRunnerContext.cs
	src/FluentMigrator.Runner/Initialization/RunnerContext.cs
	src/FluentMigrator.Runner/MigrationLoader.cs
	src/FluentMigrator.Runner/MigrationRunner.cs
	src/FluentMigrator.Tests/Unit/MigrationLoaderTests.cs
	src/FluentMigrator.Tests/Unit/MigrationRunnerTests.cs
	src/FluentMigrator/FluentMigrator.csproj
	src/FluentMigrator/Infrastructure/IMigrationContext.cs
	src/FluentMigrator/Infrastructure/MigrationContext.cs
	src/FluentMigrator/VersionTableInfo/DefaultVersionTableMetaData.cs
@daniellee
Copy link
Contributor

I'm looking at this PR again in connection to #290 and allowing users to set their own MigrationConventions. It's very hard for me to say if this extra layer of factories is needed or not. For the main use case of executing migrations it's not, so it depends on how much people want to customize MigrationRunner. I'm leaning towards the convention based approach of scanning the target assembly for a class that implements IMigrationConventions rather than using a factory.

Another option is allowing more dependency injection via the constructor which would mean a new constructor where you could pass in your own version of the dependencies (VersionLoader, ProfileLoader, MigrationInformationLoader etc.) To be honest this approach feels much more explicit than fetching dependencies from a factory via runner context in the constructor.

BTW, thanks for the pull request and sorry it's taken so long to process it.

What do you think, @tommarien?

@tommarien
Copy link
Contributor

@daniellee conventionel seems like the correct option if you like at the existing code base, for instance iversionmetadata etc. The extra constructor seems a little overcomplicated.

@daniellee
Copy link
Contributor

@tommarien The convention based approach does not help in the example in this PR. You won't be able to mix in something from the ApplicationContext as @Pathoschild does. So I can see the point of this. I suppose you could set the ApplicationContext on the MigrationConventions class to make it available and that way even users who are executing migrations via one of the runners would have access to it.

@tommarien
Copy link
Contributor

Closing to old pr's, sorry about this

@tommarien tommarien closed this Feb 27, 2015
fratle pushed a commit to fratle/fluentmigrator that referenced this pull request Mar 24, 2017
In order to support overloading the default VersionTableMetadata.

Enables runtime configuration of VersionTableMetadata
schema and version table can be dynamically configured.

related issues: fluentmigrator#488 fluentmigrator#287
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants