diff --git a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http index 2210f5806a..dccf5d6cea 100644 --- a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http +++ b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http @@ -5,7 +5,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T191545.200.json +<> 2022-02-23T081959.200.json ### @@ -16,7 +16,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T191543.200.json +<> 2022-02-23T081958.200.json ### @@ -27,7 +27,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T191542.200.json +<> 2022-02-23T081956.200.json ### @@ -38,7 +38,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T191540.200.json +<> 2022-02-23T081955.200.json ### @@ -49,7 +49,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T191538.200.json +<> 2022-02-23T081953.200.json ### @@ -60,7 +60,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T191537.200.json +<> 2022-02-23T081950.200.json ### @@ -71,19 +71,17 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-21T191532.200.json +<> 2022-02-23T081942.200.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T190227.200.json - ### POST http://localhost:5000/Greetings/Tyrion/new @@ -93,7 +91,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190115.200.json +<> 2022-02-23T080140.200.json ### @@ -104,7 +102,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190113.200.json +<> 2022-02-23T080137.200.json ### @@ -115,7 +113,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190112.200.json +<> 2022-02-23T080136.200.json ### @@ -126,7 +124,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190109.200.json +<> 2022-02-23T080133.200.json ### @@ -137,7 +135,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190108.200.json +<> 2022-02-23T080131.200.json ### @@ -148,18 +146,18 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-21T190103.200.json +<> 2022-02-23T080107.200.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-02-21T185929.500.json +<> 2022-02-21T191545.200.json ### @@ -170,7 +168,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131223.200.json +<> 2022-02-21T191543.200.json ### @@ -181,7 +179,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131221.200.json +<> 2022-02-21T191542.200.json ### @@ -192,7 +190,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131220.200.json +<> 2022-02-21T191540.200.json ### @@ -203,7 +201,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131218.200.json +<> 2022-02-21T191538.200.json ### @@ -214,35 +212,40 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131215.200.json +<> 2022-02-21T191537.200.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T131210.200.json +<> 2022-02-21T191532.200.json ### -GET http://localhost:5000/People/Tyrion +POST http://localhost:5000/Greetings/Tyrion/new +Content-Type: application/json -<> 2022-02-21T131206.200.json +{ + "Greeting" : "I drink, and I know things" +} + +<> 2022-02-21T190227.200.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131202.200.json +<> 2022-02-21T190115.200.json ### @@ -253,7 +256,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T193631.200.json +<> 2022-02-21T190113.200.json ### @@ -264,7 +267,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T193630.200.json +<> 2022-02-21T190112.200.json ### @@ -275,7 +278,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T193628.200.json +<> 2022-02-21T190109.200.json ### @@ -286,18 +289,18 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T193626.200.json +<> 2022-02-21T190108.200.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-07T193624.200.json +<> 2022-02-21T190103.200.json ### @@ -308,7 +311,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-07T193615.200.json +<> 2022-02-21T185929.500.json ### @@ -319,7 +322,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192928.200.json +<> 2022-02-21T131223.200.json ### @@ -330,7 +333,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192925.200.json +<> 2022-02-21T131221.200.json ### @@ -341,7 +344,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192922.200.json +<> 2022-02-21T131220.200.json ### @@ -352,7 +355,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192920.200.json +<> 2022-02-21T131218.200.json ### @@ -363,7 +366,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192918.200.json +<> 2022-02-21T131215.200.json ### @@ -374,13 +377,13 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192907.200.json +<> 2022-02-21T131210.200.json ### -GET http://localhost:5000/Greetings/Tyrion +GET http://localhost:5000/People/Tyrion -<> 2022-02-07T192855.200.json +<> 2022-02-21T131206.200.json ### @@ -391,40 +394,40 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-07T192850.200.json +<> 2022-02-21T131202.200.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } +<> 2022-02-07T193631.200.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } +<> 2022-02-07T193630.200.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -### - -GET http://localhost:5000/Greetings/Tyrion - -<> 2022-02-07T090154.200.json +<> 2022-02-07T193628.200.json ### @@ -435,7 +438,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T090145.200.json +<> 2022-02-07T193626.200.json ### @@ -446,18 +449,18 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T090143.200.json +<> 2022-02-07T193624.200.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-07T090141.200.json +<> 2022-02-07T193615.200.json ### @@ -468,7 +471,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T090140.200.json +<> 2022-02-07T192928.200.json ### @@ -479,13 +482,18 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T090137.200.json +<> 2022-02-07T192925.200.json ### -GET http://localhost:5000/Greetings/Tyrion +POST http://localhost:5000/Greetings/Tyrion/new +Content-Type: application/json -<> 2022-02-07T090110.200.json +{ + "Greeting" : "I drink, and I know things" +} + +<> 2022-02-07T192922.200.json ### @@ -496,24 +504,35 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T090107.200.json +<> 2022-02-07T192920.200.json ### -GET http://localhost:5000/People/Tyrion +POST http://localhost:5000/Greetings/Tyrion/new +Content-Type: application/json -<> 2022-02-07T090102.200.json +{ + "Greeting" : "I drink, and I know things" +} + +<> 2022-02-07T192918.200.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-02-07T090057.200.json +<> 2022-02-07T192907.200.json + +### + +GET http://localhost:5000/Greetings/Tyrion + +<> 2022-02-07T192855.200.json ### diff --git a/samples/WebAPI_EFCore/README.md b/samples/WebAPI_EFCore/README.md index 29c02e31c0..1b3ae41945 100644 --- a/samples/WebAPI_EFCore/README.md +++ b/samples/WebAPI_EFCore/README.md @@ -26,9 +26,13 @@ We 'depend inwards' i.e. **GreetingsAdapters -> GreetingsPorts -> GreetingsEntit The assemblies migrations: **Greetings_MySqlMigrations** and **Greetings_SqliteMigrations** hold generated code to configure the Db. Consider this adapter layer code - the use of separate modules allows us to switch migration per enviroment. -### GreetingsWatcher +### SalutationAnalytics -This just listens for a GreetingMade message and dumps it to the console. It demonstrates listening to a queue. In this case it is too trivial for a ports & adapters separation, but that approach could be used if there was an entity layer and domain logic. +This listens for a GreetingMade message and stores it. It demonstrates listening to a queue. It also demonstrates the use of scopes provided by Brighter's ServiceActivator, which work with EFCore. These support writing to an Outbox when this component raises a message in turn. + +We don't listen to that message, and without any listeners the RMQ will drop the message we send, as it has no queues to give it to. We don't listen because we would just be repeating what we have shown here. + +We also add an Inbox here. The Inbox can be used to de-duplicate messages. In messaging the guarantee is 'at least once' if you use a technique such as an Outbox to ensure sending. This means we may receive a message twice. We either need, as in this case, to use an Inbox to de-duplicate, or we need to be idempotent such that receiving the message multiple times would result in the same outcome. ## Build and Deploy @@ -54,6 +58,20 @@ We provide a docker compose file to allow you to run a 'Production' environment and so on +### Sqlite Database Read-Only Errors + +A Sqlite database will only have permissions for the process that created it. This can result in you receiving read-only errors between invocations of the sample. You either need to alter the permissions on your Db, or delete it between runs. + +Maintainers, please don't check the Sqlite files into source control. + +### Queue Creation and Dropped Messages + +Queues are created by consumers. This is because publishers don't know who consumes them, and thus don't create their queues. This means that if you run a producer, such as GreetingsWeb, and use tests.http to push in greetings, although a message will be published to RMQ, it won't have a queue to be delivered to and will be dropped, unless you have first run the SalutationAnalytics worker to create the queue. + +Generally, the rule of thumb is: start the consumer and *then* start the producer. + +You can spot this by looking in the [RMQ Management console](http://localhost:1567) and noting that no queue is bound to the routing key in th exchange. + ## Tests We provide a tests.http file (supported by both JetBrains Rider and VS Code with the REST Client plugin) to allow you to test operations. diff --git a/samples/WebAPI_EFCore/SalutationAnalytics/Program.cs b/samples/WebAPI_EFCore/SalutationAnalytics/Program.cs index 7beb8c4ac9..e2df030527 100644 --- a/samples/WebAPI_EFCore/SalutationAnalytics/Program.cs +++ b/samples/WebAPI_EFCore/SalutationAnalytics/Program.cs @@ -14,8 +14,10 @@ using Paramore.Brighter.MessagingGateway.RMQ; using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection; using Paramore.Brighter.ServiceActivator.Extensions.Hosting; +using Polly.Registry; using SalutationAnalytics.Database; using SalutationPorts.EntityGateway; +using SalutationPorts.Policies; using SalutationPorts.Requests; namespace SalutationAnalytics @@ -82,11 +84,12 @@ private static IHostBuilder CreateHostBuilder(string[] args) => options.HandlerLifetime = ServiceLifetime.Scoped; options.MapperLifetime = ServiceLifetime.Singleton; options.CommandProcessorLifetime = ServiceLifetime.Scoped; + options.PolicyRegistry = new SalutationPolicy(); }) .UseExternalBus(new RmqProducerRegistryFactory( new RmqMessagingGatewayConnection { - AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@rabbitmq:5672")), + AmpqUri = new AmqpUriSpecification(new Uri($"amqp://guest:guest@{host}:5672")), Exchange = new Exchange("paramore.brighter.exchange"), }, new RmqPublication[] { diff --git a/samples/WebAPI_EFCore/SalutationPorts/Handlers/GreetingMadeHandler.cs b/samples/WebAPI_EFCore/SalutationPorts/Handlers/GreetingMadeHandler.cs index ab8c7cdf14..10f0be0db5 100644 --- a/samples/WebAPI_EFCore/SalutationPorts/Handlers/GreetingMadeHandler.cs +++ b/samples/WebAPI_EFCore/SalutationPorts/Handlers/GreetingMadeHandler.cs @@ -5,6 +5,7 @@ using Paramore.Brighter; using Paramore.Brighter.Inbox.Attributes; using Paramore.Brighter.Logging.Attributes; +using Paramore.Brighter.Policies.Attributes; using SalutationEntities; using SalutationPorts.EntityGateway; using SalutationPorts.Requests; @@ -22,8 +23,9 @@ public GreetingMadeHandlerAsync(SalutationsEntityGateway uow, IAmACommandProcess _postBox = postBox; } - [UseInboxAsync(step:0, contextKey: typeof(GreetingMadeHandlerAsync), onceOnly: true )] + //[UseInboxAsync(step:0, contextKey: typeof(GreetingMadeHandlerAsync), onceOnly: true )] -- we are using a global inbox, so need to be explicit!! [RequestLoggingAsync(step: 1, timing: HandlerTiming.Before)] + [UsePolicyAsync(step:2, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)] public override async Task HandleAsync(GreetingMade @event, CancellationToken cancellationToken = default(CancellationToken)) { var posts = new List(); @@ -48,6 +50,8 @@ public GreetingMadeHandlerAsync(SalutationsEntityGateway uow, IAmACommandProcess await tx.RollbackAsync(cancellationToken); Console.WriteLine("Salutation analytical record not saved"); + + throw; } await _postBox.ClearOutboxAsync(posts, cancellationToken: cancellationToken); diff --git a/samples/WebAPI_EFCore/SalutationPorts/Policies/Retry.cs b/samples/WebAPI_EFCore/SalutationPorts/Policies/Retry.cs new file mode 100644 index 0000000000..4db47aa42d --- /dev/null +++ b/samples/WebAPI_EFCore/SalutationPorts/Policies/Retry.cs @@ -0,0 +1,27 @@ +using System; +using Polly; +using Polly.Contrib.WaitAndRetry; +using Polly.Retry; + +namespace SalutationPorts.Policies +{ + public static class Retry + { + public const string RETRYPOLICYASYNC = "SalutationPorts.Policies.RetryPolicyAsync"; + public const string EXPONENTIAL_RETRYPOLICYASYNC = "SalutationPorts.Policies.ExponenttialRetryPolicyAsync"; + + public static AsyncRetryPolicy GetSimpleHandlerRetryPolicy() + { + return Policy.Handle().WaitAndRetryAsync(new[] + { + TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(150) + }); + } + + public static AsyncRetryPolicy GetExponentialHandlerRetryPolicy() + { + var delay = Backoff.ExponentialBackoff(TimeSpan.FromMilliseconds(100), retryCount: 5, fastFirst: true); + return Policy.Handle().WaitAndRetryAsync(delay); + } + } +} diff --git a/samples/WebAPI_EFCore/SalutationPorts/Policies/SalutationPolicy.cs b/samples/WebAPI_EFCore/SalutationPorts/Policies/SalutationPolicy.cs new file mode 100644 index 0000000000..ddf21c324f --- /dev/null +++ b/samples/WebAPI_EFCore/SalutationPorts/Policies/SalutationPolicy.cs @@ -0,0 +1,18 @@ +using Paramore.Brighter; + +namespace SalutationPorts.Policies +{ + public class SalutationPolicy : DefaultPolicy + { + public SalutationPolicy() + { + AddSalutationPolicies(); + } + + private void AddSalutationPolicies() + { + Add(Retry.RETRYPOLICYASYNC, Retry.GetSimpleHandlerRetryPolicy()); + Add(Retry.EXPONENTIAL_RETRYPOLICYASYNC, Retry.GetExponentialHandlerRetryPolicy()); + } + } +} diff --git a/samples/WebAPI_EFCore/SalutationPorts/SalutationPorts.csproj b/samples/WebAPI_EFCore/SalutationPorts/SalutationPorts.csproj index 158f29e8cd..8239a7c08e 100644 --- a/samples/WebAPI_EFCore/SalutationPorts/SalutationPorts.csproj +++ b/samples/WebAPI_EFCore/SalutationPorts/SalutationPorts.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Paramore.Brighter.MessagingGateway.RMQ/RmqMessageGateway.cs b/src/Paramore.Brighter.MessagingGateway.RMQ/RmqMessageGateway.cs index 223f03b08b..296f577116 100644 --- a/src/Paramore.Brighter.MessagingGateway.RMQ/RmqMessageGateway.cs +++ b/src/Paramore.Brighter.MessagingGateway.RMQ/RmqMessageGateway.cs @@ -126,7 +126,7 @@ protected virtual void ConnectToBroker(OnMissingChannel makeExchange) connection.ConnectionBlocked += HandleBlocked; connection.ConnectionUnblocked += HandleUnBlocked; - s_logger.LogDebug("RMQMessagingGateway: Opening channel to Rabbit MQ on subscription {URL}", + s_logger.LogDebug("RMQMessagingGateway: Opening channel to Rabbit MQ on {URL}", Connection.AmpqUri.GetSanitizedUri()); Channel = connection.CreateModel();