/
using_static_database_multitenancy.cs
206 lines (160 loc) · 7.08 KB
/
using_static_database_multitenancy.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
using System.Linq;
using System.Threading.Tasks;
using JasperFx.Core.Reflection;
using Marten;
using Marten.Services;
using Marten.Storage;
using Marten.Testing.Documents;
using Marten.Testing.Harness;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Npgsql;
using Shouldly;
using Weasel.Postgresql;
using Weasel.Postgresql.Migrations;
namespace MultiTenancyTests;
[CollectionDefinition("multi-tenancy", DisableParallelization = true)]
public class using_static_database_multitenancy: IAsyncLifetime
{
private IHost _host;
private IDocumentStore theStore;
private async Task<string> CreateDatabaseIfNotExists(NpgsqlConnection conn, string databaseName)
{
var builder = new NpgsqlConnectionStringBuilder(ConnectionSource.ConnectionString);
var exists = await conn.DatabaseExists(databaseName);
if (!exists)
{
await new DatabaseSpecification().BuildDatabase(conn, databaseName);
}
builder.Database = databaseName;
return builder.ConnectionString;
}
public async Task InitializeAsync()
{
await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString);
await conn.OpenAsync();
var db1ConnectionString = await CreateDatabaseIfNotExists(conn, "database1");
var tenant3ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant3");
var tenant4ConnectionString = await CreateDatabaseIfNotExists(conn, "tenant4");
#region sample_using_multi_tenanted_databases
_host = await Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddMarten(opts =>
{
// Explicitly map tenant ids to database connection strings
opts.MultiTenantedDatabases(x =>
{
// Map multiple tenant ids to a single named database
x.AddMultipleTenantDatabase(db1ConnectionString, "database1")
.ForTenants("tenant1", "tenant2");
// Map a single tenant id to a database, which uses the tenant id as well for the database identifier
x.AddSingleTenantDatabase(tenant3ConnectionString, "tenant3");
x.AddSingleTenantDatabase(tenant4ConnectionString, "tenant4");
});
opts.RegisterDocumentType<User>();
opts.RegisterDocumentType<Target>();
})
// All detected changes will be applied to all
// the configured tenant databases on startup
.ApplyAllDatabaseChangesOnStartup();
}).StartAsync();
#endregion
theStore = _host.Services.GetRequiredService<IDocumentStore>();
}
public async Task DisposeAsync()
{
await _host.StopAsync();
theStore.Dispose();
}
[Fact]
public void default_tenant_usage_is_disabled()
{
theStore.Options.Advanced
.DefaultTenantUsageEnabled.ShouldBeFalse();
}
[Fact]
public async Task creates_databases_from_apply()
{
await using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString);
await conn.OpenAsync();
(await conn.DatabaseExists("database1")).ShouldBeTrue();
(await conn.DatabaseExists("tenant3")).ShouldBeTrue();
(await conn.DatabaseExists("tenant4")).ShouldBeTrue();
}
[Fact]
public async Task changes_are_applied_to_each_database()
{
using var store = _host.Services.GetRequiredService<IDocumentStore>().As<DocumentStore>();
var databases = await store.Tenancy.BuildDatabases();
foreach (IMartenDatabase database in databases)
{
await using var conn = database.CreateConnection();
await conn.OpenAsync();
var tables = await conn.ExistingTablesAsync();
tables.Any(x => x.QualifiedName == "public.mt_doc_user").ShouldBeTrue();
tables.Any(x => x.QualifiedName == "public.mt_doc_target").ShouldBeTrue();
}
}
[Fact]
public async Task can_open_a_session_to_a_different_database()
{
await using var session =
await theStore.LightweightSerializableSessionAsync(new SessionOptions { TenantId = "tenant1" });
session.Connection.Database.ShouldBe("database1");
}
[Fact]
public async Task can_use_bulk_inserts()
{
var targets3 = Target.GenerateRandomData(100).ToArray();
var targets4 = Target.GenerateRandomData(50).ToArray();
await theStore.Advanced.Clean.DeleteAllDocumentsAsync();
await theStore.BulkInsertDocumentsAsync("tenant3", targets3);
await theStore.BulkInsertDocumentsAsync("tenant4", targets4);
await using (var query3 = theStore.QuerySession("tenant3"))
{
var ids = await query3.Query<Target>().Select(x => x.Id).ToListAsync();
ids.OrderBy(x => x).ShouldHaveTheSameElementsAs(targets3.OrderBy(x => x.Id).Select(x => x.Id).ToList());
}
await using (var query4 = theStore.QuerySession("tenant4"))
{
var ids = await query4.Query<Target>().Select(x => x.Id).ToListAsync();
ids.OrderBy(x => x).ShouldHaveTheSameElementsAs(targets4.OrderBy(x => x.Id).Select(x => x.Id).ToList());
}
}
[Fact]
public async Task clean_crosses_the_tenanted_databases()
{
var targets3 = Target.GenerateRandomData(100).ToArray();
var targets4 = Target.GenerateRandomData(50).ToArray();
await theStore.BulkInsertDocumentsAsync("tenant3", targets3);
await theStore.BulkInsertDocumentsAsync("tenant4", targets4);
await theStore.Advanced.Clean.DeleteAllDocumentsAsync();
await using (var query3 = theStore.QuerySession("tenant3"))
{
(await query3.Query<Target>().AnyAsync()).ShouldBeFalse();
}
await using (var query4 = theStore.QuerySession("tenant4"))
{
(await query4.Query<Target>().AnyAsync()).ShouldBeFalse();
}
}
public static async Task administering_multiple_databases(IDocumentStore store)
{
#region sample_administering_multiple_databases
// Apply all detected changes in every known database
await store.Storage.ApplyAllConfiguredChangesToDatabaseAsync();
// Only apply to the default database if not using multi-tenancy per
// database
await store.Storage.Database.ApplyAllConfiguredChangesToDatabaseAsync();
// Find a specific database
var database = await store.Storage.FindOrCreateDatabase("tenant1");
// Tear down everything
await database.CompletelyRemoveAllAsync();
// Check out the projection state in just this database
var state = await database.FetchEventStoreStatistics();
// Apply all outstanding database changes in just this database
await database.ApplyAllConfiguredChangesToDatabaseAsync();
#endregion
}
}