Skip to content

Commit

Permalink
Added docs around dynamically setting up tenants for multitenancy per…
Browse files Browse the repository at this point in the history
… database
  • Loading branch information
oskardudycz committed Jul 22, 2023
1 parent 1d37cd4 commit f210059
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 8 deletions.
2 changes: 1 addition & 1 deletion docs/configuration/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

::: warning
The usage of Marten.CommandLine shown in this document is only valid for applications bootstrapped with the
[generic host builder](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.1) with Marten registered in the application's IoC container.
[generic host builder](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host) with Marten registered in the application's IoC container.
:::

There is a separate NuGet package called _Marten.CommandLine_ that can be used to quickly add command-line tooling directly to
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration/hostbuilder.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The `AddMarten()` method will add these service registrations to your applicatio

For more information, see:

* [Dependency injection in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1) for more explanation about the service lifetime behavior.
* [Dependency injection in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) for more explanation about the service lifetime behavior.
* Check [identity map mechanics](/documents/identity) for an explanation of Marten session behavior
* Check [storing documents and unit of work](/documents/sessions) for session basics

Expand Down
26 changes: 22 additions & 4 deletions docs/configuration/multitenancy.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,31 @@ _host = await Host.CreateDefaultBuilder()
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/CoreTests/DatabaseMultiTenancy/using_per_database_multitenancy.cs#L83-L108' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_single_server_multi_tenancy' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Dynamically applying changes to tenants databases

If you didn't call the `ApplyAllDatabaseChangesOnStartup` method, Marten would still try to create a database [upon the session creation](/documents/sessions). This action is invasive and can cause issues like timeouts, cold starts, or deadlocks. It also won't apply all defined changes upfront (so, e.g. [indexes](/documents/indexing), [custom schema extensions](/schema/extensions)).

If you don't know the tenant upfront, you can create and apply changes dynamically by:

<!-- snippet: sample_manual_single_tenancy_apply_changes -->
<a id='snippet-sample_manual_single_tenancy_apply_changes'></a>
```cs
var tenant = await theStore.Tenancy.GetTenantAsync(tenantId);
await tenant.Database.ApplyAllConfiguredChangesToDatabaseAsync();
```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/CoreTests/adding_custom_schema_objects.cs#L151-L154' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_manual_single_tenancy_apply_changes' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

You can place this code somewhere in the tenant initialization code. For instance:

- tenant setup procedure,
- dedicated API endpoint
- [custom session factory](/configuration/hostbuilder/#customizing-session-creation-globally), although that's not recommended for the reasons mentioned above.

## Write your own tenancy strategy!

:::tip
It is strongly recommended that you first refer to the existing Marten options for per-database multi-tenancy
before you write your own model. There are several helpers in the Marten codebase that will hopefully make
this task easier. Failing all else, please feel free to ask questions in the [Marten's Discord channel](https://discord.gg/WMxrvegf8H) about custom
multi-tenancy strategies.
It is strongly recommended that you first refer to the existing Marten options for per-database multi-tenancy before you write your own model. There are several helpers in the Marten codebase that will hopefully make this task easier. Failing all else, please feel free to ask questions in the [Marten's Discord channel](https://discord.gg/WMxrvegf8H) about custom multi-tenancy strategies.
:::

The multi-tenancy strategy is pluggable. Start by implementing the `Marten.Storage.ITenancy` interface:
Expand Down
13 changes: 11 additions & 2 deletions docs/schema/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ Do note that when you use the `Add<T>()` syntax, Marten will pass along the curr

While you *can* directly implement the `ISchemaObject` interface for something Marten doesn't already support. Marten provides an even easier extensibility mechanism to add custom database objects such as Postgres tables, functions and sequences using `StorageFeatures.ExtendedSchemaObjects` using [Weasel](https://github.com/JasperFx/weasel).

::: warning
Marten will apply **Schema Feature Extensions** automatically when you call `ApplyAllConfiguredChangesToDatabaseAsync` for:

- single schema configuration,
- [multitenancy per database](/configuration/multitenancy) with tenants known upfront.

But it **won't apply them** for multitenancy per database with **unknown** tenants. If you cannot predict them, read the guidance on [dynamically applying changes to tenants databases](/configuration/multitenancy#dynamically-applying-changes-to-tenants-databases).
:::

## Table

Postgresql tables can be modeled with the `Table` class from `Weasel.Postgresql.Tables` as shown in this example below:
Expand Down Expand Up @@ -100,7 +109,7 @@ $f$ language sql immutable;

await theStore.Storage.ApplyAllConfiguredChangesToDatabaseAsync();
```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/CoreTests/adding_custom_schema_objects.cs#L188-L210' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_customschemafunction' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/CoreTests/adding_custom_schema_objects.cs#L190-L212' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_customschemafunction' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Sequence
Expand All @@ -122,7 +131,7 @@ StoreOptions(opts =>

await theStore.Storage.ApplyAllConfiguredChangesToDatabaseAsync();
```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/CoreTests/adding_custom_schema_objects.cs#L224-L238' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_customschemasequence' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/CoreTests/adding_custom_schema_objects.cs#L226-L240' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_customschemasequence' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Extension
Expand Down
2 changes: 2 additions & 0 deletions src/CoreTests/adding_custom_schema_objects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,10 @@ public async Task enable_an_extension_with_multitenancy_with_tenants_upfront_thr
opts.Storage.ExtendedSchemaObjects.Add(extension);
});

#region sample_manual_single_tenancy_apply_changes
var tenant = await theStore.Tenancy.GetTenantAsync(tenantId);
await tenant.Database.ApplyAllConfiguredChangesToDatabaseAsync();
#endregion

await using var sessionNext = theStore.QuerySession(tenantId);
var result = await sessionNext.QueryAsync<bool>("select unaccent('Æ') = 'AE';");
Expand Down

0 comments on commit f210059

Please sign in to comment.