Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ const config: UserConfig<DefaultTheme.Config> = {
{text: 'Queue, Topic, and Binding Management', link:'/guide/messaging/transports/rabbitmq/object-management'},
{text: 'Topics', link:'/guide/messaging/transports/rabbitmq/topics'},
{text: 'Interoperability', link:'/guide/messaging/transports/rabbitmq/interoperability'},
{text: 'Connecting to Multiple Brokers', link: '/guide/messaging/transports/rabbitmq/multiple-brokers'}
{text: 'Connecting to Multiple Brokers', link: '/guide/messaging/transports/rabbitmq/multiple-brokers'},
{text: 'Multi-Tenancy', link: '/guide/messaging/transports/rabbitmq/multi-tenancy'}
]},
{text: 'Azure Service Bus', link: '/guide/messaging/transports/azureservicebus/', items:[
{text: 'Publishing', link:'/guide/messaging/transports/azureservicebus/publishing'},
Expand All @@ -138,7 +139,8 @@ const config: UserConfig<DefaultTheme.Config> = {
{text: 'Topics and Subscriptions', link:'/guide/messaging/transports/azureservicebus/topics'},
{text: 'Interoperability', link:'/guide/messaging/transports/azureservicebus/interoperability'},
{text: 'Session Identifiers and FIFO Queues', link: '/guide/messaging/transports/azureservicebus/session-identifiers'},
{text: 'Scheduled Delivery', link: '/guide/messaging/transports/azureservicebus/scheduled'}
{text: 'Scheduled Delivery', link: '/guide/messaging/transports/azureservicebus/scheduled'},
{text: 'Multi-Tenancy', link: '/guide/messaging/transports/azureservicebus/multi-tenancy'}
]},
{text: 'Amazon SQS', link: '/guide/messaging/transports/sqs/', items:[
{text: 'Publishing', link:'/guide/messaging/transports/sqs/publishing'},
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/durability/efcore.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public class SampleMappedDbContext : DbContext
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/EfCoreTests/SampleDbContext.cs#L50-L76' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_mapping_envelope_storage_to_dbcontext' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/EfCoreTests/SampleDbContext.cs#L51-L77' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_mapping_envelope_storage_to_dbcontext' title='Start of snippet'>anchor</a></sup>
<a id='snippet-sample_mapping_envelope_storage_to_dbcontext-1'></a>
```cs
public class SampleMappedDbContext : DbContext
Expand Down
32 changes: 31 additions & 1 deletion docs/guide/http/metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,34 @@ As of Wolverine 3.4, Wolverine will also apply OpenAPI metadata from any value c
or other middleware that implements the `IEndpointMetadataProvider` interface -- which many `IResult` implementations
from within ASP.Net Core middleware do. Consider this example from the tests:

snippet: sample_using_optional_iresult_with_openapi_metadata
<!-- snippet: sample_using_optional_iresult_with_openapi_metadata -->
<a id='snippet-sample_using_optional_iresult_with_openapi_metadata'></a>
```cs
public class ValidatedCompoundEndpoint2
{
public static User? Load(BlockUser2 cmd)
{
return cmd.UserId.IsNotEmpty() ? new User(cmd.UserId) : null;
}

// This method would be called, and if the NotFound value is
// not null, will stop the rest of the processing
// Likewise, Wolverine will use the NotFound type to add
// OpenAPI metadata
public static NotFound? Validate(User? user)
{
if (user == null)
return (NotFound?)Results.NotFound<User>(user);

return null;
}

[WolverineDelete("/optional/result")]
public static string Handle(BlockUser2 cmd, User user)
{
return "Ok - user blocked";
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Validation/ValidatedCompoundEndpoint.cs#L33-L61' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_optional_iresult_with_openapi_metadata' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
32 changes: 31 additions & 1 deletion docs/guide/http/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,37 @@ public static async Task<IResult> ExecuteOne<T>(IValidator<T> validator, IProble
Likewise, you can also just return a `null` from middleware for `IResult` and Wolverine will interpret that as
"just continue" as shown in this sample:

snippet: sample_using_optional_iresult_with_openapi_metadata
<!-- snippet: sample_using_optional_iresult_with_openapi_metadata -->
<a id='snippet-sample_using_optional_iresult_with_openapi_metadata'></a>
```cs
public class ValidatedCompoundEndpoint2
{
public static User? Load(BlockUser2 cmd)
{
return cmd.UserId.IsNotEmpty() ? new User(cmd.UserId) : null;
}

// This method would be called, and if the NotFound value is
// not null, will stop the rest of the processing
// Likewise, Wolverine will use the NotFound type to add
// OpenAPI metadata
public static NotFound? Validate(User? user)
{
if (user == null)
return (NotFound?)Results.NotFound<User>(user);

return null;
}

[WolverineDelete("/optional/result")]
public static string Handle(BlockUser2 cmd, User user)
{
return "Ok - user blocked";
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Validation/ValidatedCompoundEndpoint.cs#L33-L61' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_optional_iresult_with_openapi_metadata' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Using Configure(chain) Methods

Expand Down
4 changes: 2 additions & 2 deletions docs/guide/http/multi-tenancy.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,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#L415-L424' 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#L421-L430' 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 +283,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#L426-L435' 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#L432-L441' 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
2 changes: 1 addition & 1 deletion docs/guide/http/querystring.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@ public async Task use_decimal_querystring_hit()
body.ReadAsText().ShouldBe("Amount is 42.1");
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/Wolverine.Http.Tests/using_querystring_parameters.cs#L201-L240' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_string_usage' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/Wolverine.Http.Tests/using_querystring_parameters.cs#L267-L306' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_string_usage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
59 changes: 59 additions & 0 deletions docs/guide/messaging/transports/azureservicebus/multi-tenancy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Multi-Tenancy with Azure Service Bus <Badge type="tip" text="3.4" />

Let's take a trip to the world of IoT where you might very well build a single cloud hosted service that needs
to communicate via Rabbit MQ with devices at your customers sites. You'd preferably like to keep traffic separate
so that one customer never accidentally receives information from another customer. In this case, Wolverine now
lets you register separate Rabbit MQ brokers -- or at least separate virtual hosts within a single Rabbit MQ broker --
for each tenant.

::: info
Definitely see [Multi-Tenancy with Wolverine](/guide/handlers/multi-tenancy) for more information about how
Wolverine tracks the tenant id across messages.
:::

Let's just jump straight into a simple example of the configuration:

snippet: sample_configuring_azure_service_bus_for_multi_tenancy

::: warning
Wolverine has no way of creating new Azure Service Bus namespaces for you
:::

In the code sample above, I'm setting up the Azure Service Bus transport to "know" that there are multiple tenants
with separate Azure Service Bus fully qualified namespaces.

::: tip
Note that Wolverine uses the credentials specified for the default Azure Service
Bus connection for all tenant specific connections
:::

At runtime, if we send a message like so:

<!-- snippet: sample_send_message_to_specific_tenant -->
<a id='snippet-sample_send_message_to_specific_tenant'></a>
```cs
public static async Task send_message_to_specific_tenant(IMessageBus bus)
{
// Send a message tagged to a specific tenant id
await bus.PublishAsync(new Message1(), new DeliveryOptions { TenantId = "two" });
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/multi_tenancy_through_virtual_hosts.cs#L211-L219' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_send_message_to_specific_tenant' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

In the case above, in the Wolverine internals, it:

1. Routes the message to a Azure Service Bus queue named "outgoing"
2. Within the sender for that queue, Wolverine sees that `TenantId == "two"`, so it sends the message to the "outgoing" queue
on the Azure Service Bus connection that we specified for the "two" tenant id.

Likewise, see the listening set up against the "incoming" queue above. At runtime, this Wolverine application will be
listening to a queue named "incoming" on the default Azure Service Bus namespace and a separate queue named "incoming" on the separate
fully qualified namespaces for the known tenants. When a message is received at any of these queues, it's tagged with the
`TenantId` that's appropriate for each separate tenant-specific listening endpoint. That helps Wolverine also track
tenant specific operations (with Marten maybe?) and tracks the tenant id across any outgoing messages or responses as well.





104 changes: 104 additions & 0 deletions docs/guide/messaging/transports/rabbitmq/multi-tenancy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Multi-Tenancy with Rabbit MQ <Badge type="tip" text="3.4" />

Let's take a trip to the world of IoT where you might very well build a single cloud hosted service that needs
to communicate via Rabbit MQ with devices at your customers sites. You'd preferably like to keep traffic separate
so that one customer never accidentally receives information from another customer. In this case, Wolverine now
lets you register separate Rabbit MQ brokers -- or at least separate virtual hosts within a single Rabbit MQ broker --
for each tenant.

::: info
Definitely see [Multi-Tenancy with Wolverine](/guide/handlers/multi-tenancy) for more information about how
Wolverine tracks the tenant id across messages.
:::

Let's just jump straight into a simple example of the configuration:

<!-- snippet: sample_configuring_rabbit_mq_for_tenancy -->
<a id='snippet-sample_configuring_rabbit_mq_for_tenancy'></a>
```cs
var builder = Host.CreateApplicationBuilder();

builder.UseWolverine(opts =>
{
// At this point, you still have to have a *default* broker connection to be used for
// messaging.
opts.UseRabbitMq(new Uri(builder.Configuration.GetConnectionString("main")))

// This will be respected across *all* the tenant specific
// virtual hosts and separate broker connections
.AutoProvision()

// This is the default, if there is no tenant id on an outgoing message,
// use the default broker
.TenantIdBehavior(TenantedIdBehavior.FallbackToDefault)

// Or tell Wolverine instead to just quietly ignore messages sent
// to unrecognized tenant ids
.TenantIdBehavior(TenantedIdBehavior.IgnoreUnknownTenants)

// Or be draconian and make Wolverine assert and throw an exception
// if an outgoing message does not have a tenant id
.TenantIdBehavior(TenantedIdBehavior.TenantIdRequired)

// Add specific tenants for separate virtual host names
// on the same broker as the default connection
.AddTenant("one", "vh1")
.AddTenant("two", "vh2")
.AddTenant("three", "vh3")

// Or, you can add a broker connection to something completel
// different for a tenant
.AddTenant("four", new Uri(builder.Configuration.GetConnectionString("rabbit_four")));

// This Wolverine application would be listening to a queue
// named "incoming" on all virtual hosts and/or tenant specific message
// brokers
opts.ListenToRabbitQueue("incoming");

// More on this in the docs....
opts.PublishMessage<Message1>()
.ToRabbitQueue("outgoing");
});
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/multi_tenancy_through_virtual_hosts.cs#L160-L206' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_configuring_rabbit_mq_for_tenancy' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

::: warning
Wolverine has no way of creating new virtual hosts in Rabbit MQ for you. You will have to do that manually
through either the Rabbit MQ admin site, the Rabbit MQ HTTP API, or the Rabbit MQ command line.
:::

In the code sample above, I'm setting up Rabbit MQ to "know" that there are four specific tenants identified as
"one", "two", "three", and "four". I've also told Wolverine how to connect to Rabbit MQ separately for each
known tenant id.

At runtime, if we send a message like so:

<!-- snippet: sample_send_message_to_specific_tenant -->
<a id='snippet-sample_send_message_to_specific_tenant'></a>
```cs
public static async Task send_message_to_specific_tenant(IMessageBus bus)
{
// Send a message tagged to a specific tenant id
await bus.PublishAsync(new Message1(), new DeliveryOptions { TenantId = "two" });
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/multi_tenancy_through_virtual_hosts.cs#L211-L219' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_send_message_to_specific_tenant' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

In the case above, in the Wolverine internals, it:

1. Routes the message to a Rabbit MQ queue named "outgoing"
2. Within the sender for that queue, Wolverine sees that `TenantId == "two"`, so it sends the message to the "outgoing" queue
on the "vh2" virtual host

Likewise, see the listening set up against the "incoming" queue above. At runtime, this Wolverine application will be
listening to a queue named "incoming" on the default Rabbit MQ broker and a separate queue named "incoming" on the separate
virtual hosts or brokers for the known tenants. When a message is received at any of these queues, it's tagged with the
`TenantId` that's appropriate for each separate tenant-specific listening endpoint. That helps Wolverine also track
tenant specific operations (with Marten maybe?) and tracks the tenant id across any outgoing messages or responses as well.





Loading
Loading