65.9K
CodeProject 正在变化。 阅读更多。
Home

使用FluentMigrator为多个微服务提供单个数据库

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2022年6月3日

CPOL

3分钟阅读

viewsIcon

7856

如何借助FluentMigrator使用单个数据库存储多个微服务的信息

如果您有多个微服务,通常的做法是为每个微服务使用单独的数据库。但最近,我们面临以下问题:我们的数据库托管提供商的价格计划只包含有限数量的数据库。我们无法为每个微服务创建一个新数据库,因为成本太高。我们该如何解决这个问题?

高级方法

在本文中,我将使用 SQL Server 作为我的数据库。一般来说,解决方案非常简单。所有微服务将使用同一个数据库。但是,我们如何确保不会发生冲突?我们将使用模式(Schemas)。每个微服务将仅在某个特定的数据库模式中创建数据库对象(表、视图、存储过程等),该模式在所有微服务中都是唯一的。为了避免访问其他微服务数据的问题,我们将创建一个单独的登录名和用户,并仅授予他们一个模式的权限。

例如,对于处理订单的微服务,我们可以这样做:

CREATE LOGIN [orders_login] WITH PASSWORD='p@ssw0rd'

execute('CREATE SCHEMA [orders]')

CREATE USER [orders_user] FOR LOGIN [orders_login] WITH DEFAULT_SCHEMA=[orders]

GRANT CREATE TABLE to [orders_user]
GRANT ALTER,DELETE,SELECT,UPDATE,INSERT,REFERENCES ON SCHEMA :: [orders] to [orders_user]

现在我们准备好创建数据库对象了。

FluentMigrator

我将使用 FluentMigrator NuGet 包来修改数据库的结构。它非常容易使用。首先配置它:

var serviceProvider = new ServiceCollection()
    .AddFluentMigratorCore()
    .ConfigureRunner(
        builder =>
        {
            builder
                .AddSqlServer2016()
                .WithGlobalConnectionString(connectionString)
                .ScanIn(typeof(Database).Assembly).For.Migrations();
        })
    .BuildServiceProvider();

这里,我们使用 SQL Server 2016 或更高版本。connectionString 变量包含我们数据库的连接字符串。Database 类型可以是包含迁移的程序集中的任何类型。等等!什么是迁移?

这就是我们描述要对数据库进行的更改的方式。每个迁移都是一个简单的类,它继承自 Migration

[Migration(1)]
public class FirstMigration : Migration
{
    public const string TableName = "orders";   

    public override void Up()
    {
        Create.Table(TableName)
            .WithColumn("id").AsInt32().PrimaryKey().Identity()
            .WithColumn("code").AsString(100).NotNullable();
    }

    public override void Down()
    {
        Delete.Table(TableName);
    }
}

UpDown 方法中,您描述在应用和回滚迁移时要执行的操作。Migration 属性包含一个数字,该数字指定迁移的应用顺序。

现在,将您的迁移应用到数据库非常简单:

var runner = serviceProvider.GetRequiredService<IMigrationRunner>();

runner.MigrateUp();

就这样。现在所有迁移都应该已应用到数据库。FluentMigrator 还会创建一个 VersionInfo 表,其中包含有关所有当前已应用迁移的信息。借助此表,FluentMigrator 下次将知道应该向数据库额外应用哪些迁移。

不幸的是,对于我们的用例,它并非如此。存在两个问题。

首先,VersionInfo 表默认在 dbo 模式中创建。但这对我们来说是不可接受的。每个微服务都必须在其自己的模式内拥有自己的 VersionInfo 表。

第二个问题如下:考虑以下迁移代码:

Create.Table("orders")

不幸的是,此代码也会在 dbo 模式中创建表 orders。当然,我们可以显式指定模式:

Create.Table("orders").InSchema("orders")

但我更喜欢避免这种情况。有人会忘记编写此模式,并且我们可能会遇到错误。我想为整个微服务替换默认模式。

VersionInfo 表的模式

VersionInfo 表设置自定义模式非常容易:

var serviceProvider = new ServiceCollection()
    .AddSingleton<IConventionSet>(new DefaultConventionSet("orders", null))
    .AddFluentMigratorCore()
    .ConfigureRunner(
        builder =>
        {
            builder
                .AddSqlServer2016()
                .WithGlobalConnectionString(connectionString)
                .ScanIn(typeof(Database).Assembly).For.Migrations();
        })
    .BuildServiceProvider();

在这里,我们只是为具有相应模式的 IConventionSet 接口注册了一个新的 DefaultConventionSet 类实例。现在我们的 VersionInfo 表将在 orders 模式中创建。

数据库对象的默认模式

不幸的是,要理解如何替换其他数据库对象的默认模式并不容易。我花了一些时间。让我们从 AddSqlServer2016 方法的 代码 开始。它注册了 SqlServer2008Quoter 类的实例。此类从 SqlServer2005Quoter 类继承 QuoteSchemaName 方法。在这里,您可以看到默认模式的来源。

我们将用我们自己的类替换这个 Quoter 类:

sealed class Quoter : SqlServer2008Quoter
{
    private readonly string _defaultSchemaName;

    public Quoter(string defaultSchemaName)
    {
        if (string.IsNullOrWhiteSpace(defaultSchemaName))
            throw new ArgumentException("Value cannot be null or whitespace.", 
                                         nameof(defaultSchemaName));
        _defaultSchemaName = defaultSchemaName;
    }           

    public override string QuoteSchemaName(string schemaName)
    {
        if (string.IsNullOrEmpty(schemaName))
            return $"[{_defaultSchemaName}]";
        return base.QuoteSchemaName(schemaName);
    }
}

如您所见,它非常简单。实现几乎与 SqlServer2005Quoter 类相同,但我们使用自定义模式而不是 dbo

现在我们只需要注册这个类:

var serviceProvider = new ServiceCollection()
    .AddSingleton<IConventionSet>(new DefaultConventionSet("orders", null))
    .AddFluentMigratorCore()
    .ConfigureRunner(
        builder =>
        {
            builder
                .AddSqlServer2016()
                .WithGlobalConnectionString(connectionString)
                .ScanIn(typeof(Database).Assembly).For.Migrations();

            builder.Services.RemoveAll<SqlServer2008Quoter>()
                .AddSingleton<SqlServer2008Quoter>(new Quoter("orders"));
        })
    .BuildServiceProvider();

一切都按我们预期的方式正常工作。

结论

我希望本文对您有所帮助。令人惊讶的是,很难理解如何更改数据库对象的默认模式。我希望我为您节省了一些时间。祝你好运!

如果您喜欢我的文章,可以在我的 博客 中阅读更多内容。

历史

  • 2022年6月3日:初始版本
© . All rights reserved.