diff --git a/JustSaying.sln b/JustSaying.sln
index fc163af7e..7a3aa1899 100644
--- a/JustSaying.sln
+++ b/JustSaying.sln
@@ -56,6 +56,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A94633F2-29F
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E22A50F2-9952-4483-8AD1-09BE354FB3E4}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{10067D40-00F2-4B06-B73B-381DE95E6DD3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JustSaying.Sample.Restaurant.Models", "samples\JustSaying.Sample.Restaurant.Models\JustSaying.Sample.Restaurant.Models.csproj", "{0F64EB5C-6784-4870-A3E9-460EB78ED53D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JustSaying.Sample.Restaurant.KitchenConsole", "samples\JustSaying.Sample.Restaurant.KitchenConsole\JustSaying.Sample.Restaurant.KitchenConsole.csproj", "{55D91685-60BF-42E9-B166-E037F8FBA4F5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JustSaying.Sample.Restaurant.OrderingApi", "samples\JustSaying.Sample.Restaurant.OrderingApi\JustSaying.Sample.Restaurant.OrderingApi.csproj", "{C40BB811-EDF8-47A4-93F5-2563E15C5CC1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -94,6 +102,18 @@ Global
{FC6E028F-01DA-47A5-895A-5C1DD27981D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC6E028F-01DA-47A5-895A-5C1DD27981D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC6E028F-01DA-47A5-895A-5C1DD27981D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0F64EB5C-6784-4870-A3E9-460EB78ED53D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0F64EB5C-6784-4870-A3E9-460EB78ED53D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0F64EB5C-6784-4870-A3E9-460EB78ED53D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0F64EB5C-6784-4870-A3E9-460EB78ED53D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {55D91685-60BF-42E9-B166-E037F8FBA4F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {55D91685-60BF-42E9-B166-E037F8FBA4F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {55D91685-60BF-42E9-B166-E037F8FBA4F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {55D91685-60BF-42E9-B166-E037F8FBA4F5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C40BB811-EDF8-47A4-93F5-2563E15C5CC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C40BB811-EDF8-47A4-93F5-2563E15C5CC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C40BB811-EDF8-47A4-93F5-2563E15C5CC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C40BB811-EDF8-47A4-93F5-2563E15C5CC1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -109,6 +129,9 @@ Global
{949EC37A-A278-4D02-A358-CC6F76B6E4CF} = {F0BCBE5F-2132-422D-B17B-23B7FCC4A8A8}
{1B9BB1E2-E46B-4E2B-A172-61ABA8B35B62} = {A94633F2-29F2-48C6-840A-C5370B300AE2}
{FC6E028F-01DA-47A5-895A-5C1DD27981D4} = {A94633F2-29F2-48C6-840A-C5370B300AE2}
+ {0F64EB5C-6784-4870-A3E9-460EB78ED53D} = {10067D40-00F2-4B06-B73B-381DE95E6DD3}
+ {55D91685-60BF-42E9-B166-E037F8FBA4F5} = {10067D40-00F2-4B06-B73B-381DE95E6DD3}
+ {C40BB811-EDF8-47A4-93F5-2563E15C5CC1} = {10067D40-00F2-4B06-B73B-381DE95E6DD3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {18FBDF85-C124-4444-9F03-D0D4F2B3A612}
diff --git a/README.md b/README.md
index 2c0a75740..9f0b478c8 100644
--- a/README.md
+++ b/README.md
@@ -284,6 +284,31 @@ At this point, the power tool is only able to move an arbitrary number of messag
JustSaying.Tools.exe move -from "source_queue_name" -to "destination_queue_name" -in "region" -count "1"
````
+## Sample Application
+
+- To run the sample application against a simulated AWS SQS / SNS endpoint run this container
+ ```sh
+ docker pull pafortin/goaws
+ docker run -d --name goaws -p 4100:4100 pafortin/goaws
+ ```
+- Alternatively to use your real AWS account
+ - Locate the setup code `services.AddJustSaying(...)` in both [`Program.cs`](./samples/JustSaying.Sample.Restaurant.KitchenConsole/Program.cs) and [`Startup.cs`](./samples/JustSaying.Sample.Restaurant.JustSaying.Sample.Restaurant.OrderingApi/Startup.cs)
+ - Remove the references to the 'ServiceUrl`
+ - Add references to `x.WithCredentials(...)` supplying your real aws credentials
+- Execute both `KitchenConsole` and `OrderingApi` applications
+
+- Demonstrates
+ - Publishing messages to a SNS Topic from a WebApi application and Console Application
+ - Receiving messages from a SQS queue subscribed to a SNS topic in a WebApi application and Console Application
+
+- Further samples in progress
+ - Demonstrate use of `PublishMetaData`
+ - Demonstrate integration with [CorrelationId](https://www.nuget.org/packages/CorrelationId/)
+ - Demonstrate use of Cancellation Tokens
+ - Add acceptance tests project
+ - Add dockerfile for api, console, aws and tests
+ - Demonstrate docker-compose executing acceptance tests against other images
+
## Contributing...
Please read the [contributing guide](./.github/CONTRIBUTING.md "Contributing to JustSaying").
diff --git a/samples/JustSaying.Sample.Restaurant.KitchenConsole/ConfigurationExtensions.cs b/samples/JustSaying.Sample.Restaurant.KitchenConsole/ConfigurationExtensions.cs
new file mode 100644
index 000000000..591bbb694
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.KitchenConsole/ConfigurationExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+using Amazon;
+using Microsoft.Extensions.Configuration;
+
+namespace JustSaying.Sample.Restaurant.KitchenConsole
+{
+ public static class ConfigurationExtensions
+ {
+ private const string AWSServiceUrlKey = "AWSServiceUrl";
+
+ private const string AWSRegionKey = "AWSRegion";
+
+ public static bool HasAWSServiceUrl(this IConfiguration configuration)
+ {
+ return !string.IsNullOrWhiteSpace(configuration[AWSServiceUrlKey]);
+ }
+
+ public static Uri GetAWSServiceUri(this IConfiguration configuration)
+ {
+ return new Uri(configuration[AWSServiceUrlKey]);
+ }
+
+ public static RegionEndpoint GetAWSRegion(this IConfiguration configuration)
+ {
+ return RegionEndpoint.GetBySystemName(configuration[AWSRegionKey]);
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.KitchenConsole/JustSaying.Sample.Restaurant.KitchenConsole.csproj b/samples/JustSaying.Sample.Restaurant.KitchenConsole/JustSaying.Sample.Restaurant.KitchenConsole.csproj
new file mode 100644
index 000000000..0112efa4c
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.KitchenConsole/JustSaying.Sample.Restaurant.KitchenConsole.csproj
@@ -0,0 +1,29 @@
+
+
+
+ Exe
+ netcoreapp2.2
+ $(NoWarn);CA1031;CA2007
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/JustSaying.Sample.Restaurant.KitchenConsole/OrderPlacedEventHandler.cs b/samples/JustSaying.Sample.Restaurant.KitchenConsole/OrderPlacedEventHandler.cs
new file mode 100644
index 000000000..3c4c5e15b
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.KitchenConsole/OrderPlacedEventHandler.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Threading.Tasks;
+using JustSaying.Messaging;
+using JustSaying.Messaging.MessageHandling;
+using JustSaying.Sample.Restaurant.Models;
+using Microsoft.Extensions.Logging;
+
+namespace JustSaying.Sample.Restaurant.KitchenConsole
+{
+ public class OrderPlacedEventHandler : IHandlerAsync
+ {
+ private readonly IMessagePublisher _publisher;
+ private readonly ILogger _logger;
+
+ ///
+ /// Handles messages of type OrderPlacedEvent
+ /// Takes a dependency on IMessagePublisher so that further messages can be published
+ ///
+ public OrderPlacedEventHandler(IMessagePublisher publisher, ILogger log)
+ {
+ _publisher = publisher;
+ _logger = log;
+ }
+
+ public async Task Handle(OrderPlacedEvent message)
+ {
+ // Returning true would indicate:
+ // The message was handled successfully
+ // The message can be removed from the queue.
+ // Returning false would indicate:
+ // The message was not handled successfully
+ // The message handling should be retried (configured by default)
+ // The message should be moved to the error queue if all retries fail
+
+ try
+ {
+ _logger.LogInformation("Order {orderId} for {description} received", message.OrderId, message.Description);
+
+ // This is where you would actually handle the order placement
+ // Intentionally left empty for the sake of this being a sample application
+
+ _logger.LogInformation("Order {orderId} ready", message.OrderId);
+
+ var orderReadyEvent = new OrderReadyEvent
+ {
+ OrderId = message.OrderId
+ };
+
+ await _publisher.PublishAsync(orderReadyEvent).ConfigureAwait(false);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to handle message for {orderId}", message.OrderId);
+ return false;
+ }
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.KitchenConsole/Program.cs b/samples/JustSaying.Sample.Restaurant.KitchenConsole/Program.cs
new file mode 100644
index 000000000..824e35f69
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.KitchenConsole/Program.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Threading.Tasks;
+using JustSaying.Sample.Restaurant.Models;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace JustSaying.Sample.Restaurant.KitchenConsole
+{
+ internal class Program
+ {
+ public static async Task Main()
+ {
+ Console.Title = "KitchenConsole";
+
+ await new HostBuilder()
+ .ConfigureAppConfiguration((hostContext, config) =>
+ {
+ config.AddJsonFile("appsettings.json", optional: false);
+ config.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true);
+ config.AddEnvironmentVariables();
+ })
+ .ConfigureLogging(loggingBuilder => loggingBuilder.AddConsole())
+ .ConfigureServices((hostContext, services) =>
+ {
+ var configuration = hostContext.Configuration;
+ services.AddJustSaying(config =>
+ {
+ config.Client(x =>
+ {
+ if (configuration.HasAWSServiceUrl())
+ {
+ // The AWS client SDK allows specifying a custom HTTP endpoint.
+ // For testing purposes it is useful to specify a value that
+ // points to a docker image such as `p4tin/goaws` or `localstack/localstack`
+ x.WithServiceUri(configuration.GetAWSServiceUri())
+ .WithAnonymousCredentials();
+ }
+ else
+ {
+ // The real AWS environment will require some means of authentication
+ //x.WithBasicCredentials("###", "###");
+ //x.WithSessionCredentials("###", "###", "###");
+ }
+ });
+
+ config.Messaging(x =>
+ {
+ // Configures which AWS Region to operate in
+ x.WithRegion(configuration.GetAWSRegion());
+ });
+
+ config.Subscriptions(x =>
+ {
+ // Creates the following if they do not already exist
+ // - a SQS queue of name `orderplacedevent`
+ // - a SQS queue of name `orderplacedevent_error`
+ // - a SNS topic of name `orderplacedevent`
+ // - a SNS topic subscription on topic 'orderplacedevent' and queue 'orderplacedevent'
+ x.ForTopic();
+ });
+
+ config.Publications(x =>
+ {
+ // Creates the following if they do not already exist
+ // - a SNS topic of name `orderreadyevent`
+ x.WithTopic();
+ });
+ });
+
+ // Added a message handler for message type for 'OrderPlacedEvent' on topic 'orderplacedevent' and queue 'orderplacedevent'
+ services.AddJustSayingHandler();
+
+ // Add a background service that is listening for messages related to the above subscriptions
+ services.AddHostedService();
+ })
+ .UseConsoleLifetime()
+ .Build()
+ .RunAsync();
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.KitchenConsole/Subscriber.cs b/samples/JustSaying.Sample.Restaurant.KitchenConsole/Subscriber.cs
new file mode 100644
index 000000000..21967ad64
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.KitchenConsole/Subscriber.cs
@@ -0,0 +1,32 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace JustSaying.Sample.Restaurant.KitchenConsole
+{
+ ///
+ /// A background service responsible for starting the bus which listens for
+ /// messages on the configured queues
+ ///
+ public class Subscriber : BackgroundService
+ {
+ private readonly IMessagingBus _bus;
+ private readonly ILogger _logger;
+
+ public Subscriber(IMessagingBus bus, ILogger logger)
+ {
+ _bus = bus;
+ _logger = logger;
+ }
+
+ protected override Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ _logger.LogInformation("Kitchen subscriber running");
+
+ _bus.Start(stoppingToken);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.KitchenConsole/appsettings.json b/samples/JustSaying.Sample.Restaurant.KitchenConsole/appsettings.json
new file mode 100644
index 000000000..ad9431e61
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.KitchenConsole/appsettings.json
@@ -0,0 +1,11 @@
+{
+ "AWSRegion": "eu-west-1",
+ "AWSServiceUrl": "http://localhost:4100",
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "System": "Warning",
+ "Microsoft": "Warning"
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.Models/JustSaying.Sample.Restaurant.Models.csproj b/samples/JustSaying.Sample.Restaurant.Models/JustSaying.Sample.Restaurant.Models.csproj
new file mode 100644
index 000000000..fcfb521ac
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.Models/JustSaying.Sample.Restaurant.Models.csproj
@@ -0,0 +1,11 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
diff --git a/samples/JustSaying.Sample.Restaurant.Models/OrderPlacedEvent.cs b/samples/JustSaying.Sample.Restaurant.Models/OrderPlacedEvent.cs
new file mode 100644
index 000000000..433a572d4
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.Models/OrderPlacedEvent.cs
@@ -0,0 +1,11 @@
+using JustSaying.Models;
+
+namespace JustSaying.Sample.Restaurant.Models
+{
+ public class OrderPlacedEvent : Message
+ {
+ public int OrderId { get; set; }
+
+ public string Description { get; set; }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.Models/OrderReadyEvent.cs b/samples/JustSaying.Sample.Restaurant.Models/OrderReadyEvent.cs
new file mode 100644
index 000000000..81dd41c70
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.Models/OrderReadyEvent.cs
@@ -0,0 +1,9 @@
+using JustSaying.Models;
+
+namespace JustSaying.Sample.Restaurant.Models
+{
+ public class OrderReadyEvent : Message
+ {
+ public int OrderId { get; set; }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.OrderingApi/ConfigurationExtensions.cs b/samples/JustSaying.Sample.Restaurant.OrderingApi/ConfigurationExtensions.cs
new file mode 100644
index 000000000..6a8177f8e
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.OrderingApi/ConfigurationExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+using Amazon;
+using Microsoft.Extensions.Configuration;
+
+namespace JustSaying.Sample.Restaurant.OrderingApi
+{
+ public static class ConfigurationExtensions
+ {
+ private const string AWSServiceUrlKey = "AWSServiceUrl";
+
+ private const string AWSRegionKey = "AWSRegion";
+
+ public static bool HasAWSServiceUrl(this IConfiguration configuration)
+ {
+ return !string.IsNullOrWhiteSpace(configuration[AWSServiceUrlKey]);
+ }
+
+ public static Uri GetAWSServiceUri(this IConfiguration configuration)
+ {
+ return new Uri(configuration[AWSServiceUrlKey]);
+ }
+
+ public static RegionEndpoint GetAWSRegion(this IConfiguration configuration)
+ {
+ return RegionEndpoint.GetBySystemName(configuration[AWSRegionKey]);
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.OrderingApi/Controllers/OrdersController.cs b/samples/JustSaying.Sample.Restaurant.OrderingApi/Controllers/OrdersController.cs
new file mode 100644
index 000000000..7391783c6
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.OrderingApi/Controllers/OrdersController.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Threading.Tasks;
+using JustSaying.Messaging;
+using JustSaying.Sample.Restaurant.Models;
+using JustSaying.Sample.Restaurant.OrderingApi.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+
+namespace JustSaying.Sample.Restaurant.OrderingApi.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class OrdersController : ControllerBase
+ {
+ private readonly IMessagePublisher _publisher;
+ private readonly ILogger _log;
+
+ public OrdersController(IMessagePublisher publisher, ILogger log)
+ {
+ _publisher = publisher;
+ _log = log;
+ }
+
+ // POST api/orders
+ [HttpPost]
+ public async Task PostAsync([FromBody] CustomerOrderModel order)
+ {
+ _log.LogInformation("Order received for {description}", order.Description);
+
+ // Save order to database generating OrderId
+ var orderId = new Random().Next(1, 100);
+
+ var message = new OrderPlacedEvent
+ {
+ OrderId = orderId,
+ Description = order.Description
+ };
+
+ await _publisher.PublishAsync(message);
+
+ _log.LogInformation("Order {orderId} placed", orderId);
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.OrderingApi/Handlers/OrderReadyEventHandler.cs b/samples/JustSaying.Sample.Restaurant.OrderingApi/Handlers/OrderReadyEventHandler.cs
new file mode 100644
index 000000000..a975faa2e
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.OrderingApi/Handlers/OrderReadyEventHandler.cs
@@ -0,0 +1,34 @@
+using System.Threading.Tasks;
+using JustSaying.Messaging.MessageHandling;
+using JustSaying.Sample.Restaurant.Models;
+using Microsoft.Extensions.Logging;
+
+namespace JustSaying.Sample.Restaurant.OrderingApi.Handlers
+{
+ public class OrderReadyEventHandler : IHandlerAsync
+ {
+ private readonly ILogger _log;
+
+ public OrderReadyEventHandler(ILogger log)
+ {
+ _log = log;
+ }
+
+ public Task Handle(OrderReadyEvent message)
+ {
+ _log.LogInformation("Order {orderId} ready", message.OrderId);
+
+ // This is where you would actually handle the order placement
+ // Intentionally left empty for the sake of this being a sample application
+
+ // Returning true would indicate:
+ // The message was handled successfully
+ // The message can be removed from the queue.
+ // Returning false would indicate:
+ // The message was not handled successfully
+ // The message handling should be retried (configured by default)
+ // The message should be moved to the error queue if all retries fail
+ return Task.FromResult(true);
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.OrderingApi/JustSaying.Sample.Restaurant.OrderingApi.csproj b/samples/JustSaying.Sample.Restaurant.OrderingApi/JustSaying.Sample.Restaurant.OrderingApi.csproj
new file mode 100644
index 000000000..9ba5b194e
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.OrderingApi/JustSaying.Sample.Restaurant.OrderingApi.csproj
@@ -0,0 +1,30 @@
+
+
+
+ netcoreapp2.2
+ InProcess
+ Linux
+ JustSaying.Sample.Restaurant.OrderingApi
+ $(NoWarn);CA2007
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/JustSaying.Sample.Restaurant.OrderingApi/Models/CustomerOrderModel.cs b/samples/JustSaying.Sample.Restaurant.OrderingApi/Models/CustomerOrderModel.cs
new file mode 100644
index 000000000..8bbe14b8e
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.OrderingApi/Models/CustomerOrderModel.cs
@@ -0,0 +1,7 @@
+namespace JustSaying.Sample.Restaurant.OrderingApi.Models
+{
+ public class CustomerOrderModel
+ {
+ public string Description { get; set; }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.OrderingApi/Program.cs b/samples/JustSaying.Sample.Restaurant.OrderingApi/Program.cs
new file mode 100644
index 000000000..b5f57e22c
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.OrderingApi/Program.cs
@@ -0,0 +1,24 @@
+using System;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace JustSaying.Sample.Restaurant.OrderingApi
+{
+ public static class Program
+ {
+ public static void Main(string[] args)
+ {
+ Console.Title = "OrderingApi";
+
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args)
+ {
+ return WebHost.CreateDefaultBuilder(args)
+ .ConfigureLogging((loggingBuilder) => loggingBuilder.AddConsole())
+ .UseStartup();
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.OrderingApi/Properties/launchSettings.json b/samples/JustSaying.Sample.Restaurant.OrderingApi/Properties/launchSettings.json
new file mode 100644
index 000000000..19dfdc450
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.OrderingApi/Properties/launchSettings.json
@@ -0,0 +1,36 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:52289",
+ "sslPort": 44386
+ }
+ },
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "ancmHostingModel": "InProcess",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "JustSaying.Sample.Restaurant.OrderingApi": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:5001;http://localhost:5000"
+ },
+ "Docker": {
+ "commandName": "Docker",
+ "launchBrowser": true,
+ "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}"
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.OrderingApi/Startup.cs b/samples/JustSaying.Sample.Restaurant.OrderingApi/Startup.cs
new file mode 100644
index 000000000..3d2ce87cb
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.OrderingApi/Startup.cs
@@ -0,0 +1,89 @@
+using JustSaying.Sample.Restaurant.Models;
+using JustSaying.Sample.Restaurant.OrderingApi.Handlers;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Swashbuckle.AspNetCore.Swagger;
+
+namespace JustSaying.Sample.Restaurant.OrderingApi
+{
+ public class Startup
+ {
+ private readonly IConfiguration _configuration;
+
+ public Startup(IConfiguration configuration)
+ {
+ _configuration = configuration;
+ }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
+
+ services.AddJustSaying(config =>
+ {
+ config.Client(x =>
+ {
+ if (_configuration.HasAWSServiceUrl())
+ {
+ // The AWS client SDK allows specifying a custom HTTP endpoint.
+ // For testing purposes it is useful to specify a value that
+ // points to a docker image such as `p4tin/goaws` or `localstack/localstack`
+ x.WithServiceUri(_configuration.GetAWSServiceUri())
+ .WithAnonymousCredentials();
+ }
+ else
+ {
+ // The real AWS environment will require some means of authentication
+ //x.WithBasicCredentials("###", "###");
+ //x.WithSessionCredentials("###", "###", "###");
+ }
+ });
+ config.Messaging(x =>
+ {
+ // Configures which AWS Region to operate in
+ x.WithRegion(_configuration.GetAWSRegion());
+ });
+ config.Subscriptions(x =>
+ {
+ // Creates the following if they do not already exist
+ // - a SQS queue of name `orderreadyevent`
+ // - a SQS queue of name `orderreadyevent_error`
+ // - a SNS topic of name `orderreadyevent`
+ // - a SNS topic subscription on topic 'orderreadyevent' and queue 'orderreadyevent'
+ x.ForTopic();
+ });
+ config.Publications(x =>
+ {
+ // Creates the following if they do not already exist
+ // - a SNS topic of name `orderplacedevent`
+ x.WithTopic();
+ });
+ });
+
+ // Added a message handler for message type for 'OrderReadyEvent' on topic 'orderreadyevent' and queue 'orderreadyevent'
+ services.AddJustSayingHandler();
+
+ // Add a background service that is listening for messages related to the above subscriptions
+ services.AddHostedService();
+
+ services.AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("v1", new Info { Title = "Restaurant Ordering API", Version = "v1" });
+ });
+ }
+
+ public static void Configure(IApplicationBuilder app)
+ {
+ app.UseSwagger();
+ app.UseSwaggerUI(c =>
+ {
+ c.SwaggerEndpoint("/swagger/v1/swagger.json", "Restaurant Ordering API");
+ c.RoutePrefix = string.Empty;
+ });
+ app.UseDeveloperExceptionPage();
+ app.UseMvc();
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.OrderingApi/Subscriber.cs b/samples/JustSaying.Sample.Restaurant.OrderingApi/Subscriber.cs
new file mode 100644
index 000000000..d16201e44
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.OrderingApi/Subscriber.cs
@@ -0,0 +1,32 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace JustSaying.Sample.Restaurant.OrderingApi
+{
+ ///
+ /// A background service responsible for starting the bus which listens for
+ /// messages on the configured queues
+ ///
+ public class Subscriber : BackgroundService
+ {
+ private readonly IMessagingBus _bus;
+ private readonly ILogger _logger;
+
+ public Subscriber(IMessagingBus bus, ILogger logger)
+ {
+ _bus = bus;
+ _logger = logger;
+ }
+
+ protected override Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ _logger.LogInformation("Ordering API subscriber running");
+
+ _bus.Start(stoppingToken);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/samples/JustSaying.Sample.Restaurant.OrderingApi/appsettings.json b/samples/JustSaying.Sample.Restaurant.OrderingApi/appsettings.json
new file mode 100644
index 000000000..0d09ab273
--- /dev/null
+++ b/samples/JustSaying.Sample.Restaurant.OrderingApi/appsettings.json
@@ -0,0 +1,12 @@
+{
+ "AWSRegion": "eu-west-1",
+ "AWSServiceUrl": "http://localhost:4100",
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "System": "Warning",
+ "Microsoft": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}