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": "*" +}