Skip to content

Commit

Permalink
updated docs on scheduling messages
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremydmiller committed Sep 6, 2023
1 parent 6981b5b commit ceeab64
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 22 deletions.
6 changes: 4 additions & 2 deletions docs/guide/durability/sagas.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public record StartOrder(string OrderId);

public record CompleteOrder(string Id);

// This message will always be scheduled to be delivered after
// a one minute delay
public record OrderTimeout(string Id) : TimeoutMessage(1.Minutes());

public class Order : Saga
Expand Down Expand Up @@ -66,7 +68,7 @@ public class Order : Saga
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/OrderSagaSample/OrderSaga.cs#L6-L52' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_order_saga' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/OrderSagaSample/OrderSaga.cs#L6-L58' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_order_saga' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

A few explanatory notes on this code before we move on to detailed documentation:
Expand Down Expand Up @@ -298,7 +300,7 @@ public static (Order, OrderTimeout) Start(StartOrder order, ILogger<Order> logge
return (new Order{Id = order.OrderId}, new OrderTimeout(order.OrderId));
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/OrderSagaSample/OrderSaga.cs#L18-L30' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_starting_a_saga_inside_a_handler' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/OrderSagaSample/OrderSaga.cs#L24-L36' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_starting_a_saga_inside_a_handler' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

You can also simply return one or more `Saga` type objects from a handler method as shown below where `Reservation` is a Wolverine saga:
Expand Down
6 changes: 3 additions & 3 deletions docs/guide/handlers/cascading.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,12 @@ of cascading messages **and** have full access to the power of Wolverine. Here's
<!-- snippet: sample_customized_cascaded_messages -->
<a id='snippet-sample_customized_cascaded_messages'></a>
```cs
public IEnumerable<object> Consume(Incoming incoming)
public static IEnumerable<object> Consume(Incoming incoming)
{
// Delay the message delivery
// Delay the message delivery by 10 minutes
yield return new Message1().DelayedFor(10.Minutes());

// Schedule the message delivery
// Schedule the message delivery for a certain time
yield return new Message2().ScheduledAt(new DateTimeOffset(DateTime.Today.AddDays(2)));

// Customize the message delivery however you please...
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/handlers/return-values.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static (Order, OrderTimeout) Start(StartOrder order, ILogger<Order> logge
return (new Order{Id = order.OrderId}, new OrderTimeout(order.OrderId));
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/OrderSagaSample/OrderSaga.cs#L18-L30' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_starting_a_saga_inside_a_handler' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/OrderSagaSample/OrderSaga.cs#L24-L36' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_starting_a_saga_inside_a_handler' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Custom Return Value Handling
Expand Down
6 changes: 3 additions & 3 deletions docs/guide/http/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static ArithmeticResults PostJson(Question question)
};
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/TestEndpoints.cs#L73-L85' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_simple_wolverine_http_endpoint' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/TestEndpoints.cs#L83-L95' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_simple_wolverine_http_endpoint' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

In the method signature above, `Question` is the "request" type (the payload sent from the client to the server) and `Answer` is the "resource" type (what is being returned to the client).
Expand All @@ -42,7 +42,7 @@ public static Task<ArithmeticResults> PostJsonAsync(Question question)
return Task.FromResult(results);
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/TestEndpoints.cs#L87-L101' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_simple_wolverine_http_endpoint_async' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/TestEndpoints.cs#L97-L111' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_simple_wolverine_http_endpoint_async' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

The resource type is still `Answer`. Likewise, if an endpoint returns `ValueTask<Answer>`, the resource type
Expand Down Expand Up @@ -392,7 +392,7 @@ and register that strategy within our `MapWolverineEndpoints()` set up like so:
// Customizing parameter handling
opts.AddParameterHandlingStrategy<NowParameterStrategy>();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L120-L125' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_adding_custom_parameter_handling' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L128-L133' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_adding_custom_parameter_handling' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

And lastly, here's the application within an HTTP endpoint for extra context:
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/http/fluentvalidation.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@ app.MapWolverineEndpoints(opts =>
// Wolverine.Http.FluentValidation
opts.UseFluentValidationProblemDetailMiddleware();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L83-L106' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_configure_endpoints' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L91-L114' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_configure_endpoints' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
2 changes: 1 addition & 1 deletion docs/guide/http/mediator.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ app.MapPostToWolverine<CustomRequest, CustomResponse>("/wolverine/request");
app.MapDeleteToWolverine<CustomRequest, CustomResponse>("/wolverine/request");
app.MapPutToWolverine<CustomRequest, CustomResponse>("/wolverine/request");
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L129-L141' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_optimized_mediator_usage' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L137-L149' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_optimized_mediator_usage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

With this mechanism, Wolverine is able to optimize the runtime function for Minimal API by eliminating IoC service locations
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/http/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ app.MapWolverineEndpoints(opts =>
// Wolverine.Http.FluentValidation
opts.UseFluentValidationProblemDetailMiddleware();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L83-L106' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_configure_endpoints' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L91-L114' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_configure_endpoints' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

The `HttpChain` model is a configuration time structure that Wolverine.Http will use at runtime to create the full
Expand Down Expand Up @@ -101,5 +101,5 @@ app.MapWolverineEndpoints(opts =>
// Wolverine.Http.FluentValidation
opts.UseFluentValidationProblemDetailMiddleware();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L83-L106' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_configure_endpoints' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L91-L114' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_configure_endpoints' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
17 changes: 15 additions & 2 deletions docs/guide/http/querystring.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static string UsingQueryString(string name) // name is from the query str
return name.IsEmpty() ? "Name is missing" : $"Name is {name}";
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/TestEndpoints.cs#L45-L53' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_string_value_as_query_string' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/TestEndpoints.cs#L47-L55' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_string_value_as_query_string' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

And the corresponding tests:
Expand Down Expand Up @@ -55,6 +55,19 @@ public async Task use_string_querystring_miss()

body.ReadAsText().ShouldBe("Name is missing");
}

[Fact]
public async Task use_decimal_querystring_hit()
{
var body = await Scenario(x =>
{
x.WithRequestHeader("Accept-Language", "fr-FR");
x.Get.Url("/querystring/decimal?amount=42.1");
x.Header("content-type").SingleValueShouldEqual("text/plain");
});

body.ReadAsText().ShouldBe("Amount is 42.1");
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/Wolverine.Http.Tests/end_to_end.cs#L185-L211' 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/end_to_end.cs#L187-L226' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_string_usage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
4 changes: 2 additions & 2 deletions docs/guide/http/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static string SimpleStringRouteArgument(string name)
return $"Name is {name}";
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/TestEndpoints.cs#L25-L33' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_string_route_parameter' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/TestEndpoints.cs#L27-L35' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_string_route_parameter' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

In the sample above, the `name` argument will be the value of the route argument
Expand All @@ -36,7 +36,7 @@ public static string IntRouteArgument(int age)
return $"Age is {age}";
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/TestEndpoints.cs#L35-L43' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_numeric_route_parameter' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/TestEndpoints.cs#L37-L45' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_numeric_route_parameter' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

The following code snippet from `WolverineFx.Http` itself shows the valid route
Expand Down
135 changes: 134 additions & 1 deletion docs/guide/messaging/message-bus.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,140 @@ public ValueTask PublishMessage(IMessageContext bus)

## Scheduling Message Delivery or Execution

TODO
::: tip
While Wolverine has an in memory scheduled delivery and execution model by default, that was only intended for delayed message
execution retries. You will most likely want to either use a transport type that supports native scheduled delivery like
the [Azure Service Bus transport](/guide/messaging/transports/azureservicebus/scheduled), or utilize the [database backed
message persistence](/guide/durability/) to enable durable message scheduling.
:::

Wolverine supports the concept of scheduled message delivery. Likewise, Wolverine also supports scheduled message execution
if you're publishing to a [local queue](/guide/messaging/transports/local) within your current application. The actual
mechanics for message scheduling will vary according to the endpoint destination that a message is being published to,
including whether or not the scheduled message is durable and will outlive any unexpected or planned process terminations.

::: tip
The built in outbox message scheduling was meant for relatively low numbers of messages, and was primarily meant for scheduled
message retries. If you have an excessive number of scheduled messages, you may want to utilize the database backed queues
in Wolverine which are optimized for much higher number of scheduled messages.
:::

First off, your guide for understanding the scheduled message delivery mechanics in effective order:

* If the destination endpoint has native message delivery capabilities, Wolverine uses that capability. Outbox mechanics
still apply to when the outgoing message is released to the external endpoint's sender
* If the destination endpoint is durable, meaning that it's enrolled in Wolverine's [transactional outbox](/guide/durability/), then Wolverine
will store the scheduled messages in the outgoing envelope storage for later execution. In this case, Wolverine is polling
for the ready to execute or deliver messages across all running Wolverine nodes. This option is durable in case of process
exits.
* In lieu of any other support, Wolverine has an in memory option that can do scheduled delivery or execution

To schedule message delivery (scheduled execution really just means scheduling message publishing to a local queue), you
actually have a couple different syntactical options. First, if you're directly using the `IMessageBus` interface, you
can schedule a message with a delay using this extension method:

<!-- snippet: sample_ScheduleSend_In_3_Days -->
<a id='snippet-sample_schedulesend_in_3_days'></a>
```cs
public async Task schedule_send(IMessageContext context, Guid issueId)
{
var timeout = new WarnIfIssueIsStale
{
IssueId = issueId
};

// Process the issue timeout logic 3 days from now
await context.ScheduleAsync(timeout, 3.Days());

// The code above is short hand for this:
await context.PublishAsync(timeout, new DeliveryOptions
{
ScheduleDelay = 3.Days()
});
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/ScheduledExecutionSamples.cs#L8-L27' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_schedulesend_in_3_days' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Or using an absolute time, with this overload of the extension method:

<!-- snippet: sample_ScheduleSend_At_5_PM_Tomorrow -->
<a id='snippet-sample_schedulesend_at_5_pm_tomorrow'></a>
```cs
public async Task schedule_send_at_5_tomorrow_afternoon(IMessageContext context, Guid issueId)
{
var timeout = new WarnIfIssueIsStale
{
IssueId = issueId
};

var time = DateTime.Today.AddDays(1).AddHours(17);

// Process the issue timeout at 5PM tomorrow
// Do note that Wolverine quietly converts this
// to universal time in storage
await context.ScheduleAsync(timeout, time);
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/ScheduledExecutionSamples.cs#L29-L47' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_schedulesend_at_5_pm_tomorrow' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Now, Wolverine tries really hard to enable you to use [pure functions](https://en.wikipedia.org/wiki/Pure_function) for as many message handlers as possible, so
there's of course an option to schedule message delivery while still using [cascading messages](/guide/handlers/cascading) with the `DelayedFor()` and
`ScheduledAt()` extension methods shown below:

<!-- snippet: sample_customized_cascaded_messages -->
<a id='snippet-sample_customized_cascaded_messages'></a>
```cs
public static IEnumerable<object> Consume(Incoming incoming)
{
// Delay the message delivery by 10 minutes
yield return new Message1().DelayedFor(10.Minutes());

// Schedule the message delivery for a certain time
yield return new Message2().ScheduledAt(new DateTimeOffset(DateTime.Today.AddDays(2)));

// Customize the message delivery however you please...
yield return new Message3()
.WithDeliveryOptions(new DeliveryOptions().WithHeader("foo", "bar"));

// Send back to the original sender
yield return Respond.ToSender(new Message4());
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/OutgoingMessagesSample.cs#L37-L55' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_customized_cascaded_messages' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Lastly, there's a special base class called `TimeoutMessage` that your message types can extend to add scheduling logic
directly to the message itself for easy usage as a cascaded message. Here's an example message type:

<!-- snippet: sample_OrderTimeout -->
<a id='snippet-sample_ordertimeout'></a>
```cs
// This message will always be scheduled to be delivered after
// a one minute delay
public record OrderTimeout(string Id) : TimeoutMessage(1.Minutes());
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/OrderSagaSample/OrderSaga.cs#L12-L18' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ordertimeout' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Which is used within this sample saga implementation:

<!-- snippet: sample_starting_a_saga_inside_a_handler -->
<a id='snippet-sample_starting_a_saga_inside_a_handler'></a>
```cs
// This method would be called when a StartOrder message arrives
// to start a new Order
public static (Order, OrderTimeout) Start(StartOrder order, ILogger<Order> logger)
{
logger.LogInformation("Got a new order with id {Id}", order.OrderId);

// creating a timeout message for the saga
return (new Order{Id = order.OrderId}, new OrderTimeout(order.OrderId));
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/OrderSagaSample/OrderSaga.cs#L24-L36' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_starting_a_saga_inside_a_handler' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


## Customizing Message Delivery
Expand Down
8 changes: 4 additions & 4 deletions src/Samples/DocumentationSamples/OutgoingMessagesSample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace DocumentationSamples;

public class OutgoingMessageHandler
public static class OutgoingMessageHandler
{
#region sample_using_OutgoingMessage

Expand Down Expand Up @@ -36,12 +36,12 @@ public static OutgoingMessages Handle(Incoming incoming)

#region sample_customized_cascaded_messages

public IEnumerable<object> Consume(Incoming incoming)
public static IEnumerable<object> Consume(Incoming incoming)
{
// Delay the message delivery
// Delay the message delivery by 10 minutes
yield return new Message1().DelayedFor(10.Minutes());

// Schedule the message delivery
// Schedule the message delivery for a certain time
yield return new Message2().ScheduledAt(new DateTimeOffset(DateTime.Today.AddDays(2)));

// Customize the message delivery however you please...
Expand Down
Loading

0 comments on commit ceeab64

Please sign in to comment.