目录
在本文中,我将使用SQL Server作为我的数据库。一般来说,解决方案非常简单。所有微服务都将使用相同的数据库。但是,我们如何确定不会有冲突呢?我们将使用架构。每个微服务将仅在某个特定的数据库架构中创建数据库对象(表、视图、存储过程等),该架构在所有微服务中都是唯一的。为了避免访问其他微服务的数据时出现问题,我们将创建一个单独的登录名和用户,并仅授予他们一个架构的权限。
例如,对于微服务来处理订单,我们可以这样做:
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 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")
不幸的是,此代码在sdbo架构内也创建了order表。当然,我们可以显式指定模式:
Create.Table("orders").InSchema("orders")
但我宁愿避免这种情况。有人会忘记编写此架构,我们可能会遇到错误。我想替换整个微服务的默认架构。
为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();
一切都像我们预期的那样正常。
我希望这篇文章对您有用。很难理解如何更改数据库对象的默认架构。我希望我为你节省了一些时间。
https://www.codeproject.com/Articles/5334167/Single-Database-for-Multiple-Microservices-with