Skip to content

Commit

Permalink
Added the new TenantId as a convenience. Closes GH-1097
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremydmiller committed Jan 10, 2025
1 parent ff73eae commit 23a18a8
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 169 deletions.
58 changes: 58 additions & 0 deletions docs/guide/handlers/multi-tenancy.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,61 @@ public static IEnumerable<object> Handle(IncomingMessage message)
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/using_group_ids.cs#L32-L51' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_tenant_id_and_cascading_messages' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Referencing the TenantId <Badge type="tip" text="3.6" />

Let's say that you want to reference the current tenant id in your Wolverine message handler or Wolverine HTTP endpoint,
but you don't want to inject the Wolverine `IMessageContext` or `Envelope` into your methods, but instead would like
an easy way to just "push" the current tenant id into your handler methods. Maybe this is for ease of writing unit tests,
or conditional logic, or some other reason.

To that end, you can inject the `Wolverine.Persistence.TenantId` into any Wolverine message handler or HTTP endpoint method
to get easy access to the tenant id:

<!-- snippet: sample_TenantId -->
<a id='snippet-sample_tenantid'></a>
```cs
/// <summary>
/// Strong typed identifier for the tenant id within a Wolverine message handler
/// or HTTP endpoint that is using multi-tenancy
/// </summary>
/// <param name="Value">The active tenant id. Note that this can be null</param>
public record TenantId(string Value)
{
public const string DefaultTenantId = "*DEFAULT*";

/// <summary>
/// Is there a non-default tenant id?
/// </summary>
/// <returns></returns>
public bool IsEmpty() => Value.IsEmpty() || Value == DefaultTenantId;
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Wolverine/Persistence/TenantId.cs#L9-L27' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_tenantid' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

There's really nothing to it other than just pulling that type in as a parameter argument to a message handler:

<!-- snippet: sample_injecting_tenant_id -->
<a id='snippet-sample_injecting_tenant_id'></a>
```cs
public static class SomeCommandHandler
{
// Wolverine is keying off the type, the parameter name
// doesn't really matter
public static void Handle(SomeCommand command, TenantId tenantId)
{
Debug.WriteLine($"I got a command {command} for tenant {tenantId.Value}");
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/CoreTests/Acceptance/multi_tenancy.cs#L108-L120' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_injecting_tenant_id' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

In tests, you can create that `TenantId` value just by:

```csharp
var tenantId = new TenantId("tenant1");
```

and then just pass the value into the method under test.
8 changes: 6 additions & 2 deletions docs/guide/http/multi-tenancy.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ public class GET_todoitems_tenant : Wolverine.Http.HttpHandler
}
```

## Referencing the Tenant Id in Endpoint Methods

See [Referencing the TenantId](/guide/handlers/multi-tenancy.html#referencing-the-tenantid) on using Wolverine's `TenantId` type.

## Requiring Tenant Id -- or Not!

You can direct Wolverine.HTTP to verify that there is a non-null, non-empty tenant id on all requests with this syntax:
Expand Down Expand Up @@ -265,7 +269,7 @@ public static string NoTenantNoProblem()
return "hey";
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/Wolverine.Http.Tests/multi_tenancy_detection_and_integration.cs#L430-L439' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_nottenanted' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/Wolverine.Http.Tests/multi_tenancy_detection_and_integration.cs#L440-L449' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_nottenanted' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

If the above usage completely disabled all tenant id detection or validation, in the case of an endpoint that *might* be
Expand All @@ -283,7 +287,7 @@ public static string MaybeTenanted(IMessageBus bus)
return bus.TenantId ?? "none";
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/Wolverine.Http.Tests/multi_tenancy_detection_and_integration.cs#L441-L450' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_maybe_tenanted_attribute_usage' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/Wolverine.Http.Tests/multi_tenancy_detection_and_integration.cs#L451-L460' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_maybe_tenanted_attribute_usage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Wolverine.Http.Runtime.MultiTenancy;
using Wolverine.Http.Tests.Bugs;
using Wolverine.Marten;
using Wolverine.Persistence;
using Xunit.Abstractions;

namespace Wolverine.Http.Tests;
Expand Down Expand Up @@ -49,7 +50,11 @@ protected async Task configure(Action<WolverineHttpOptions> configure)
// Haven't gotten around to it yet, but there'll be some end to
// end tests in a bit from the ASP.Net request all the way down
// to the underlying tenant databases
builder.Services.AddMarten(Servers.PostgresConnectionString)
builder.Services.AddMarten(m =>
{
m.Connection(Servers.PostgresConnectionString);
m.DisableNpgsqlLogging = true;
})
.IntegrateWithWolverine();

// Defaults are good enough here
Expand Down Expand Up @@ -376,31 +381,34 @@ public static class TenantedEndpoints
{
[Authorize]
[WolverineGet("/tenant/route/{tenant}")]
public static string GetTenantIdFromRoute(IMessageBus bus)
public static string GetTenantIdFromRoute(IMessageBus bus, TenantId tenantId)
{
tenantId.Value.ShouldBe(bus.TenantId);
return bus.TenantId;
}

[Authorize]
[WolverineGet("/tenant")]
public static string GetTenantIdFromWhatever(IMessageBus bus, HttpContext httpContext)
public static string GetTenantIdFromWhatever(IMessageBus bus, HttpContext httpContext, TenantId tenantId)
{
// IHttpActivityFeature.Activity is set to null after the request, so to access the
// Activity in the test we capture the Activity into a custom Feature
httpContext.Features.Set(CustomActivityFeature.FromHttpContext(httpContext));

tenantId.Value.ShouldBe(bus.TenantId);
return bus.TenantId;
}

[WolverineGet("/todo/{id}")]
public static Task<TenantTodo?> Get(string id, IQuerySession session)
public static Task<TenantTodo?> Get(string id, IQuerySession session, TenantId tenantId)
{
tenantId.Value.ShouldBe(session.TenantId);
return session.LoadAsync<TenantTodo>(id);
}

[WolverinePost("/todo/create")]
public static IMartenOp Create(CreateTodo command)
public static IMartenOp Create(CreateTodo command, TenantId tenantId)
{
tenantId.IsEmpty().ShouldBeFalse();
return MartenOps.Insert(new TenantTodo
{
Id = command.Id,
Expand All @@ -409,19 +417,21 @@ public static IMartenOp Create(CreateTodo command)
}

[WolverineGet("/tenant/bus/{tenant}")]
public static string GetTenantWithArgs1(IMessageBus bus)
public static string GetTenantWithArgs1(IMessageBus bus, TenantId tenantId)
{
tenantId.Value.ShouldBe(bus.TenantId);
return bus.TenantId;
}

[WolverineGet("/tenant/context/{tenant}")]
public static string GetTenantWithArgs1(IMessageContext context)
public static string GetTenantWithArgs1(IMessageContext context, TenantId tenantId)
{
tenantId.Value.ShouldBe(context.TenantId);
return context.TenantId;
}

[WolverineGet("/tenant/both/{tenant}")]
public static string GetTenantWithArgs1(IMessageContext context, IMessageBus bus)
public static string GetTenantWithArgs1(IMessageContext context, IMessageBus bus, TenantId tenantId)
{
bus.TenantId.ShouldBe(context.TenantId);
return context.TenantId;
Expand Down
30 changes: 27 additions & 3 deletions src/Testing/CoreTests/Acceptance/multi_tenancy.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Diagnostics;
using CoreTests.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Wolverine.Persistence;
using Wolverine.Tracking;
using Xunit;

Expand Down Expand Up @@ -75,22 +77,44 @@ public record TenantedResult(string TenantId);

public static class TenantedHandler
{
public static (TenantedResult, TenantedMessage2) Handle(TenantedMessage1 message, Envelope envelope, TenantedMessageTracker tracker)
public static (TenantedResult, TenantedMessage2) Handle(TenantedMessage1 message, Envelope envelope, TenantedMessageTracker tracker, TenantId tenantId)
{
tenantId.Value.ShouldBe(envelope.TenantId);

tracker.TrackedOne[message.Id] = envelope.TenantId;
return (new TenantedResult(envelope.TenantId), new TenantedMessage2(message.Id));
}

public static TenantedMessage3 Handle(TenantedMessage2 message, Envelope envelope, TenantedMessageTracker tracker)
public static TenantedMessage3 Handle(TenantedMessage2 message, Envelope envelope, TenantedMessageTracker tracker, TenantId tenantId)
{
tenantId.Value.ShouldBe(envelope.TenantId);

tracker.TrackedTwo[message.Id] = envelope.TenantId;
return new TenantedMessage3(message.Id);
}

public static void Handle(TenantedMessage3 message, Envelope envelope, TenantedMessageTracker tracker)
public static void Handle(TenantedMessage3 message, Envelope envelope, TenantedMessageTracker tracker, TenantId tenantId)
{
tenantId.Value.ShouldBe(envelope.TenantId);

tracker.TrackedThree[message.Id] = envelope.TenantId;
}

public static void Handle(TenantedResult result) => Debug.WriteLine("Got a tracked result");
}

public record SomeCommand;

#region sample_injecting_tenant_id

public static class SomeCommandHandler
{
// Wolverine is keying off the type, the parameter name
// doesn't really matter
public static void Handle(SomeCommand command, TenantId tenantId)
{
Debug.WriteLine($"I got a command {command} for tenant {tenantId.Value}");
}
}

#endregion
Loading

0 comments on commit 23a18a8

Please sign in to comment.