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





5.00/5 (1投票)
如何借助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);
}
}
在 Up
和 Down
方法中,您描述在应用和回滚迁移时要执行的操作。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日:初始版本