From 8e0edabeed57e078cd3e6a7907470a3ace6ff7c3 Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Fri, 16 Aug 2019 10:51:35 +0200 Subject: [PATCH] [skip appveyor] #9 Documentation and examples Implemented two examples. More needed to get rid of the mess that is the current "Test" project. --- .../02-DeclareDurableQueues.csproj | 19 ++++ .../ExampleMessageController.cs | 28 +++++ 02-DeclareDurableQueues/Program.cs | 48 ++++++++ ExampleHelper.cs/ExampleConsoleApp.cs | 104 ++++++++++++++++++ ExampleHelper.cs/ExampleLib.csproj | 12 ++ ExampleHelper.cs/IExampleState.cs | 14 +++ .../01-PublishSubscribe.csproj | 20 ++++ .../ExampleMessageController.cs | 27 +++++ .../01-PublishSubscribe/ExamplePublisher.cs | 29 +++++ Examples/01-PublishSubscribe/Program.cs | 64 +++++++++++ .../Messaging.TapetiExample.csproj | 11 ++ .../PublishSubscribeMessage.cs | 13 +++ Tapeti.sln | 34 +++++- 13 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 02-DeclareDurableQueues/02-DeclareDurableQueues.csproj create mode 100644 02-DeclareDurableQueues/ExampleMessageController.cs create mode 100644 02-DeclareDurableQueues/Program.cs create mode 100644 ExampleHelper.cs/ExampleConsoleApp.cs create mode 100644 ExampleHelper.cs/ExampleLib.csproj create mode 100644 ExampleHelper.cs/IExampleState.cs create mode 100644 Examples/01-PublishSubscribe/01-PublishSubscribe.csproj create mode 100644 Examples/01-PublishSubscribe/ExampleMessageController.cs create mode 100644 Examples/01-PublishSubscribe/ExamplePublisher.cs create mode 100644 Examples/01-PublishSubscribe/Program.cs create mode 100644 Messaging.TapetiExample/Messaging.TapetiExample.csproj create mode 100644 Messaging.TapetiExample/PublishSubscribeMessage.cs diff --git a/02-DeclareDurableQueues/02-DeclareDurableQueues.csproj b/02-DeclareDurableQueues/02-DeclareDurableQueues.csproj new file mode 100644 index 0000000..7104473 --- /dev/null +++ b/02-DeclareDurableQueues/02-DeclareDurableQueues.csproj @@ -0,0 +1,19 @@ + + + + Exe + netcoreapp2.1 + _02_DeclareDurableQueues + + + + + + + + + + + + + diff --git a/02-DeclareDurableQueues/ExampleMessageController.cs b/02-DeclareDurableQueues/ExampleMessageController.cs new file mode 100644 index 0000000..ee5266e --- /dev/null +++ b/02-DeclareDurableQueues/ExampleMessageController.cs @@ -0,0 +1,28 @@ +using System; +using ExampleLib; +using Messaging.TapetiExample; +using Tapeti.Annotations; + +namespace _02_DeclareDurableQueues +{ + [MessageController] + [DurableQueue("tapeti.example.02")] + public class ExampleMessageController + { + private readonly IExampleState exampleState; + + + public ExampleMessageController(IExampleState exampleState) + { + this.exampleState = exampleState; + } + + + public void HandlePublishSubscribeMessage(PublishSubscribeMessage message) + { + // Note that if you run example 01 after 02, it's message will also be in this durable queue + Console.WriteLine("Received message: " + message.Greeting); + exampleState.Done(); + } + } +} diff --git a/02-DeclareDurableQueues/Program.cs b/02-DeclareDurableQueues/Program.cs new file mode 100644 index 0000000..935470a --- /dev/null +++ b/02-DeclareDurableQueues/Program.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using ExampleLib; +using Messaging.TapetiExample; +using SimpleInjector; +using Tapeti; +using Tapeti.Default; +using Tapeti.SimpleInjector; + +namespace _02_DeclareDurableQueues +{ + public class Program + { + public static void Main(string[] args) + { + var container = new Container(); + var dependencyResolver = new SimpleInjectorDependencyResolver(container); + + container.Register(); + + var helper = new ExampleConsoleApp(dependencyResolver); + helper.Run(MainAsync); + } + + + internal static async Task MainAsync(IDependencyResolver dependencyResolver, Func waitForDone) + { + var config = new TapetiConfig(dependencyResolver) + .RegisterAllControllers() + .EnableDeclareDurableQueues() + .Build(); + + using (var connection = new TapetiConnection(config)) + { + // This creates or updates the durable queue + await connection.Subscribe(); + + await dependencyResolver.Resolve().Publish(new PublishSubscribeMessage + { + Greeting = "Hello durable queue!" + }); + + // Wait for the controller to signal that the message has been received + await waitForDone(); + } + } + } +} diff --git a/ExampleHelper.cs/ExampleConsoleApp.cs b/ExampleHelper.cs/ExampleConsoleApp.cs new file mode 100644 index 0000000..59e15f0 --- /dev/null +++ b/ExampleHelper.cs/ExampleConsoleApp.cs @@ -0,0 +1,104 @@ +using System; +using System.Threading.Tasks; +using Tapeti; + +namespace ExampleLib +{ + /// + /// Callback method for ExampleConsoleApp.Run + /// + /// A reference to the dependency resolver passed to the ExampleConsoleApp + /// Await this function to wait for the Done signal + public delegate Task AsyncFunc(IDependencyResolver dependencyResolver, Func waitForDone); + + + /// + /// Since the examples do not run as a service, we need to know when the example has run + /// to completion. This helper injects IExampleState into the container which + /// can be used to signal that it has finished. It also provides the Wait + /// method to wait for this signal. + /// + public class ExampleConsoleApp + { + private readonly IDependencyContainer dependencyResolver; + private readonly TaskCompletionSource doneSignal = new TaskCompletionSource(); + + + /// + public ExampleConsoleApp(IDependencyContainer dependencyResolver) + { + this.dependencyResolver = dependencyResolver; + dependencyResolver.RegisterDefault(() => new ExampleState(this)); + } + + + /// + /// Runs the specified async method and waits for completion. Handles exceptions and waiting + /// for user input when the example application finishes. + /// + /// + public void Run(AsyncFunc asyncFunc) + { + try + { + asyncFunc(dependencyResolver, WaitAsync).Wait(); + } + catch (Exception e) + { + Console.WriteLine(UnwrapException(e)); + } + finally + { + Console.WriteLine("Press any Enter key to continue..."); + Console.ReadLine(); + } + } + + + /// + /// Returns a Task which completed when IExampleState.Done is called + /// + public async Task WaitAsync() + { + await doneSignal.Task; + } + + + internal Exception UnwrapException(Exception e) + { + while (true) + { + if (!(e is AggregateException aggregateException)) + return e; + + if (aggregateException.InnerExceptions.Count != 1) + return e; + + e = aggregateException.InnerExceptions[0]; + } + } + + internal void Done() + { + doneSignal.TrySetResult(true); + } + + + private class ExampleState : IExampleState + { + private readonly ExampleConsoleApp owner; + + + public ExampleState(ExampleConsoleApp owner) + { + this.owner = owner; + } + + + public void Done() + { + owner.Done(); + } + } + } +} diff --git a/ExampleHelper.cs/ExampleLib.csproj b/ExampleHelper.cs/ExampleLib.csproj new file mode 100644 index 0000000..f3cca6f --- /dev/null +++ b/ExampleHelper.cs/ExampleLib.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + true + + + + + + + diff --git a/ExampleHelper.cs/IExampleState.cs b/ExampleHelper.cs/IExampleState.cs new file mode 100644 index 0000000..8f47b22 --- /dev/null +++ b/ExampleHelper.cs/IExampleState.cs @@ -0,0 +1,14 @@ +namespace ExampleLib +{ + /// + /// Since the examples do not run as a service, this interface provides a way + /// for the implementation to signal that it has finished and the example can be closed. + /// + public interface IExampleState + { + /// + /// Signals the Program that the example has finished and the application can be closed. + /// + void Done(); + } +} diff --git a/Examples/01-PublishSubscribe/01-PublishSubscribe.csproj b/Examples/01-PublishSubscribe/01-PublishSubscribe.csproj new file mode 100644 index 0000000..245b78a --- /dev/null +++ b/Examples/01-PublishSubscribe/01-PublishSubscribe.csproj @@ -0,0 +1,20 @@ + + + + Exe + netcoreapp2.1 + _01_PublishSubscribe + + + + + + + + + + + + + + diff --git a/Examples/01-PublishSubscribe/ExampleMessageController.cs b/Examples/01-PublishSubscribe/ExampleMessageController.cs new file mode 100644 index 0000000..6cce0a1 --- /dev/null +++ b/Examples/01-PublishSubscribe/ExampleMessageController.cs @@ -0,0 +1,27 @@ +using System; +using ExampleLib; +using Messaging.TapetiExample; +using Tapeti.Annotations; + +namespace _01_PublishSubscribe +{ + [MessageController] + [DynamicQueue("tapeti.example.01")] + public class ExampleMessageController + { + private readonly IExampleState exampleState; + + + public ExampleMessageController(IExampleState exampleState) + { + this.exampleState = exampleState; + } + + + public void HandlePublishSubscribeMessage(PublishSubscribeMessage message) + { + Console.WriteLine("Received message: " + message.Greeting); + exampleState.Done(); + } + } +} diff --git a/Examples/01-PublishSubscribe/ExamplePublisher.cs b/Examples/01-PublishSubscribe/ExamplePublisher.cs new file mode 100644 index 0000000..83347b7 --- /dev/null +++ b/Examples/01-PublishSubscribe/ExamplePublisher.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Messaging.TapetiExample; +using Tapeti; + +namespace _01_PublishSubscribe +{ + public class ExamplePublisher + { + private readonly IPublisher publisher; + + /// + /// Shows that the IPublisher is registered in the container by Tapeti + /// + /// + public ExamplePublisher(IPublisher publisher) + { + this.publisher = publisher; + } + + + public async Task SendTestMessage() + { + await publisher.Publish(new PublishSubscribeMessage + { + Greeting = "Hello world of messaging!" + }); + } + } +} diff --git a/Examples/01-PublishSubscribe/Program.cs b/Examples/01-PublishSubscribe/Program.cs new file mode 100644 index 0000000..a010fc1 --- /dev/null +++ b/Examples/01-PublishSubscribe/Program.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using ExampleLib; +using SimpleInjector; +using Tapeti; +using Tapeti.Default; +using Tapeti.SimpleInjector; + +namespace _01_PublishSubscribe +{ + public class Program + { + public static void Main(string[] args) + { + var container = new Container(); + var dependencyResolver = new SimpleInjectorDependencyResolver(container); + + container.Register(); + container.Register(); + + + // This helper is used because this example is not run as a service. You do not + // need it in your own applications. + var helper = new ExampleConsoleApp(dependencyResolver); + helper.Run(MainAsync); + } + + + internal static async Task MainAsync(IDependencyResolver dependencyResolver, Func waitForDone) + { + var config = new TapetiConfig(dependencyResolver) + .RegisterAllControllers() + .Build(); + + using (var connection = new TapetiConnection(config) + { + // Params is optional if you want to use the defaults, but we'll set it + // explicitly for this example + Params = new TapetiConnectionParams + { + HostName = "localhost", + Username = "guest", + Password = "guest" + } + }) + { + // Create the queues and start consuming immediately. + // If you need to do some processing before processing messages, but after the + // queues have initialized, pass false as the startConsuming parameter and store + // the returned ISubscriber. Then call Resume on it later. + await connection.Subscribe(); + + + // We could get an IPublisher from the container directly, but since you'll usually use + // it as an injected constructor parameter this shows + await dependencyResolver.Resolve().SendTestMessage(); + + + // Wait for the controller to signal that the message has been received + await waitForDone(); + } + } + } +} diff --git a/Messaging.TapetiExample/Messaging.TapetiExample.csproj b/Messaging.TapetiExample/Messaging.TapetiExample.csproj new file mode 100644 index 0000000..56cdff2 --- /dev/null +++ b/Messaging.TapetiExample/Messaging.TapetiExample.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/Messaging.TapetiExample/PublishSubscribeMessage.cs b/Messaging.TapetiExample/PublishSubscribeMessage.cs new file mode 100644 index 0000000..0a14f37 --- /dev/null +++ b/Messaging.TapetiExample/PublishSubscribeMessage.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Messaging.TapetiExample +{ + /// + /// Example of a simple broadcast message used in the standard publish - subscribe pattern + /// + public class PublishSubscribeMessage + { + [Required] + public string Greeting { get; set; } + } +} diff --git a/Tapeti.sln b/Tapeti.sln index d870188..90289af 100644 --- a/Tapeti.sln +++ b/Tapeti.sln @@ -23,7 +23,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Serilog", "Tapeti.Se EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.Transient", "Tapeti.Transient\Tapeti.Transient.csproj", "{A6355E63-19AB-47EA-91FA-49B5E9B41F88}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tapeti.DataAnnotations.Extensions", "Tapeti.DataAnnotations.Extensions\Tapeti.DataAnnotations.Extensions.csproj", "{1AAA5A2C-EAA8-4C49-96A6-673EA1EEE831}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tapeti.DataAnnotations.Extensions", "Tapeti.DataAnnotations.Extensions\Tapeti.DataAnnotations.Extensions.csproj", "{1AAA5A2C-EAA8-4C49-96A6-673EA1EEE831}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{266B9B94-A4D2-41C2-860C-24A7C3B63B56}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "01-PublishSubscribe", "Examples\01-PublishSubscribe\01-PublishSubscribe.csproj", "{8350A0AB-F0EE-48CF-9CA6-6019467101CF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleLib", "ExampleHelper.cs\ExampleLib.csproj", "{F3B38753-06B4-4932-84B4-A07692AD802D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Messaging.TapetiExample", "Messaging.TapetiExample\Messaging.TapetiExample.csproj", "{D24120D4-50A2-44B6-A4EA-6ADAAEBABA84}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02-DeclareDurableQueues", "02-DeclareDurableQueues\02-DeclareDurableQueues.csproj", "{85511282-EF91-4B56-B7DC-9E8706556D6E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -75,10 +85,32 @@ Global {1AAA5A2C-EAA8-4C49-96A6-673EA1EEE831}.Debug|Any CPU.Build.0 = Debug|Any CPU {1AAA5A2C-EAA8-4C49-96A6-673EA1EEE831}.Release|Any CPU.ActiveCfg = Release|Any CPU {1AAA5A2C-EAA8-4C49-96A6-673EA1EEE831}.Release|Any CPU.Build.0 = Release|Any CPU + {8350A0AB-F0EE-48CF-9CA6-6019467101CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8350A0AB-F0EE-48CF-9CA6-6019467101CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8350A0AB-F0EE-48CF-9CA6-6019467101CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8350A0AB-F0EE-48CF-9CA6-6019467101CF}.Release|Any CPU.Build.0 = Release|Any CPU + {F3B38753-06B4-4932-84B4-A07692AD802D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3B38753-06B4-4932-84B4-A07692AD802D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3B38753-06B4-4932-84B4-A07692AD802D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3B38753-06B4-4932-84B4-A07692AD802D}.Release|Any CPU.Build.0 = Release|Any CPU + {D24120D4-50A2-44B6-A4EA-6ADAAEBABA84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D24120D4-50A2-44B6-A4EA-6ADAAEBABA84}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D24120D4-50A2-44B6-A4EA-6ADAAEBABA84}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D24120D4-50A2-44B6-A4EA-6ADAAEBABA84}.Release|Any CPU.Build.0 = Release|Any CPU + {85511282-EF91-4B56-B7DC-9E8706556D6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85511282-EF91-4B56-B7DC-9E8706556D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85511282-EF91-4B56-B7DC-9E8706556D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85511282-EF91-4B56-B7DC-9E8706556D6E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8350A0AB-F0EE-48CF-9CA6-6019467101CF} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56} + {F3B38753-06B4-4932-84B4-A07692AD802D} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56} + {D24120D4-50A2-44B6-A4EA-6ADAAEBABA84} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56} + {85511282-EF91-4B56-B7DC-9E8706556D6E} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B09CC2BF-B2AF-4CB6-8728-5D1D8E5C50FA} EndGlobalSection