From aacbbc3bbae41eb1619b6e9eac7ed8b7559a401b Mon Sep 17 00:00:00 2001 From: martincostello Date: Wed, 21 Nov 2018 13:17:36 +0000 Subject: [PATCH 01/43] BusBuilder prototype first-step Add new BusBuilder class as a prototype of a replacement for CreateMeABus. --- JustSaying/BusBuilder.cs | 51 ++++++++++++++++++++++++++++++++++++++ JustSaying/CreateMeABus.cs | 4 ++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 JustSaying/BusBuilder.cs diff --git a/JustSaying/BusBuilder.cs b/JustSaying/BusBuilder.cs new file mode 100644 index 000000000..4efc9edf6 --- /dev/null +++ b/JustSaying/BusBuilder.cs @@ -0,0 +1,51 @@ +using System; +using JustSaying.AwsTools; +using Microsoft.Extensions.Logging; + +namespace JustSaying +{ + /// + /// A class representing a builder for JustSaying buses. + /// + public class BusBuilder + { + private IAwsClientFactory ClientFactory { get; set; } = new DefaultAwsClientFactory(); + + private ILoggerFactory LoggerFactory { get; set; } + + /// + /// Specifies the to use. + /// + /// The to use. + /// + /// The current . + /// + public BusBuilder WithClientFactory(IAwsClientFactory clientFactory) + { + ClientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory)); + return this; + } + + /// + /// Specifies the to use. + /// + /// The to use. + /// + /// The current . + /// + public BusBuilder WithLoggerFactory(ILoggerFactory loggerFactory) + { + LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + return this; + } + + /// + /// Create a new bus. + /// + /// + /// The to use to create the bus. + /// + public JustSayingFluentlyDependencies CreateMeABus() + => new JustSayingFluentlyDependencies() { LoggerFactory = LoggerFactory }; // TODO ClientFactory not used here yet - lots of rework to make this the root + } +} diff --git a/JustSaying/CreateMeABus.cs b/JustSaying/CreateMeABus.cs index 046e961ac..90d0dffbc 100644 --- a/JustSaying/CreateMeABus.cs +++ b/JustSaying/CreateMeABus.cs @@ -9,10 +9,12 @@ public static class CreateMeABus /// /// Allows to override default globally. /// + ////[Obsolete("Use the BusBuilder class to create message buses.")] public static Func DefaultClientFactory { get; set; } = () => new DefaultAwsClientFactory(); - public static JustSayingFluentlyDependencies WithLogging(ILoggerFactory loggerFactory) => + ////[Obsolete("Use the BusBuilder class to create message buses.")] + public static JustSayingFluentlyDependencies WithLogging(ILoggerFactory loggerFactory) => new JustSayingFluentlyDependencies { LoggerFactory = loggerFactory}; } } From f54a90211056d0bb383bba1dee92cb5eb3d2b612 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sat, 24 Nov 2018 13:33:04 +0000 Subject: [PATCH 02/43] Start to flesh out the builder Start to flesh out the implementation of the builder. Add new IMessagingBus interface for when just start/stop semantics are needed (e.g. by a host). --- .../Fluent/MessagingBusBuilderTests.cs | 30 ++ JustSaying/BusBuilder.cs | 51 --- JustSaying/Fluent/AwsClientFactoryBuilder.cs | 112 ++++++ JustSaying/Fluent/Builder`2.cs | 30 ++ JustSaying/Fluent/IBuilderChild.cs | 17 + JustSaying/Fluent/IBuilder`1.cs | 20 + JustSaying/Fluent/MessagingBusBuilder.cs | 151 +++++++ .../Fluent/MessagingConfigurationBuilder.cs | 371 ++++++++++++++++++ JustSaying/IMessagingBus.cs | 16 + JustSaying/JustSayingBus.cs | 2 +- 10 files changed, 748 insertions(+), 52 deletions(-) create mode 100644 JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs delete mode 100644 JustSaying/BusBuilder.cs create mode 100644 JustSaying/Fluent/AwsClientFactoryBuilder.cs create mode 100644 JustSaying/Fluent/Builder`2.cs create mode 100644 JustSaying/Fluent/IBuilderChild.cs create mode 100644 JustSaying/Fluent/IBuilder`1.cs create mode 100644 JustSaying/Fluent/MessagingBusBuilder.cs create mode 100644 JustSaying/Fluent/MessagingConfigurationBuilder.cs create mode 100644 JustSaying/IMessagingBus.cs diff --git a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs new file mode 100644 index 000000000..be0328a42 --- /dev/null +++ b/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs @@ -0,0 +1,30 @@ +using System.Threading; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace JustSaying.Fluent +{ + public static class MessagingBusBuilderTests + { + [Fact] + public static void Can_Create_Messaging_Bus_Fluently() + { + // Arrange + var builder = new MessagingBusBuilder() + .Client() + .WithBasicCredentials("accessKey", "secretKey") + .And().Parent + .Messaging() + .WithRegions("eu-west-1", "eu-central-1") + .And() + .WithActiveRegion("eu-west-1") + .And().Parent + .WithLoggerFactory(new LoggerFactory()); + + // Assert + IMessagingBus bus = builder.Build(); + + bus.Start(new CancellationToken(canceled: true)); + } + } +} diff --git a/JustSaying/BusBuilder.cs b/JustSaying/BusBuilder.cs deleted file mode 100644 index 4efc9edf6..000000000 --- a/JustSaying/BusBuilder.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using JustSaying.AwsTools; -using Microsoft.Extensions.Logging; - -namespace JustSaying -{ - /// - /// A class representing a builder for JustSaying buses. - /// - public class BusBuilder - { - private IAwsClientFactory ClientFactory { get; set; } = new DefaultAwsClientFactory(); - - private ILoggerFactory LoggerFactory { get; set; } - - /// - /// Specifies the to use. - /// - /// The to use. - /// - /// The current . - /// - public BusBuilder WithClientFactory(IAwsClientFactory clientFactory) - { - ClientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory)); - return this; - } - - /// - /// Specifies the to use. - /// - /// The to use. - /// - /// The current . - /// - public BusBuilder WithLoggerFactory(ILoggerFactory loggerFactory) - { - LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); - return this; - } - - /// - /// Create a new bus. - /// - /// - /// The to use to create the bus. - /// - public JustSayingFluentlyDependencies CreateMeABus() - => new JustSayingFluentlyDependencies() { LoggerFactory = LoggerFactory }; // TODO ClientFactory not used here yet - lots of rework to make this the root - } -} diff --git a/JustSaying/Fluent/AwsClientFactoryBuilder.cs b/JustSaying/Fluent/AwsClientFactoryBuilder.cs new file mode 100644 index 000000000..2907f63ea --- /dev/null +++ b/JustSaying/Fluent/AwsClientFactoryBuilder.cs @@ -0,0 +1,112 @@ +using System; +using Amazon.Runtime; +using JustSaying.AwsTools; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for instances of . This class cannot be inherited. + /// + public sealed class AwsClientFactoryBuilder : Builder, + IBuilderChild + { + /// + /// Initializes a new instance of the class. + /// + /// The that owns this instance. + internal AwsClientFactoryBuilder(MessagingBusBuilder parent) + { + Parent = parent; + } + + /// + public MessagingBusBuilder Parent { get; } + + /// + protected override AwsClientFactoryBuilder Self => this; + + /// + /// Gets or sets a delegate to use to create the to use. + /// + private Func ClientFactory { get; set; } + + /// + /// Gets or sets the to use. + /// + private AWSCredentials Credentials { get; set; } + + /// + /// Creates a new instance of . + /// + /// + /// The created instance of . + /// + public override IAwsClientFactory Build() + { + if (ClientFactory != null) + { + return ClientFactory(); + } + + return + Credentials == null ? + new DefaultAwsClientFactory() : + new DefaultAwsClientFactory(Credentials); + } + + /// + /// Specifies the to use. + /// + /// A delegate to a method to use to create an . + /// + /// The current . + /// + public AwsClientFactoryBuilder WithClientFactory(Func clientFactory) + { + ClientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory)); + return Self; + } + + /// + /// Specifies the to use. + /// + /// The to use. + /// + /// The current . + /// + public AwsClientFactoryBuilder WithCredentials(AWSCredentials credentials) + { + Credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); + return Self; + } + + /// + /// Specifies the basic AWS credentials to use. + /// + /// The access key to use. + /// The secret key to use. + /// + /// The current . + /// + public AwsClientFactoryBuilder WithBasicCredentials(string accessKey, string secretKey) + { + Credentials = new BasicAWSCredentials(accessKey, secretKey); + return Self; + } + + /// + /// Specifies the basic AWS credentials to use. + /// + /// The access key Id to use. + /// The secret access key to use. + /// The session token to use. + /// + /// The current . + /// + public AwsClientFactoryBuilder WithSessionCredentials(string accessKeyId, string secretAccessKey, string token) + { + Credentials = new SessionAWSCredentials(accessKeyId, secretAccessKey, token); + return Self; + } + } +} diff --git a/JustSaying/Fluent/Builder`2.cs b/JustSaying/Fluent/Builder`2.cs new file mode 100644 index 000000000..e587365bf --- /dev/null +++ b/JustSaying/Fluent/Builder`2.cs @@ -0,0 +1,30 @@ +namespace JustSaying.Fluent +{ + /// + /// The base class for implementations of . + /// + /// The type created by the builder. + /// The builder's own type. + public abstract class Builder : IBuilder + where T : class + where TBuilder : Builder + { + /// + /// Gets the current . + /// + public TBuilder And() => Self; + + /// + /// Creates a new instance of . + /// + /// + /// The created new instance of . + /// + public abstract T Build(); + + /// + /// Gets the current . + /// + protected abstract TBuilder Self { get; } + } +} diff --git a/JustSaying/Fluent/IBuilderChild.cs b/JustSaying/Fluent/IBuilderChild.cs new file mode 100644 index 000000000..1c6210322 --- /dev/null +++ b/JustSaying/Fluent/IBuilderChild.cs @@ -0,0 +1,17 @@ +namespace JustSaying.Fluent +{ + /// + /// Defines a builder that is owned by another builder. + /// + /// The type of the child builder. + /// The type of the parent builder. + public interface IBuilderChild : IBuilder + where TChild : class + where TParent : class + { + /// + /// Gets the parent of this builder. + /// + TParent Parent { get; } + } +} diff --git a/JustSaying/Fluent/IBuilder`1.cs b/JustSaying/Fluent/IBuilder`1.cs new file mode 100644 index 000000000..9e5b387e7 --- /dev/null +++ b/JustSaying/Fluent/IBuilder`1.cs @@ -0,0 +1,20 @@ +namespace JustSaying.Fluent +{ + /// + /// Defines a method for creating instances of types with a builder pattern. + /// + /// + /// The type created by the builder. + /// + public interface IBuilder + where T : class + { + /// + /// Creates a new instance of type . + /// + /// + /// The created instance of . + /// + T Build(); + } +} diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs new file mode 100644 index 000000000..c64dd3741 --- /dev/null +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -0,0 +1,151 @@ +using System; +using JustSaying.AwsTools.QueueCreation; +using JustSaying.Messaging.MessageSerialisation; +using Microsoft.Extensions.Logging; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for an . + /// + public class MessagingBusBuilder : Builder + { + /// + protected override MessagingBusBuilder Self => this; + + /// + /// Gets or sets the builder to use for creating an AWS client factory. + /// + private AwsClientFactoryBuilder ClientFactoryBuilder { get; set; } + + /// + /// Gets or sets the builder to use to configure messaging. + /// + private MessagingConfigurationBuilder MessagingConfig { get; set; } + + /// + /// Gets or sets a delegate to a method to create the to use. + /// + private Func LoggerFactory { get; set; } + + /// + /// Gets or sets a delegate to a method to create the to use. + /// + private Func NamingStrategy { get; set; } + + /// + /// Gets or sets a delegate to a method to create the to use. + /// + private Func SerializationRegister { get; set; } + + /// + /// Configures the factory for AWS clients. + /// + /// + /// The to use to configure the client. + /// + public AwsClientFactoryBuilder Client() + { + if (ClientFactoryBuilder == null) + { + ClientFactoryBuilder = new AwsClientFactoryBuilder(this); + } + + return ClientFactoryBuilder; + } + + /// + /// Configures messaging. + /// + /// + /// The to use to configure messaging. + /// + public MessagingConfigurationBuilder Messaging() + { + if (MessagingConfig == null) + { + MessagingConfig = new MessagingConfigurationBuilder(this); + } + + return MessagingConfig; + } + + /// + /// Specifies the to use. + /// + /// The to use. + /// + /// The current . + /// + public MessagingBusBuilder WithLoggerFactory(ILoggerFactory loggerFactory) + => WithLoggerFactory(() => loggerFactory); + + /// + /// Specifies the to use. + /// + /// A delegate to a method to get the to use. + /// + /// The current . + /// + public MessagingBusBuilder WithLoggerFactory(Func loggerFactory) + { + LoggerFactory = loggerFactory; + return this; + } + + /// + /// Specifies the to use. + /// + /// The to use. + /// + /// The current . + /// + public MessagingBusBuilder WithNamingStrategy(INamingStrategy strategy) + => WithNamingStrategy(() => strategy); + + /// + /// Specifies the to use. + /// + /// A delegate to a method to get the to use. + /// + /// The current . + /// + public MessagingBusBuilder WithNamingStrategy(Func strategy) + { + NamingStrategy = strategy; + return this; + } + + /// + /// Creates a new instance of . + /// + /// + /// The created instance of + /// + public override IMessagingBus Build() + { + IMessagingConfig config = Messaging().Build(); + config.Validate(); + + IMessageSerialisationRegister register = SerializationRegister?.Invoke() ?? new MessageSerialisationRegister(config.MessageSubjectProvider); + ILoggerFactory loggerFactory = LoggerFactory?.Invoke(); + + var bus = new JustSayingBus(config, register, loggerFactory); + + // TODO Remove the need to use the old fluent interface + // TODO Provide a way to configure these via this builder if needed? + var proxy = new AwsTools.AwsClientFactoryProxy(Client().Build); + var queueCreator = new AmazonQueueCreator(proxy, loggerFactory); + var fluent = new JustSayingFluently(bus, queueCreator, proxy, loggerFactory); + + if (NamingStrategy != null) + { + fluent.WithNamingStrategy(NamingStrategy); + } + + // TODO Subscriptions, handlers and publishers + + return bus; + } + } +} diff --git a/JustSaying/Fluent/MessagingConfigurationBuilder.cs b/JustSaying/Fluent/MessagingConfigurationBuilder.cs new file mode 100644 index 000000000..101f3d231 --- /dev/null +++ b/JustSaying/Fluent/MessagingConfigurationBuilder.cs @@ -0,0 +1,371 @@ +using System; +using System.Collections.Generic; +using Amazon; +using JustSaying.AwsTools.MessageHandling; +using JustSaying.Messaging.MessageSerialisation; +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for instances of . This class cannot be inherited. + /// + public sealed class MessagingConfigurationBuilder : Builder, + IBuilderChild + { + /// + /// Initializes a new instance of the class. + /// + /// The that owns this instance. + internal MessagingConfigurationBuilder(MessagingBusBuilder parent) + { + Parent = parent; + } + + /// + public MessagingBusBuilder Parent { get; } + + /// + protected override MessagingConfigurationBuilder Self => this; + + /// + /// Gets or sets the optional value to use for + /// + private List AdditionalSubscriberAccounts { get; set; } + + /// + /// Gets or sets the optional value to use for + /// + private Action MessageResponseLogger { get; set; } + + /// + /// Gets or sets the optional value to use for + /// + private TimeSpan? PublishFailureBackoff { get; set; } + + /// + /// Gets or sets the optional value to use for + /// + private int? PublishFailureReAttempts { get; set; } + + /// + /// Gets or sets the optional value to use for + /// + private Func GetActiveRegion { get; set; } + + /// + /// Gets or sets the optional value to use for + /// + private List Regions { get; set; } + + /// + /// Gets or sets the optional value to use for + /// + private IMessageSubjectProvider MessageSubjectProvider { get; set; } + + /// + /// Specifies the active AWS region to use. + /// + /// The active AWS region to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithActiveRegion(RegionEndpoint region) + { + if (region == null) + { + throw new ArgumentNullException(nameof(region)); + } + + return WithActiveRegion(region.SystemName); + } + + /// + /// Specifies the active AWS region to use. + /// + /// The active AWS region to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithActiveRegion(string region) + { + if (region == null) + { + throw new ArgumentNullException(nameof(region)); + } + + return WithActiveRegion(() => region); + } + + /// + /// Specifies a delgate which evaluates the current active AWS region to use. + /// + /// A delegate to a method with evaluates the active AWS region to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithActiveRegion(Func evaluator) + { + GetActiveRegion = evaluator ?? throw new ArgumentNullException(nameof(evaluator)); + return Self; + } + + /// + /// Specifies additional subscriber account(s) to use. + /// + /// The AWS account Id(s) to additionally subscribe to. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithAdditionalSubscriberAccounts(params string[] regions) + => WithAdditionalSubscriberAccounts(regions as IEnumerable); + + /// + /// Specifies additional subscriber account(s) to use. + /// + /// The AWS account Id(s) to additionally subscribe to. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithAdditionalSubscriberAccounts(IEnumerable accountIds) + { + if (accountIds == null) + { + throw new ArgumentNullException(nameof(accountIds)); + } + + AdditionalSubscriberAccounts = new List(accountIds); + return Self; + } + + /// + /// Specifies an additional subscriber account to use. + /// + /// The AWS account Id to additionally subscribe to. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithAdditionalSubscriberAccount(string accountId) + { + if (accountId == null) + { + throw new ArgumentNullException(nameof(accountId)); + } + + if (AdditionalSubscriberAccounts == null) + { + AdditionalSubscriberAccounts = new List(); + } + + AdditionalSubscriberAccounts.Add(accountId); + return Self; + } + + /// + /// Specifies a delegate to use to log message responses. + /// + /// A delegate to a method to use to log message responses. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithMessageResponseLogger(Action logger) + { + MessageResponseLogger = logger ?? throw new ArgumentNullException(nameof(logger)); + return Self; + } + + /// + /// Specifies the to use. + /// + /// The to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithMessageSubjectProvider(IMessageSubjectProvider subjectProvider) + { + MessageSubjectProvider = subjectProvider ?? throw new ArgumentNullException(nameof(subjectProvider)); + return Self; + } + + /// + /// Specifies the back-off period to use if message publishing fails. + /// + /// The back-off period to use. + /// + /// The current . + /// + public MessagingConfigurationBuilder WithPublishFailureBackoff(TimeSpan value) + { + PublishFailureBackoff = value; + return Self; + } + + /// + /// Specifies the number of publish re-attempts to use if message publishing fails. + /// + /// The number of re-attempts. + /// + /// The current . + /// + public MessagingConfigurationBuilder WithPublishFailureReattempts(int value) + { + PublishFailureReAttempts = value; + return Self; + } + + /// + /// Specifies the AWS region(s) to use. + /// + /// The AWS region(s) to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithRegions(params string[] regions) + => WithRegions(regions as IEnumerable); + + /// + /// Specifies the AWS region(s) to use. + /// + /// The AWS region(s) to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithRegions(IEnumerable regions) + { + if (regions == null) + { + throw new ArgumentNullException(nameof(regions)); + } + + Regions = new List(regions); + return Self; + } + + /// + /// Specifies an AWS region to use. + /// + /// The AWS region to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithRegion(string region) + { + if (region == null) + { + throw new ArgumentNullException(nameof(region)); + } + + if (Regions == null) + { + Regions = new List(); + } + + Regions.Add(region); + return Self; + } + + /// + /// Specifies an AWS region to use. + /// + /// The AWS region to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingConfigurationBuilder WithRegion(RegionEndpoint region) + { + if (region == null) + { + throw new ArgumentNullException(nameof(region)); + } + + return WithRegion(region.SystemName); + } + + /// + /// Creates a new instance of . + /// + /// + /// The created instance of . + /// + public override IMessagingConfig Build() + { + var config = new MessagingConfig(); + + if (Regions?.Count > 0) + { + foreach (string region in Regions) + { + config.Regions.Add(region); + } + } + + if (AdditionalSubscriberAccounts?.Count > 0) + { + config.AdditionalSubscriberAccounts = AdditionalSubscriberAccounts; + } + + if (GetActiveRegion != null) + { + config.GetActiveRegion = GetActiveRegion; + } + + if (MessageResponseLogger != null) + { + config.MessageResponseLogger = MessageResponseLogger; + } + + if (MessageSubjectProvider != null) + { + config.MessageSubjectProvider = MessageSubjectProvider; + } + + if (PublishFailureBackoff.HasValue) + { + config.PublishFailureBackoff = PublishFailureBackoff.Value; + } + + if (PublishFailureReAttempts.HasValue) + { + config.PublishFailureReAttempts = PublishFailureReAttempts.Value; + } + + return config; + } + } +} diff --git a/JustSaying/IMessagingBus.cs b/JustSaying/IMessagingBus.cs new file mode 100644 index 000000000..485d20f92 --- /dev/null +++ b/JustSaying/IMessagingBus.cs @@ -0,0 +1,16 @@ +using System.Threading; + +namespace JustSaying +{ + /// + /// Defines a messaging bus. + /// + public interface IMessagingBus + { + /// + /// Starts the message bus. + /// + /// A which will stop the bus when signalled. + void Start(CancellationToken cancellationToken); + } +} diff --git a/JustSaying/JustSayingBus.cs b/JustSaying/JustSayingBus.cs index a364621a3..27734a2fc 100644 --- a/JustSaying/JustSayingBus.cs +++ b/JustSaying/JustSayingBus.cs @@ -15,7 +15,7 @@ namespace JustSaying { - public sealed class JustSayingBus : IAmJustSaying, IAmJustInterrogating + public sealed class JustSayingBus : IAmJustSaying, IAmJustInterrogating, IMessagingBus { private readonly Dictionary> _subscribersByRegionAndQueue; private readonly Dictionary> _publishersByRegionAndTopic; From d2495ff8c7f2f4a3997858adebd5d312cc7b4836 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sat, 24 Nov 2018 16:19:49 +0000 Subject: [PATCH 03/43] Support DI and subscriptions Initial working skeleton of plumbing in dependency injection and registering message handler subscriptions. The Microsoft-specific DI methods are being added to JustSaying itself for now while I'm hacking away at this prototype. --- .../Fluent/MessagingBusBuilderTests.cs | 75 +++++++++-- .../JustSaying.UnitTests.csproj | 3 +- .../Fluent/IServiceCollectionExtensions.cs | 105 +++++++++++++++ JustSaying/Fluent/IServiceResolver.cs | 19 +++ JustSaying/Fluent/MessagingBusBuilder.cs | 126 ++++++++++++++++-- .../Fluent/MessagingConfigurationBuilder.cs | 2 +- JustSaying/Fluent/ServiceProviderResolver.cs | 35 +++++ JustSaying/Fluent/SubscriptionBuilder.cs | 59 ++++++++ JustSaying/IMessagingConfig.cs | 2 +- JustSaying/JustSaying.csproj | 2 + 10 files changed, 401 insertions(+), 27 deletions(-) create mode 100644 JustSaying/Fluent/IServiceCollectionExtensions.cs create mode 100644 JustSaying/Fluent/IServiceResolver.cs create mode 100644 JustSaying/Fluent/ServiceProviderResolver.cs create mode 100644 JustSaying/Fluent/SubscriptionBuilder.cs diff --git a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs index be0328a42..d36d39540 100644 --- a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs @@ -1,30 +1,79 @@ +using System; using System.Threading; +using System.Threading.Tasks; +using JustSaying.Messaging.MessageHandling; +using JustSaying.Models; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Xunit; +using Xunit.Abstractions; namespace JustSaying.Fluent { - public static class MessagingBusBuilderTests + public class MessagingBusBuilderTests { + public MessagingBusBuilderTests(ITestOutputHelper outputHelper) + { + OutputHelper = outputHelper; + } + + private ITestOutputHelper OutputHelper { get; } + [Fact] - public static void Can_Create_Messaging_Bus_Fluently() + public void Can_Create_Messaging_Bus_Fluently() { // Arrange - var builder = new MessagingBusBuilder() - .Client() - .WithBasicCredentials("accessKey", "secretKey") - .And().Parent - .Messaging() - .WithRegions("eu-west-1", "eu-central-1") - .And() - .WithActiveRegion("eu-west-1") - .And().Parent - .WithLoggerFactory(new LoggerFactory()); + var services = new ServiceCollection() + .AddLogging((p) => p.AddXUnit(OutputHelper)) + .AddJustSaying( + (builder) => + { + builder.Client() + .WithBasicCredentials("accessKey", "secretKey") + .And().Parent + .Messaging() + .WithRegions("eu-west-1", "eu-central-1") + .And() + .WithActiveRegion("eu-west-1") + .And().Parent + .Subscriptions() + .WithHandler(); + }) + .AddJustSayingHandler(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); // Assert - IMessagingBus bus = builder.Build(); + var bus = serviceProvider.GetRequiredService(); + bus.Start(new CancellationToken(canceled: true)); + } + [Fact] + public void Can_Create_Messaging_Bus() + { + // Arrange + var services = new ServiceCollection() + .AddLogging((p) => p.AddXUnit(OutputHelper)) + .AddJustSaying("eu-west-1") + .AddJustSayingHandler(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + // Assert + var bus = serviceProvider.GetRequiredService(); bus.Start(new CancellationToken(canceled: true)); } + + private sealed class MyMessage : Message + { + } + + private sealed class MyHandler : IHandlerAsync + { + public Task Handle(MyMessage message) + { + return Task.FromResult(true); + } + } } } diff --git a/JustSaying.UnitTests/JustSaying.UnitTests.csproj b/JustSaying.UnitTests/JustSaying.UnitTests.csproj index d72428820..b24b9baac 100644 --- a/JustSaying.UnitTests/JustSaying.UnitTests.csproj +++ b/JustSaying.UnitTests/JustSaying.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 @@ -15,6 +15,7 @@ + diff --git a/JustSaying/Fluent/IServiceCollectionExtensions.cs b/JustSaying/Fluent/IServiceCollectionExtensions.cs new file mode 100644 index 000000000..7121cd782 --- /dev/null +++ b/JustSaying/Fluent/IServiceCollectionExtensions.cs @@ -0,0 +1,105 @@ +using System; +using JustSaying.AwsTools; +using JustSaying.AwsTools.QueueCreation; +using JustSaying.Messaging.MessageHandling; +using JustSaying.Messaging.MessageSerialisation; +using JustSaying.Models; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace JustSaying.Fluent +{ + public static class IServiceCollectionExtensions + { + // TODO This is here for convenience while protyping, would probably live elsewhere + // so we don't need to force the dependency on MS' DI types + + public static IServiceCollection AddJustSaying(this IServiceCollection services, params string[] regions) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (regions == null) + { + throw new ArgumentNullException(nameof(regions)); + } + + return services.AddJustSaying((builder) => builder.Messaging().WithRegions(regions)); + } + + public static IServiceCollection AddJustSaying(this IServiceCollection services, Action configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + return services.AddJustSaying((builder, _) => configure(builder)); + } + + public static IServiceCollection AddJustSaying(this IServiceCollection services, Action configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + // Register as self so the same singleton instance implements two different interfaces + services.TryAddSingleton((p) => new ServiceProviderResolver(p)); + services.TryAddSingleton((p) => p.GetRequiredService()); + services.TryAddSingleton((p) => p.GetRequiredService()); + + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton((p) => new AwsClientFactoryProxy(p.GetRequiredService)); + services.TryAddSingleton(); + services.TryAddSingleton(); + + services.TryAddSingleton( + (p) => + { + var config = p.GetRequiredService(); + return new MessageSerialisationRegister(config.MessageSubjectProvider); + }); + + services.TryAddSingleton( + (serviceProvider) => + { + var builder = serviceProvider + .GetRequiredService() + .WithServiceResolver(new ServiceProviderResolver(serviceProvider)); + + configure(builder, serviceProvider); + + return builder.Build(); + }); + + return services; + } + + public static IServiceCollection AddJustSayingHandler(this IServiceCollection services) + where TMessage : Message + where THandler : class, IHandlerAsync + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.TryAddTransient, THandler>(); + return services; + } + } +} diff --git a/JustSaying/Fluent/IServiceResolver.cs b/JustSaying/Fluent/IServiceResolver.cs new file mode 100644 index 000000000..7d68f9c43 --- /dev/null +++ b/JustSaying/Fluent/IServiceResolver.cs @@ -0,0 +1,19 @@ +namespace JustSaying.Fluent +{ + /// + /// Defines a method for resolving instances of types from a depenency injection container. + /// + public interface IServiceResolver + { + /// + /// Resolves an instance of the specified type. + /// + /// + /// The type to resolve an instance of. + /// + /// + /// The resolved instance of . + /// + T ResolveService(); + } +} diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index c64dd3741..46e497d6b 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -1,4 +1,5 @@ using System; +using JustSaying.AwsTools; using JustSaying.AwsTools.QueueCreation; using JustSaying.Messaging.MessageSerialisation; using Microsoft.Extensions.Logging; @@ -10,6 +11,11 @@ namespace JustSaying.Fluent /// public class MessagingBusBuilder : Builder { + /// + /// Gets the optional to use. + /// + internal IServiceResolver ServiceResolver { get; private set; } + /// protected override MessagingBusBuilder Self => this; @@ -23,6 +29,11 @@ public class MessagingBusBuilder : Builder /// private MessagingConfigurationBuilder MessagingConfig { get; set; } + /// + /// Gets or sets the builder to use for subscriptions. + /// + private SubscriptionBuilder SubscriptionBuilder { get; set; } + /// /// Gets or sets a delegate to a method to create the to use. /// @@ -70,6 +81,22 @@ public MessagingConfigurationBuilder Messaging() return MessagingConfig; } + /// + /// Configures the subscriptions. + /// + /// + /// The to use to configure the subscriptions. + /// + public SubscriptionBuilder Subscriptions() + { + if (SubscriptionBuilder == null) + { + SubscriptionBuilder = new SubscriptionBuilder(this); + } + + return SubscriptionBuilder; + } + /// /// Specifies the to use. /// @@ -116,6 +143,19 @@ public MessagingBusBuilder WithNamingStrategy(Func strategy) return this; } + /// + /// Specifies the to use. + /// + /// The to use. + /// + /// The current . + /// + public MessagingBusBuilder WithServiceResolver(IServiceResolver serviceResolver) + { + ServiceResolver = serviceResolver; + return Self; + } + /// /// Creates a new instance of . /// @@ -124,28 +164,92 @@ public MessagingBusBuilder WithNamingStrategy(Func strategy) /// public override IMessagingBus Build() { - IMessagingConfig config = Messaging().Build(); - config.Validate(); + IMessagingConfig config = CreateConfig(); - IMessageSerialisationRegister register = SerializationRegister?.Invoke() ?? new MessageSerialisationRegister(config.MessageSubjectProvider); - ILoggerFactory loggerFactory = LoggerFactory?.Invoke(); + config.Validate(); - var bus = new JustSayingBus(config, register, loggerFactory); + ILoggerFactory loggerFactory = + LoggerFactory?.Invoke() ?? ServiceResolver?.ResolveService() ?? new NullLoggerFactory(); - // TODO Remove the need to use the old fluent interface - // TODO Provide a way to configure these via this builder if needed? - var proxy = new AwsTools.AwsClientFactoryProxy(Client().Build); - var queueCreator = new AmazonQueueCreator(proxy, loggerFactory); - var fluent = new JustSayingFluently(bus, queueCreator, proxy, loggerFactory); + JustSayingBus bus = CreateBus(config, loggerFactory); + JustSayingFluently fluent = CreateFluent(bus, loggerFactory); if (NamingStrategy != null) { fluent.WithNamingStrategy(NamingStrategy); } - // TODO Subscriptions, handlers and publishers + // TODO Publishers + // TODO Where do topic/queue names come in? + if (SubscriptionBuilder != null) + { + SubscriptionBuilder.Configure(fluent); + } return bus; } + + private JustSayingBus CreateBus(IMessagingConfig config, ILoggerFactory loggerFactory) + { + IMessageSerialisationRegister register = + SerializationRegister?.Invoke() ?? ServiceResolver?.ResolveService() ?? new MessageSerialisationRegister(config.MessageSubjectProvider); + + return new JustSayingBus(config, register, loggerFactory); + } + + private IMessagingConfig CreateConfig() + { + return MessagingConfig != null ? + MessagingConfig.Build() : + ServiceResolver?.ResolveService() ?? new MessagingConfig(); + } + + private IAwsClientFactoryProxy CreateFactoryProxy() + { + return ClientFactoryBuilder != null ? + new AwsClientFactoryProxy(ClientFactoryBuilder.Build) : + ServiceResolver?.ResolveService() ?? new AwsClientFactoryProxy(); + } + + private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory loggerFactory) + { + IAwsClientFactoryProxy proxy = CreateFactoryProxy(); + IVerifyAmazonQueues queueCreator = ServiceResolver?.ResolveService() ?? new AmazonQueueCreator(proxy, loggerFactory); + + return new JustSayingFluently(bus, queueCreator, proxy, loggerFactory); + } + + private sealed class NullLoggerFactory : ILoggerFactory + { + public void AddProvider(ILoggerProvider provider) + { + } + + public ILogger CreateLogger(string categoryName) + { + return new NullLogger(); + } + + public void Dispose() + { + } + + private sealed class NullLogger : ILogger + { + public IDisposable BeginScope(TState state) + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return false; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + } + } + } } } diff --git a/JustSaying/Fluent/MessagingConfigurationBuilder.cs b/JustSaying/Fluent/MessagingConfigurationBuilder.cs index 101f3d231..5dfe7c7f6 100644 --- a/JustSaying/Fluent/MessagingConfigurationBuilder.cs +++ b/JustSaying/Fluent/MessagingConfigurationBuilder.cs @@ -325,7 +325,7 @@ public MessagingConfigurationBuilder WithRegion(RegionEndpoint region) /// public override IMessagingConfig Build() { - var config = new MessagingConfig(); + var config = Parent.ServiceResolver?.ResolveService() ?? new MessagingConfig(); if (Regions?.Count > 0) { diff --git a/JustSaying/Fluent/ServiceProviderResolver.cs b/JustSaying/Fluent/ServiceProviderResolver.cs new file mode 100644 index 000000000..43a70fd23 --- /dev/null +++ b/JustSaying/Fluent/ServiceProviderResolver.cs @@ -0,0 +1,35 @@ +using System; +using JustSaying.Messaging.MessageHandling; +using Microsoft.Extensions.DependencyInjection; + +namespace JustSaying.Fluent +{ + /// + /// A class that implements and + /// for . This class cannot be inherited. + /// + internal sealed class ServiceProviderResolver : IServiceResolver, IHandlerResolver + { + /// + /// Initializes a new instance of the class. + /// + /// The to use. + internal ServiceProviderResolver(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + /// + /// Gets the to use. + /// + private IServiceProvider ServiceProvider { get; } + + /// + public IHandlerAsync ResolveHandler(HandlerResolutionContext context) + => ServiceProvider.GetRequiredService>(); + + /// + public T ResolveService() + => ServiceProvider.GetRequiredService(); + } +} diff --git a/JustSaying/Fluent/SubscriptionBuilder.cs b/JustSaying/Fluent/SubscriptionBuilder.cs new file mode 100644 index 000000000..0a3b7e0dd --- /dev/null +++ b/JustSaying/Fluent/SubscriptionBuilder.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for subscriptions. This class cannot be inherited. + /// + public sealed class SubscriptionBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The that owns this instance. + internal SubscriptionBuilder(MessagingBusBuilder parent) + { + Parent = parent; + } + + /// + /// Gets the parent of this builder. + /// + public MessagingBusBuilder Parent { get; } + + /// + /// Gets the configured handler registrations. + /// + private IList> Registrations = new List>(); + + /// + /// Registers a message handler. + /// + /// The message type to register a handler for. + /// + /// The current . + /// + public SubscriptionBuilder WithHandler() + where T : Message + { + Registrations.Add((p, resolver) => p.WithMessageHandler(resolver)); + return this; + } + + /// + /// Configures the subscriptions for the . + /// + /// The to configure subscriptions for. + internal void Configure(JustSayingFluently bus) + { + IHandlerResolver resolver = Parent.ServiceResolver.ResolveService(); + + foreach (Action registration in Registrations) + { + registration(bus, resolver); + } + } + } +} diff --git a/JustSaying/IMessagingConfig.cs b/JustSaying/IMessagingConfig.cs index ae5d66482..a8363075a 100644 --- a/JustSaying/IMessagingConfig.cs +++ b/JustSaying/IMessagingConfig.cs @@ -8,7 +8,7 @@ public interface IMessagingConfig : IPublishConfiguration //ToDo: This vs publis { IList Regions { get; } Func GetActiveRegion { get; set; } - IMessageSubjectProvider MessageSubjectProvider { get; } + IMessageSubjectProvider MessageSubjectProvider { get; set; } void Validate(); } diff --git a/JustSaying/JustSaying.csproj b/JustSaying/JustSaying.csproj index 99fc66104..f6d7bda39 100644 --- a/JustSaying/JustSaying.csproj +++ b/JustSaying/JustSaying.csproj @@ -12,9 +12,11 @@ + + From b8c17a1563b8eb36161b3c7b00891d2484960f62 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 09:53:43 +0000 Subject: [PATCH 04/43] Use nested builders Configure builders below the message bus builders using actions, rather than with child builders. --- .../Fluent/MessagingBusBuilderTests.cs | 13 ++--- JustSaying/Fluent/AwsClientFactoryBuilder.cs | 11 ++-- JustSaying/Fluent/IBuilderChild.cs | 17 ------- .../Fluent/IServiceCollectionExtensions.cs | 7 ++- JustSaying/Fluent/MessagingBusBuilder.cs | 51 +++++++++++++++---- .../Fluent/MessagingConfigurationBuilder.cs | 13 +++-- 6 files changed, 62 insertions(+), 50 deletions(-) delete mode 100644 JustSaying/Fluent/IBuilderChild.cs diff --git a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs index d36d39540..4d2b4de81 100644 --- a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs @@ -28,16 +28,9 @@ public void Can_Create_Messaging_Bus_Fluently() .AddJustSaying( (builder) => { - builder.Client() - .WithBasicCredentials("accessKey", "secretKey") - .And().Parent - .Messaging() - .WithRegions("eu-west-1", "eu-central-1") - .And() - .WithActiveRegion("eu-west-1") - .And().Parent - .Subscriptions() - .WithHandler(); + builder.Client((options) => options.WithBasicCredentials("accessKey", "secretKey")) + .Messaging((options) => options.WithRegions("eu-west-1", "eu-central-1").And().WithActiveRegion("eu-west-1")) + .Subscriptions((options) => options.WithHandler()); }) .AddJustSayingHandler(); diff --git a/JustSaying/Fluent/AwsClientFactoryBuilder.cs b/JustSaying/Fluent/AwsClientFactoryBuilder.cs index 2907f63ea..b317ce47b 100644 --- a/JustSaying/Fluent/AwsClientFactoryBuilder.cs +++ b/JustSaying/Fluent/AwsClientFactoryBuilder.cs @@ -7,20 +7,19 @@ namespace JustSaying.Fluent /// /// A class representing a builder for instances of . This class cannot be inherited. /// - public sealed class AwsClientFactoryBuilder : Builder, - IBuilderChild + public sealed class AwsClientFactoryBuilder : Builder { /// /// Initializes a new instance of the class. /// - /// The that owns this instance. - internal AwsClientFactoryBuilder(MessagingBusBuilder parent) + /// The that owns this instance. + internal AwsClientFactoryBuilder(MessagingBusBuilder busBuilder) { - Parent = parent; + BusBuilder = busBuilder; } /// - public MessagingBusBuilder Parent { get; } + public MessagingBusBuilder BusBuilder { get; } /// protected override AwsClientFactoryBuilder Self => this; diff --git a/JustSaying/Fluent/IBuilderChild.cs b/JustSaying/Fluent/IBuilderChild.cs deleted file mode 100644 index 1c6210322..000000000 --- a/JustSaying/Fluent/IBuilderChild.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace JustSaying.Fluent -{ - /// - /// Defines a builder that is owned by another builder. - /// - /// The type of the child builder. - /// The type of the parent builder. - public interface IBuilderChild : IBuilder - where TChild : class - where TParent : class - { - /// - /// Gets the parent of this builder. - /// - TParent Parent { get; } - } -} diff --git a/JustSaying/Fluent/IServiceCollectionExtensions.cs b/JustSaying/Fluent/IServiceCollectionExtensions.cs index 7121cd782..1af26f31a 100644 --- a/JustSaying/Fluent/IServiceCollectionExtensions.cs +++ b/JustSaying/Fluent/IServiceCollectionExtensions.cs @@ -26,7 +26,12 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, throw new ArgumentNullException(nameof(regions)); } - return services.AddJustSaying((builder) => builder.Messaging().WithRegions(regions)); + return services + .AddJustSaying( + (builder) => + { + builder.Messaging((options) => options.WithRegions(regions)); + }); } public static IServiceCollection AddJustSaying(this IServiceCollection services, Action configure) diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index 46e497d6b..6d59d6806 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -52,49 +52,82 @@ public class MessagingBusBuilder : Builder /// /// Configures the factory for AWS clients. /// + /// A delegate to a method to use to configure the AWS clients. /// - /// The to use to configure the client. + /// The current . /// - public AwsClientFactoryBuilder Client() + /// + /// is . + /// + public MessagingBusBuilder Client(Action configure) { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + if (ClientFactoryBuilder == null) { ClientFactoryBuilder = new AwsClientFactoryBuilder(this); } - return ClientFactoryBuilder; + configure(ClientFactoryBuilder); + + return Self; } /// /// Configures messaging. /// + /// A delegate to a method to use to configure messaging. /// - /// The to use to configure messaging. + /// The current . /// - public MessagingConfigurationBuilder Messaging() + /// + /// is . + /// + public MessagingBusBuilder Messaging(Action configure) { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + if (MessagingConfig == null) { MessagingConfig = new MessagingConfigurationBuilder(this); } - return MessagingConfig; + configure(MessagingConfig); + + return Self; } /// /// Configures the subscriptions. /// + /// A delegate to a method to use to configure subscriptions. /// - /// The to use to configure the subscriptions. + /// The current . /// - public SubscriptionBuilder Subscriptions() + /// + /// is . + /// + public MessagingBusBuilder Subscriptions(Action configure) { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + if (SubscriptionBuilder == null) { SubscriptionBuilder = new SubscriptionBuilder(this); } - return SubscriptionBuilder; + configure(SubscriptionBuilder); + + return Self; } /// diff --git a/JustSaying/Fluent/MessagingConfigurationBuilder.cs b/JustSaying/Fluent/MessagingConfigurationBuilder.cs index 5dfe7c7f6..8530e460c 100644 --- a/JustSaying/Fluent/MessagingConfigurationBuilder.cs +++ b/JustSaying/Fluent/MessagingConfigurationBuilder.cs @@ -10,20 +10,19 @@ namespace JustSaying.Fluent /// /// A class representing a builder for instances of . This class cannot be inherited. /// - public sealed class MessagingConfigurationBuilder : Builder, - IBuilderChild + public sealed class MessagingConfigurationBuilder : Builder { /// /// Initializes a new instance of the class. /// - /// The that owns this instance. - internal MessagingConfigurationBuilder(MessagingBusBuilder parent) + /// The that owns this instance. + internal MessagingConfigurationBuilder(MessagingBusBuilder busBuilder) { - Parent = parent; + BusBuilder = busBuilder; } /// - public MessagingBusBuilder Parent { get; } + public MessagingBusBuilder BusBuilder { get; } /// protected override MessagingConfigurationBuilder Self => this; @@ -325,7 +324,7 @@ public MessagingConfigurationBuilder WithRegion(RegionEndpoint region) /// public override IMessagingConfig Build() { - var config = Parent.ServiceResolver?.ResolveService() ?? new MessagingConfig(); + var config = BusBuilder.ServiceResolver?.ResolveService() ?? new MessagingConfig(); if (Regions?.Count > 0) { From 71afc43ef201bbb741b5919bb89e8f5127f90793 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 10:21:46 +0000 Subject: [PATCH 05/43] Use region parameter Use the region parameter when creating client configurations. --- JustSaying.TestingFramework/LocalAwsClientFactory.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/JustSaying.TestingFramework/LocalAwsClientFactory.cs b/JustSaying.TestingFramework/LocalAwsClientFactory.cs index 4d35dd20a..077af89bd 100644 --- a/JustSaying.TestingFramework/LocalAwsClientFactory.cs +++ b/JustSaying.TestingFramework/LocalAwsClientFactory.cs @@ -21,6 +21,7 @@ public IAmazonSimpleNotificationService GetSnsClient(RegionEndpoint region) var credentials = new AnonymousAWSCredentials(); var clientConfig = new AmazonSimpleNotificationServiceConfig { + RegionEndpoint = region, ServiceURL = ServiceUrl.ToString() }; @@ -32,6 +33,7 @@ public IAmazonSQS GetSqsClient(RegionEndpoint region) var credentials = new AnonymousAWSCredentials(); var clientConfig = new AmazonSQSConfig { + RegionEndpoint = region, ServiceURL = ServiceUrl.ToString() }; From 8acc1ae3895758920828d486c7992819636396c0 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 10:22:21 +0000 Subject: [PATCH 06/43] Add missing DI registrations Add registrations for IMessageMonitor and IMessageSerialisationFactory when configuring the bus. --- JustSaying/Fluent/IServiceCollectionExtensions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/JustSaying/Fluent/IServiceCollectionExtensions.cs b/JustSaying/Fluent/IServiceCollectionExtensions.cs index 1af26f31a..e1e851458 100644 --- a/JustSaying/Fluent/IServiceCollectionExtensions.cs +++ b/JustSaying/Fluent/IServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using JustSaying.AwsTools.QueueCreation; using JustSaying.Messaging.MessageHandling; using JustSaying.Messaging.MessageSerialisation; +using JustSaying.Messaging.Monitoring; using JustSaying.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -70,8 +71,9 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, services.TryAddSingleton(); services.TryAddSingleton((p) => new AwsClientFactoryProxy(p.GetRequiredService)); services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton( (p) => { From 1a3b70f933c06ae0a61ca7e94e242454d2d5a451 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 10:25:26 +0000 Subject: [PATCH 07/43] Add builder method for service URI Add a method to the builder to allow the AWS endpoint to be configured, which makes it easier to configure things like emulators --- JustSaying/Fluent/AwsClientFactoryBuilder.cs | 78 +++++++++++++++++++- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/JustSaying/Fluent/AwsClientFactoryBuilder.cs b/JustSaying/Fluent/AwsClientFactoryBuilder.cs index b317ce47b..4adf2b5ba 100644 --- a/JustSaying/Fluent/AwsClientFactoryBuilder.cs +++ b/JustSaying/Fluent/AwsClientFactoryBuilder.cs @@ -34,6 +34,11 @@ internal AwsClientFactoryBuilder(MessagingBusBuilder busBuilder) /// private AWSCredentials Credentials { get; set; } + /// + /// Gets or sets the URI for the AWS services to use. + /// + private Uri ServiceUri { get; set; } + /// /// Creates a new instance of . /// @@ -47,10 +52,20 @@ public override IAwsClientFactory Build() return ClientFactory(); } - return - Credentials == null ? - new DefaultAwsClientFactory() : - new DefaultAwsClientFactory(Credentials); + DefaultAwsClientFactory factory; + + if (Credentials == null) + { + factory = new DefaultAwsClientFactory(); + } + else + { + factory = new DefaultAwsClientFactory(Credentials); + } + + factory.ServiceUri = ServiceUri; + + return factory; } /// @@ -107,5 +122,60 @@ public AwsClientFactoryBuilder WithSessionCredentials(string accessKeyId, string Credentials = new SessionAWSCredentials(accessKeyId, secretAccessKey, token); return Self; } + + /// + /// Specifies the AWS service URL to use. + /// + /// The URL to use for AWS services. + /// + /// The current . + /// + /// + /// is . + /// + /// + /// is not an absolute URI. + /// +#pragma warning disable CA1054 // Uri parameters should not be strings + public AwsClientFactoryBuilder WithServiceUrl(string url) +#pragma warning restore CA1054 // Uri parameters should not be strings + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + + return WithServiceUri(new Uri(url, UriKind.Absolute)); + } + + /// + /// Specifies the AWS service URI to use. + /// + /// The URI to use for AWS services. + /// + /// The current . + /// + /// + /// is . + /// + /// + /// is not an absolute URI. + /// + public AwsClientFactoryBuilder WithServiceUri(Uri uri) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + if (!uri.IsAbsoluteUri) + { + throw new ArgumentException("The AWS service URI must be an absolute URI.", nameof(uri)); + } + + ServiceUri = uri; + + return Self; + } } } From 96dfa3121c5a4e284f3df35d873d1958ddeffe06 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 10:26:03 +0000 Subject: [PATCH 08/43] Use emulator Temporarily use the emulator URI (I've got it running locally) to test the builder stuff. --- JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs index 4d2b4de81..96cd07286 100644 --- a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs @@ -28,7 +28,7 @@ public void Can_Create_Messaging_Bus_Fluently() .AddJustSaying( (builder) => { - builder.Client((options) => options.WithBasicCredentials("accessKey", "secretKey")) + builder.Client((options) => options.WithBasicCredentials("accessKey", "secretKey").And().WithServiceUrl("http://localhost:4100")) .Messaging((options) => options.WithRegions("eu-west-1", "eu-central-1").And().WithActiveRegion("eu-west-1")) .Subscriptions((options) => options.WithHandler()); }) From 5d45a73a398b193f73aa52b28e1b8713bf1bdc41 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 10:26:41 +0000 Subject: [PATCH 09/43] Fix queue creation dependencies Fix the configured AWS proxy factory not being used with the queue creator. --- JustSaying/Fluent/MessagingBusBuilder.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index 6d59d6806..5c6e221a2 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -2,6 +2,7 @@ using JustSaying.AwsTools; using JustSaying.AwsTools.QueueCreation; using JustSaying.Messaging.MessageSerialisation; +using JustSaying.Messaging.Monitoring; using Microsoft.Extensions.Logging; namespace JustSaying.Fluent @@ -247,9 +248,17 @@ private IAwsClientFactoryProxy CreateFactoryProxy() private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory loggerFactory) { IAwsClientFactoryProxy proxy = CreateFactoryProxy(); - IVerifyAmazonQueues queueCreator = ServiceResolver?.ResolveService() ?? new AmazonQueueCreator(proxy, loggerFactory); + IVerifyAmazonQueues queueCreator = new AmazonQueueCreator(proxy, loggerFactory); - return new JustSayingFluently(bus, queueCreator, proxy, loggerFactory); + var fluent = new JustSayingFluently(bus, queueCreator, proxy, loggerFactory); + + IMessageSerialisationFactory serializationFactory = ServiceResolver?.ResolveService() ?? new NewtonsoftSerialisationFactory(); + IMessageMonitor messageMonitor = ServiceResolver?.ResolveService() ?? new NullOpMessageMonitor(); + + fluent.WithSerialisationFactory(serializationFactory) + .WithMonitoring(messageMonitor); + + return fluent; } private sealed class NullLoggerFactory : ILoggerFactory From bee063580e5e25b1469173c88c2ce3f7901802b1 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 10:32:36 +0000 Subject: [PATCH 10/43] Use the same single client factory If IAwsClientFactory is not resolved through DI, use a single instance, rather than creating a new one each time it's needed. --- JustSaying/Fluent/MessagingBusBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index 5c6e221a2..c9b4679c3 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -241,7 +241,7 @@ private IMessagingConfig CreateConfig() private IAwsClientFactoryProxy CreateFactoryProxy() { return ClientFactoryBuilder != null ? - new AwsClientFactoryProxy(ClientFactoryBuilder.Build) : + new AwsClientFactoryProxy(new Lazy(ClientFactoryBuilder.Build)) : ServiceResolver?.ResolveService() ?? new AwsClientFactoryProxy(); } From 555aac3865297723ec8ddcfd0018f17f49a8fae9 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 10:34:06 +0000 Subject: [PATCH 11/43] Adjust formatting Adjust the formatting in the unit test to make it easier to follow. --- .../Fluent/MessagingBusBuilderTests.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs index 96cd07286..4f2c83ffe 100644 --- a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs @@ -28,9 +28,16 @@ public void Can_Create_Messaging_Bus_Fluently() .AddJustSaying( (builder) => { - builder.Client((options) => options.WithBasicCredentials("accessKey", "secretKey").And().WithServiceUrl("http://localhost:4100")) - .Messaging((options) => options.WithRegions("eu-west-1", "eu-central-1").And().WithActiveRegion("eu-west-1")) - .Subscriptions((options) => options.WithHandler()); + builder.Client( + (options) => options.WithBasicCredentials("accessKey", "secretKey") + .And() + .WithServiceUrl("http://localhost:4100")) + .Messaging( + (options) => options.WithRegions("eu-west-1", "eu-central-1") + .And() + .WithActiveRegion("eu-west-1")) + .Subscriptions( + (options) => options.WithHandler()); }) .AddJustSayingHandler(); From 30c617b1259f1cb7b10c8e238c875b4b43cac7e4 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 10:44:34 +0000 Subject: [PATCH 12/43] Move tests to integration project Move the two new tests to the integration tests project and use [AwsFact] so not run in AppVeyor. --- .../Fluent/MessagingBusBuilderTests.cs | 5 +++-- .../JustSaying.IntegrationTests.csproj | 5 +++-- JustSaying.UnitTests/JustSaying.UnitTests.csproj | 3 +-- 3 files changed, 7 insertions(+), 6 deletions(-) rename {JustSaying.UnitTests => JustSaying.IntegrationTests}/Fluent/MessagingBusBuilderTests.cs (97%) diff --git a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs similarity index 97% rename from JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs rename to JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 4f2c83ffe..f998ba962 100644 --- a/JustSaying.UnitTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using JustSaying.Fluent; using JustSaying.Messaging.MessageHandling; using JustSaying.Models; using Microsoft.Extensions.DependencyInjection; @@ -8,7 +9,7 @@ using Xunit; using Xunit.Abstractions; -namespace JustSaying.Fluent +namespace JustSaying.IntegrationTests { public class MessagingBusBuilderTests { @@ -19,7 +20,7 @@ public MessagingBusBuilderTests(ITestOutputHelper outputHelper) private ITestOutputHelper OutputHelper { get; } - [Fact] + [AwsFact] public void Can_Create_Messaging_Bus_Fluently() { // Arrange diff --git a/JustSaying.IntegrationTests/JustSaying.IntegrationTests.csproj b/JustSaying.IntegrationTests/JustSaying.IntegrationTests.csproj index 6d2f21213..958943d51 100644 --- a/JustSaying.IntegrationTests/JustSaying.IntegrationTests.csproj +++ b/JustSaying.IntegrationTests/JustSaying.IntegrationTests.csproj @@ -1,9 +1,9 @@ - + netcoreapp2.1 - $(NoWarn);CA2007;CA1054;CA1307;CA1063;CA1822;CA1052;CA1034;CA1816;CA1001 + $(NoWarn);CA1001;CA1034;CA1052;CA1054;CA1063;CA1307;CA1707;CA1812;CA1816;CA1822;CA2007 @@ -15,6 +15,7 @@ + diff --git a/JustSaying.UnitTests/JustSaying.UnitTests.csproj b/JustSaying.UnitTests/JustSaying.UnitTests.csproj index b24b9baac..d72428820 100644 --- a/JustSaying.UnitTests/JustSaying.UnitTests.csproj +++ b/JustSaying.UnitTests/JustSaying.UnitTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 @@ -15,7 +15,6 @@ - From e0e7af4d30c7f47988c4f119615d4a70fb7f03e8 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 18:34:51 +0000 Subject: [PATCH 13/43] Refactor subscription building Refactor the way subscriptions are configured by adding an extra level of nesting and setting up queues and other things. --- .../Fluent/MessagingBusBuilderTests.cs | 2 +- .../Fluent/IServiceCollectionExtensions.cs | 1 + JustSaying/Fluent/ISubscriptionBuilder`1.cs | 20 +++ JustSaying/Fluent/MessagingBusBuilder.cs | 6 +- .../Fluent/SqsReadConfigurationBuilder.cs | 63 +++++++++ JustSaying/Fluent/SubscriptionBuilder.cs | 59 -------- JustSaying/Fluent/SubscriptionBuilder`1.cs | 128 ++++++++++++++++++ JustSaying/Fluent/SubscriptionsBuilder.cs | 80 +++++++++++ 8 files changed, 296 insertions(+), 63 deletions(-) create mode 100644 JustSaying/Fluent/ISubscriptionBuilder`1.cs create mode 100644 JustSaying/Fluent/SqsReadConfigurationBuilder.cs delete mode 100644 JustSaying/Fluent/SubscriptionBuilder.cs create mode 100644 JustSaying/Fluent/SubscriptionBuilder`1.cs create mode 100644 JustSaying/Fluent/SubscriptionsBuilder.cs diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index f998ba962..e1e2cccbc 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -38,7 +38,7 @@ public void Can_Create_Messaging_Bus_Fluently() .And() .WithActiveRegion("eu-west-1")) .Subscriptions( - (options) => options.WithHandler()); + (options) => options.WithSubscription((p) => p.IntoQueue("foo"))); }) .AddJustSayingHandler(); diff --git a/JustSaying/Fluent/IServiceCollectionExtensions.cs b/JustSaying/Fluent/IServiceCollectionExtensions.cs index e1e851458..737b18af3 100644 --- a/JustSaying/Fluent/IServiceCollectionExtensions.cs +++ b/JustSaying/Fluent/IServiceCollectionExtensions.cs @@ -73,6 +73,7 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton( (p) => diff --git a/JustSaying/Fluent/ISubscriptionBuilder`1.cs b/JustSaying/Fluent/ISubscriptionBuilder`1.cs new file mode 100644 index 000000000..90ae595fd --- /dev/null +++ b/JustSaying/Fluent/ISubscriptionBuilder`1.cs @@ -0,0 +1,20 @@ +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// Defines a builder for a subscription. + /// + /// + /// The type of the messages to subscribe to. + /// + internal interface ISubscriptionBuilder + where T : Message + { + /// + /// Configures the subscription for the . + /// + /// The to configure the subscription for. + void Configure(JustSayingFluently bus, IHandlerResolver resolver); + } +} diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index c9b4679c3..dc1d0a86c 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -33,7 +33,7 @@ public class MessagingBusBuilder : Builder /// /// Gets or sets the builder to use for subscriptions. /// - private SubscriptionBuilder SubscriptionBuilder { get; set; } + private SubscriptionsBuilder SubscriptionBuilder { get; set; } /// /// Gets or sets a delegate to a method to create the to use. @@ -114,7 +114,7 @@ public MessagingBusBuilder Messaging(Action confi /// /// is . /// - public MessagingBusBuilder Subscriptions(Action configure) + public MessagingBusBuilder Subscriptions(Action configure) { if (configure == null) { @@ -123,7 +123,7 @@ public MessagingBusBuilder Subscriptions(Action configure) if (SubscriptionBuilder == null) { - SubscriptionBuilder = new SubscriptionBuilder(this); + SubscriptionBuilder = new SubscriptionsBuilder(this); } configure(SubscriptionBuilder); diff --git a/JustSaying/Fluent/SqsReadConfigurationBuilder.cs b/JustSaying/Fluent/SqsReadConfigurationBuilder.cs new file mode 100644 index 000000000..9776faa51 --- /dev/null +++ b/JustSaying/Fluent/SqsReadConfigurationBuilder.cs @@ -0,0 +1,63 @@ +using System; +using JustSaying.AwsTools.QueueCreation; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for configuring instances of . This class cannot be inherited. + /// + public sealed class SqsReadConfigurationBuilder + { + /// + /// Gets or sets the visibility timeout value to use. + /// + private TimeSpan? VisibilityTimeout { get; set; } + + /// + /// Configures the visibility timeout to use. + /// + /// The value to use for the visibility timeout. + /// + /// The current . + /// + public SqsReadConfigurationBuilder WithVisibilityTimeout(TimeSpan value) + { + VisibilityTimeout = value; + return this; + } + + /// + /// Configures the specified . + /// + /// The configuration to configure. + internal void Configure(SqsReadConfiguration config) + { + // TODO Which ones should be configurable? All, or just the important ones? + // config.BaseQueueName = default; + // config.BaseTopicName = default; + // config.DeliveryDelay = default; + // config.ErrorQueueOptOut = default; + // config.ErrorQueueRetentionPeriod = default; + // config.FilterPolicy = default; + // config.InstancePosition = default; + // config.MaxAllowedMessagesInFlight = default; + // config.MessageBackoffStrategy = default; + // config.MessageProcessingStrategy = default; + // config.MessageRetention = default; + // config.OnError = default; + // config.PublishEndpoint = default; + // config.QueueName = default; + // config.RetryCountBeforeSendingToErrorQueue = default; + // config.ServerSideEncryption = default; + // config.Topic = default; + // config.TopicSourceAccount = default; + + if (VisibilityTimeout.HasValue) + { + config.VisibilityTimeout = VisibilityTimeout.Value; + } + + config.Validate(); + } + } +} diff --git a/JustSaying/Fluent/SubscriptionBuilder.cs b/JustSaying/Fluent/SubscriptionBuilder.cs deleted file mode 100644 index 0a3b7e0dd..000000000 --- a/JustSaying/Fluent/SubscriptionBuilder.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using JustSaying.Models; - -namespace JustSaying.Fluent -{ - /// - /// A class representing a builder for subscriptions. This class cannot be inherited. - /// - public sealed class SubscriptionBuilder - { - /// - /// Initializes a new instance of the class. - /// - /// The that owns this instance. - internal SubscriptionBuilder(MessagingBusBuilder parent) - { - Parent = parent; - } - - /// - /// Gets the parent of this builder. - /// - public MessagingBusBuilder Parent { get; } - - /// - /// Gets the configured handler registrations. - /// - private IList> Registrations = new List>(); - - /// - /// Registers a message handler. - /// - /// The message type to register a handler for. - /// - /// The current . - /// - public SubscriptionBuilder WithHandler() - where T : Message - { - Registrations.Add((p, resolver) => p.WithMessageHandler(resolver)); - return this; - } - - /// - /// Configures the subscriptions for the . - /// - /// The to configure subscriptions for. - internal void Configure(JustSayingFluently bus) - { - IHandlerResolver resolver = Parent.ServiceResolver.ResolveService(); - - foreach (Action registration in Registrations) - { - registration(bus, resolver); - } - } - } -} diff --git a/JustSaying/Fluent/SubscriptionBuilder`1.cs b/JustSaying/Fluent/SubscriptionBuilder`1.cs new file mode 100644 index 000000000..bbd272018 --- /dev/null +++ b/JustSaying/Fluent/SubscriptionBuilder`1.cs @@ -0,0 +1,128 @@ +using System; +using JustSaying.AwsTools.QueueCreation; +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for a subscription. This class cannot be inherited. + /// + /// + /// The type of the message. + /// + public sealed class SubscriptionBuilder : ISubscriptionBuilder + where T : Message + { + /// + /// Initializes a new instance of the class. + /// + internal SubscriptionBuilder() + { + } + + /// + /// Gets or sets a value indicating whether to use a point-to-point subscription. + /// + private bool IsPointToPoint { get; set; } + + /// + /// Gets or sets the queue name. + /// + private string QueueName { get; set; } = string.Empty; + + /// + /// Gets or sets a delegate to a method to use to configure SQS reads. + /// + private Action ConfigureReads { get; set; } + + /// + /// Configures that the default queue name should be used. + /// + /// + /// The current . + /// + public SubscriptionBuilder IntoDefaultQueue() + => IntoQueue(string.Empty); + + /// + /// Configures the name of the queue. + /// + /// The name of the queue to subscribe to. + /// + /// The current . + /// + /// + /// is . + /// + public SubscriptionBuilder IntoQueue(string name) + { + QueueName = name ?? throw new ArgumentNullException(nameof(name)); + return this; + } + + /// + /// Configures the SQS read configuration. + /// + /// A delegate to a method to use to configure SQS reads. + /// + /// The current . + /// + /// + /// is . + /// + public SubscriptionBuilder WithReadConfiguration(Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new SqsReadConfigurationBuilder(); + + configure(builder); + + ConfigureReads = builder.Configure; + return this; + } + + /// + /// Configures the SQS read configuration. + /// + /// A delegate to a method to use to configure SQS reads. + /// + /// The current . + /// + /// + /// is . + /// + public SubscriptionBuilder WithReadConfiguration(Action configure) + { + ConfigureReads = configure ?? throw new ArgumentNullException(nameof(configure)); + return this; + } + + /// + void ISubscriptionBuilder.Configure(JustSayingFluently bus, IHandlerResolver resolver) + { + ISubscriberIntoQueue queue; + + if (IsPointToPoint) + { + queue = bus.WithSqsPointToPointSubscriber(); + } + else + { + queue = bus.WithSqsTopicSubscriber(); + } + + var configured = queue + .IntoQueue(QueueName) + .WithMessageHandler(resolver); + + if (ConfigureReads != null) + { + configured.ConfigureSubscriptionWith(ConfigureReads); + } + } + } +} diff --git a/JustSaying/Fluent/SubscriptionsBuilder.cs b/JustSaying/Fluent/SubscriptionsBuilder.cs new file mode 100644 index 000000000..0dba85980 --- /dev/null +++ b/JustSaying/Fluent/SubscriptionsBuilder.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for subscriptions. This class cannot be inherited. + /// + public sealed class SubscriptionsBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The that owns this instance. + internal SubscriptionsBuilder(MessagingBusBuilder parent) + { + Parent = parent; + } + + /// + /// Gets the parent of this builder. + /// + internal MessagingBusBuilder Parent { get; } + + /// + /// Gets the configured subscription builders. + /// + private IList> Subscriptions { get; } = new List>(); + + /// + /// Configures a subscription. + /// + /// A delegate to a method to use to configure a subscription. + /// + /// The current . + /// + /// + /// is . + /// + public SubscriptionsBuilder WithSubscription(Action> configure) + where T : Message + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new SubscriptionBuilder(); + + configure(builder); + + Subscriptions.Add(builder); + + return this; + } + + /// + /// Configures the subscriptions for the . + /// + /// The to configure subscriptions for. + /// + /// No instance of could be resolved. + /// + internal void Configure(JustSayingFluently bus) + { + IHandlerResolver resolver = Parent.ServiceResolver?.ResolveService(); + + if (resolver == null) + { + throw new InvalidOperationException($"No {nameof(IHandlerResolver)} is registered."); + } + + foreach (ISubscriptionBuilder builder in Subscriptions) + { + builder.Configure(bus, resolver); + } + } + } +} From bdf3c3d07c92293d94f12b8d1a10eff84539c6e0 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 21:37:32 +0000 Subject: [PATCH 14/43] Add more members to SqsReadConfigurationBuilder Add more members to SqsReadConfigurationBuilder. The ones I picked were the ones I saw set in the tests. --- .../Fluent/SqsReadConfigurationBuilder.cs | 187 +++++++++++++++++- 1 file changed, 181 insertions(+), 6 deletions(-) diff --git a/JustSaying/Fluent/SqsReadConfigurationBuilder.cs b/JustSaying/Fluent/SqsReadConfigurationBuilder.cs index 9776faa51..064711ee5 100644 --- a/JustSaying/Fluent/SqsReadConfigurationBuilder.cs +++ b/JustSaying/Fluent/SqsReadConfigurationBuilder.cs @@ -1,4 +1,5 @@ using System; +using Amazon.SQS.Model; using JustSaying.AwsTools.QueueCreation; namespace JustSaying.Fluent @@ -8,11 +9,161 @@ namespace JustSaying.Fluent /// public sealed class SqsReadConfigurationBuilder { + /// + /// Gets or sets a value indicating whether to opt-out of error queues. + /// + private bool? ErrorQueueOptOut { get; set; } + + /// + /// Gets or sets the instance position value to use. + /// + private int? InstancePosition { get; set; } + + /// + /// Gets or sets the maximum number of messages that can be inflight. + /// + private int? MaximumAllowedMessagesInflight { get; set; } + + /// + /// Gets or sets the message retention value to use. + /// + private TimeSpan? MessageRetention { get; set; } + + /// + /// Gets or sets the error callback to use. + /// + private Action OnError { get; set; } + + /// + /// Gets or sets the topic source account Id to use. + /// + private string TopicSourceAccountId { get; set; } + /// /// Gets or sets the visibility timeout value to use. /// private TimeSpan? VisibilityTimeout { get; set; } + /// + /// Configures an error handler to use. + /// + /// A delegate to a method to call when an error occurs. + /// + /// The current . + /// + /// + /// is . + /// + public SqsReadConfigurationBuilder WithErrorHandler(Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + OnError = (exception, _) => action(exception); + return this; + } + + /// + /// Configures an error handler to use. + /// + /// A delegate to a method to call when an error occurs. + /// + /// The current . + /// + /// + /// is . + /// + public SqsReadConfigurationBuilder WithErrorHandler(Action action) + { + OnError = action ?? throw new ArgumentNullException(nameof(action)); + return this; + } + + /// + /// Configures that an error queue should be used. + /// + /// + /// The current . + /// + public SqsReadConfigurationBuilder WithErrorQueue() + => WithErrorQueueOptOut(false); + + /// + /// Configures that no error queue should be used. + /// + /// + /// The current . + /// + public SqsReadConfigurationBuilder WithNoErrorQueue() + => WithErrorQueueOptOut(true); + + /// + /// Configures whether to opt-out of an error queue. + /// + /// Whether or not to opt-out of an error queue. + /// + /// The current . + /// + public SqsReadConfigurationBuilder WithErrorQueueOptOut(bool value) + { + ErrorQueueOptOut = value; + return this; + } + + /// + /// Configures the instance position to use. + /// + /// The value to use for the instance position. + /// + /// The current . + /// + public SqsReadConfigurationBuilder WithInstancePosition(int value) + { + InstancePosition = value; + return this; + } + + /// + /// Configures the maximum number of messages that can be inflight at any time. + /// + /// The value to use for maximum number of inflight messages. + /// + /// The current . + /// + public SqsReadConfigurationBuilder WithMaximumMessagesInflight(int value) + { + MaximumAllowedMessagesInflight = value; + return this; + } + + /// + /// Configures the message retention period to use. + /// + /// The value to use for the message retention. + /// + /// The current . + /// + public SqsReadConfigurationBuilder WithMessageRetention(TimeSpan value) + { + MessageRetention = value; + return this; + } + + /// + /// Configures the account Id to use for the topic source. + /// + /// The Id of the AWS account which is the topic's source. + /// + /// The current . + /// + public SqsReadConfigurationBuilder WithTopicSourceAccount(string id) + { + TopicSourceAccountId = id; + return this; + } + /// /// Configures the visibility timeout to use. /// @@ -36,21 +187,45 @@ internal void Configure(SqsReadConfiguration config) // config.BaseQueueName = default; // config.BaseTopicName = default; // config.DeliveryDelay = default; - // config.ErrorQueueOptOut = default; // config.ErrorQueueRetentionPeriod = default; // config.FilterPolicy = default; - // config.InstancePosition = default; - // config.MaxAllowedMessagesInFlight = default; // config.MessageBackoffStrategy = default; // config.MessageProcessingStrategy = default; - // config.MessageRetention = default; - // config.OnError = default; // config.PublishEndpoint = default; // config.QueueName = default; // config.RetryCountBeforeSendingToErrorQueue = default; // config.ServerSideEncryption = default; // config.Topic = default; - // config.TopicSourceAccount = default; + + if (ErrorQueueOptOut.HasValue) + { + config.ErrorQueueOptOut = ErrorQueueOptOut.Value; + } + + if (InstancePosition.HasValue) + { + config.InstancePosition = InstancePosition.Value; + } + + if (MaximumAllowedMessagesInflight.HasValue) + { + config.MaxAllowedMessagesInFlight = MaximumAllowedMessagesInflight.Value; + } + + if (MessageRetention.HasValue) + { + config.MessageRetention = MessageRetention.Value; + } + + if (OnError != null) + { + config.OnError = OnError; + } + + if (TopicSourceAccountId != null) + { + config.TopicSourceAccount = TopicSourceAccountId; + } if (VisibilityTimeout.HasValue) { From 3e03a8298cca4bd726e0e5ca65bb744ef3f1ee9f Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 25 Nov 2018 21:41:08 +0000 Subject: [PATCH 15/43] Remove IBuilder Remove the IBuilder interface and the Builder<,> base class. --- .../Fluent/MessagingBusBuilderTests.cs | 2 -- JustSaying/Fluent/AwsClientFactoryBuilder.cs | 17 +++++------ JustSaying/Fluent/Builder`2.cs | 30 ------------------- JustSaying/Fluent/IBuilder`1.cs | 20 ------------- JustSaying/Fluent/MessagingBusBuilder.cs | 15 ++++------ .../Fluent/MessagingConfigurationBuilder.cs | 25 +++++++--------- 6 files changed, 24 insertions(+), 85 deletions(-) delete mode 100644 JustSaying/Fluent/Builder`2.cs delete mode 100644 JustSaying/Fluent/IBuilder`1.cs diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index e1e2cccbc..5ae0e1840 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -31,11 +31,9 @@ public void Can_Create_Messaging_Bus_Fluently() { builder.Client( (options) => options.WithBasicCredentials("accessKey", "secretKey") - .And() .WithServiceUrl("http://localhost:4100")) .Messaging( (options) => options.WithRegions("eu-west-1", "eu-central-1") - .And() .WithActiveRegion("eu-west-1")) .Subscriptions( (options) => options.WithSubscription((p) => p.IntoQueue("foo"))); diff --git a/JustSaying/Fluent/AwsClientFactoryBuilder.cs b/JustSaying/Fluent/AwsClientFactoryBuilder.cs index 4adf2b5ba..5c2c3f8fa 100644 --- a/JustSaying/Fluent/AwsClientFactoryBuilder.cs +++ b/JustSaying/Fluent/AwsClientFactoryBuilder.cs @@ -7,7 +7,7 @@ namespace JustSaying.Fluent /// /// A class representing a builder for instances of . This class cannot be inherited. /// - public sealed class AwsClientFactoryBuilder : Builder + public sealed class AwsClientFactoryBuilder { /// /// Initializes a new instance of the class. @@ -21,9 +21,6 @@ internal AwsClientFactoryBuilder(MessagingBusBuilder busBuilder) /// public MessagingBusBuilder BusBuilder { get; } - /// - protected override AwsClientFactoryBuilder Self => this; - /// /// Gets or sets a delegate to use to create the to use. /// @@ -45,7 +42,7 @@ internal AwsClientFactoryBuilder(MessagingBusBuilder busBuilder) /// /// The created instance of . /// - public override IAwsClientFactory Build() + public IAwsClientFactory Build() { if (ClientFactory != null) { @@ -78,7 +75,7 @@ public override IAwsClientFactory Build() public AwsClientFactoryBuilder WithClientFactory(Func clientFactory) { ClientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory)); - return Self; + return this; } /// @@ -91,7 +88,7 @@ public AwsClientFactoryBuilder WithClientFactory(Func clientF public AwsClientFactoryBuilder WithCredentials(AWSCredentials credentials) { Credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); - return Self; + return this; } /// @@ -105,7 +102,7 @@ public AwsClientFactoryBuilder WithCredentials(AWSCredentials credentials) public AwsClientFactoryBuilder WithBasicCredentials(string accessKey, string secretKey) { Credentials = new BasicAWSCredentials(accessKey, secretKey); - return Self; + return this; } /// @@ -120,7 +117,7 @@ public AwsClientFactoryBuilder WithBasicCredentials(string accessKey, string sec public AwsClientFactoryBuilder WithSessionCredentials(string accessKeyId, string secretAccessKey, string token) { Credentials = new SessionAWSCredentials(accessKeyId, secretAccessKey, token); - return Self; + return this; } /// @@ -175,7 +172,7 @@ public AwsClientFactoryBuilder WithServiceUri(Uri uri) ServiceUri = uri; - return Self; + return this; } } } diff --git a/JustSaying/Fluent/Builder`2.cs b/JustSaying/Fluent/Builder`2.cs deleted file mode 100644 index e587365bf..000000000 --- a/JustSaying/Fluent/Builder`2.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace JustSaying.Fluent -{ - /// - /// The base class for implementations of . - /// - /// The type created by the builder. - /// The builder's own type. - public abstract class Builder : IBuilder - where T : class - where TBuilder : Builder - { - /// - /// Gets the current . - /// - public TBuilder And() => Self; - - /// - /// Creates a new instance of . - /// - /// - /// The created new instance of . - /// - public abstract T Build(); - - /// - /// Gets the current . - /// - protected abstract TBuilder Self { get; } - } -} diff --git a/JustSaying/Fluent/IBuilder`1.cs b/JustSaying/Fluent/IBuilder`1.cs deleted file mode 100644 index 9e5b387e7..000000000 --- a/JustSaying/Fluent/IBuilder`1.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace JustSaying.Fluent -{ - /// - /// Defines a method for creating instances of types with a builder pattern. - /// - /// - /// The type created by the builder. - /// - public interface IBuilder - where T : class - { - /// - /// Creates a new instance of type . - /// - /// - /// The created instance of . - /// - T Build(); - } -} diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index dc1d0a86c..1f4480f61 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -10,16 +10,13 @@ namespace JustSaying.Fluent /// /// A class representing a builder for an . /// - public class MessagingBusBuilder : Builder + public class MessagingBusBuilder { /// /// Gets the optional to use. /// internal IServiceResolver ServiceResolver { get; private set; } - /// - protected override MessagingBusBuilder Self => this; - /// /// Gets or sets the builder to use for creating an AWS client factory. /// @@ -74,7 +71,7 @@ public MessagingBusBuilder Client(Action configure) configure(ClientFactoryBuilder); - return Self; + return this; } /// @@ -101,7 +98,7 @@ public MessagingBusBuilder Messaging(Action confi configure(MessagingConfig); - return Self; + return this; } /// @@ -128,7 +125,7 @@ public MessagingBusBuilder Subscriptions(Action configure) configure(SubscriptionBuilder); - return Self; + return this; } /// @@ -187,7 +184,7 @@ public MessagingBusBuilder WithNamingStrategy(Func strategy) public MessagingBusBuilder WithServiceResolver(IServiceResolver serviceResolver) { ServiceResolver = serviceResolver; - return Self; + return this; } /// @@ -196,7 +193,7 @@ public MessagingBusBuilder WithServiceResolver(IServiceResolver serviceResolver) /// /// The created instance of /// - public override IMessagingBus Build() + public IMessagingBus Build() { IMessagingConfig config = CreateConfig(); diff --git a/JustSaying/Fluent/MessagingConfigurationBuilder.cs b/JustSaying/Fluent/MessagingConfigurationBuilder.cs index 8530e460c..de48bc208 100644 --- a/JustSaying/Fluent/MessagingConfigurationBuilder.cs +++ b/JustSaying/Fluent/MessagingConfigurationBuilder.cs @@ -10,7 +10,7 @@ namespace JustSaying.Fluent /// /// A class representing a builder for instances of . This class cannot be inherited. /// - public sealed class MessagingConfigurationBuilder : Builder + public sealed class MessagingConfigurationBuilder { /// /// Initializes a new instance of the class. @@ -24,9 +24,6 @@ internal MessagingConfigurationBuilder(MessagingBusBuilder busBuilder) /// public MessagingBusBuilder BusBuilder { get; } - /// - protected override MessagingConfigurationBuilder Self => this; - /// /// Gets or sets the optional value to use for /// @@ -115,7 +112,7 @@ public MessagingConfigurationBuilder WithActiveRegion(string region) public MessagingConfigurationBuilder WithActiveRegion(Func evaluator) { GetActiveRegion = evaluator ?? throw new ArgumentNullException(nameof(evaluator)); - return Self; + return this; } /// @@ -149,7 +146,7 @@ public MessagingConfigurationBuilder WithAdditionalSubscriberAccounts(IEnumerabl } AdditionalSubscriberAccounts = new List(accountIds); - return Self; + return this; } /// @@ -175,7 +172,7 @@ public MessagingConfigurationBuilder WithAdditionalSubscriberAccount(string acco } AdditionalSubscriberAccounts.Add(accountId); - return Self; + return this; } /// @@ -191,7 +188,7 @@ public MessagingConfigurationBuilder WithAdditionalSubscriberAccount(string acco public MessagingConfigurationBuilder WithMessageResponseLogger(Action logger) { MessageResponseLogger = logger ?? throw new ArgumentNullException(nameof(logger)); - return Self; + return this; } /// @@ -207,7 +204,7 @@ public MessagingConfigurationBuilder WithMessageResponseLogger(Action @@ -220,7 +217,7 @@ public MessagingConfigurationBuilder WithMessageSubjectProvider(IMessageSubjectP public MessagingConfigurationBuilder WithPublishFailureBackoff(TimeSpan value) { PublishFailureBackoff = value; - return Self; + return this; } /// @@ -233,7 +230,7 @@ public MessagingConfigurationBuilder WithPublishFailureBackoff(TimeSpan value) public MessagingConfigurationBuilder WithPublishFailureReattempts(int value) { PublishFailureReAttempts = value; - return Self; + return this; } /// @@ -267,7 +264,7 @@ public MessagingConfigurationBuilder WithRegions(IEnumerable regions) } Regions = new List(regions); - return Self; + return this; } /// @@ -293,7 +290,7 @@ public MessagingConfigurationBuilder WithRegion(string region) } Regions.Add(region); - return Self; + return this; } /// @@ -322,7 +319,7 @@ public MessagingConfigurationBuilder WithRegion(RegionEndpoint region) /// /// The created instance of . /// - public override IMessagingConfig Build() + public IMessagingConfig Build() { var config = BusBuilder.ServiceResolver?.ResolveService() ?? new MessagingConfig(); From 7ab58363c4d2bd043acba6d2b7ad35165fb321e3 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 18:57:40 +0000 Subject: [PATCH 16/43] Fix compliation errors Fix compilation errors caused by rebase onto master after changes from #457. Add method to customise IMessageSerializationFactory. --- .../Fluent/IServiceCollectionExtensions.cs | 8 ++-- JustSaying/Fluent/MessagingBusBuilder.cs | 40 +++++++++++++++---- .../Fluent/MessagingConfigurationBuilder.cs | 2 +- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/JustSaying/Fluent/IServiceCollectionExtensions.cs b/JustSaying/Fluent/IServiceCollectionExtensions.cs index 737b18af3..7e77f500e 100644 --- a/JustSaying/Fluent/IServiceCollectionExtensions.cs +++ b/JustSaying/Fluent/IServiceCollectionExtensions.cs @@ -2,7 +2,7 @@ using JustSaying.AwsTools; using JustSaying.AwsTools.QueueCreation; using JustSaying.Messaging.MessageHandling; -using JustSaying.Messaging.MessageSerialisation; +using JustSaying.Messaging.MessageSerialization; using JustSaying.Messaging.Monitoring; using JustSaying.Models; using Microsoft.Extensions.DependencyInjection; @@ -72,14 +72,14 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, services.TryAddSingleton((p) => new AwsClientFactoryProxy(p.GetRequiredService)); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton( + services.TryAddSingleton( (p) => { var config = p.GetRequiredService(); - return new MessageSerialisationRegister(config.MessageSubjectProvider); + return new MessageSerializationRegister(config.MessageSubjectProvider); }); services.TryAddSingleton( diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index 1f4480f61..e86d5f03c 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -1,7 +1,7 @@ using System; using JustSaying.AwsTools; using JustSaying.AwsTools.QueueCreation; -using JustSaying.Messaging.MessageSerialisation; +using JustSaying.Messaging.MessageSerialization; using JustSaying.Messaging.Monitoring; using Microsoft.Extensions.Logging; @@ -37,15 +37,20 @@ public class MessagingBusBuilder /// private Func LoggerFactory { get; set; } + /// + /// Gets or sets the to use. + /// + private IMessageSerializationFactory MessageSerializationFactory { get; set; } + /// /// Gets or sets a delegate to a method to create the to use. /// private Func NamingStrategy { get; set; } /// - /// Gets or sets a delegate to a method to create the to use. + /// Gets or sets a delegate to a method to create the to use. /// - private Func SerializationRegister { get; set; } + private Func SerializationRegister { get; set; } /// /// Configures the factory for AWS clients. @@ -174,6 +179,22 @@ public MessagingBusBuilder WithNamingStrategy(Func strategy) return this; } + /// + /// Specifies the to use. + /// + /// The to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingBusBuilder WithMessageSerializationFactory(IMessageSerializationFactory factory) + { + MessageSerializationFactory = factory ?? throw new ArgumentNullException(nameof(factory)); + return this; + } + /// /// Specifies the to use. /// @@ -222,8 +243,8 @@ public IMessagingBus Build() private JustSayingBus CreateBus(IMessagingConfig config, ILoggerFactory loggerFactory) { - IMessageSerialisationRegister register = - SerializationRegister?.Invoke() ?? ServiceResolver?.ResolveService() ?? new MessageSerialisationRegister(config.MessageSubjectProvider); + IMessageSerializationRegister register = + SerializationRegister?.Invoke() ?? ServiceResolver?.ResolveService() ?? new MessageSerializationRegister(config.MessageSubjectProvider); return new JustSayingBus(config, register, loggerFactory); } @@ -242,6 +263,11 @@ private IAwsClientFactoryProxy CreateFactoryProxy() ServiceResolver?.ResolveService() ?? new AwsClientFactoryProxy(); } + private IMessageSerializationFactory CreateMessageSerializationFactory() + { + return MessageSerializationFactory ?? ServiceResolver?.ResolveService() ?? new NewtonsoftSerializationFactory(); + } + private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory loggerFactory) { IAwsClientFactoryProxy proxy = CreateFactoryProxy(); @@ -249,10 +275,10 @@ private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory logger var fluent = new JustSayingFluently(bus, queueCreator, proxy, loggerFactory); - IMessageSerialisationFactory serializationFactory = ServiceResolver?.ResolveService() ?? new NewtonsoftSerialisationFactory(); + IMessageSerializationFactory serializationFactory = CreateMessageSerializationFactory(); IMessageMonitor messageMonitor = ServiceResolver?.ResolveService() ?? new NullOpMessageMonitor(); - fluent.WithSerialisationFactory(serializationFactory) + fluent.WithSerializationFactory(serializationFactory) .WithMonitoring(messageMonitor); return fluent; diff --git a/JustSaying/Fluent/MessagingConfigurationBuilder.cs b/JustSaying/Fluent/MessagingConfigurationBuilder.cs index de48bc208..6f0e9b3db 100644 --- a/JustSaying/Fluent/MessagingConfigurationBuilder.cs +++ b/JustSaying/Fluent/MessagingConfigurationBuilder.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Amazon; using JustSaying.AwsTools.MessageHandling; -using JustSaying.Messaging.MessageSerialisation; +using JustSaying.Messaging.MessageSerialization; using JustSaying.Models; namespace JustSaying.Fluent From b7d26603d7569a690f5147ead7332b7aa66bb398 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 19:02:26 +0000 Subject: [PATCH 17/43] Make IMessageSerializationFactory lazy Make the registration for IMessageSerializationFactory lazy. Remove non-lazy INamingStrategy overload. --- JustSaying/Fluent/MessagingBusBuilder.cs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index e86d5f03c..b6c69f388 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -38,9 +38,9 @@ public class MessagingBusBuilder private Func LoggerFactory { get; set; } /// - /// Gets or sets the to use. + /// Gets or sets a delegate to a method to create the to use. /// - private IMessageSerializationFactory MessageSerializationFactory { get; set; } + private Func MessageSerializationFactory { get; set; } /// /// Gets or sets a delegate to a method to create the to use. @@ -156,16 +156,6 @@ public MessagingBusBuilder WithLoggerFactory(Func loggerFactory) return this; } - /// - /// Specifies the to use. - /// - /// The to use. - /// - /// The current . - /// - public MessagingBusBuilder WithNamingStrategy(INamingStrategy strategy) - => WithNamingStrategy(() => strategy); - /// /// Specifies the to use. /// @@ -182,14 +172,14 @@ public MessagingBusBuilder WithNamingStrategy(Func strategy) /// /// Specifies the to use. /// - /// The to use. + /// A delegate to a method to get the to use. /// /// The current . /// /// /// is . /// - public MessagingBusBuilder WithMessageSerializationFactory(IMessageSerializationFactory factory) + public MessagingBusBuilder WithMessageSerializationFactory(Func factory) { MessageSerializationFactory = factory ?? throw new ArgumentNullException(nameof(factory)); return this; @@ -265,7 +255,7 @@ private IAwsClientFactoryProxy CreateFactoryProxy() private IMessageSerializationFactory CreateMessageSerializationFactory() { - return MessageSerializationFactory ?? ServiceResolver?.ResolveService() ?? new NewtonsoftSerializationFactory(); + return MessageSerializationFactory?.Invoke() ?? ServiceResolver?.ResolveService() ?? new NewtonsoftSerializationFactory(); } private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory loggerFactory) From 3aeba99f632b474ba4e426b819262c203d211568 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 19:06:36 +0000 Subject: [PATCH 18/43] Make IMessageMonitor configurable Make IMessageMonitor configurable on the builder. --- JustSaying/Fluent/MessagingBusBuilder.cs | 28 +++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index b6c69f388..1722e7962 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -37,6 +37,11 @@ public class MessagingBusBuilder /// private Func LoggerFactory { get; set; } + /// + /// Gets or sets a delegate to a method to create the to use. + /// + private Func MessageMonitoring { get; set; } + /// /// Gets or sets a delegate to a method to create the to use. /// @@ -156,6 +161,22 @@ public MessagingBusBuilder WithLoggerFactory(Func loggerFactory) return this; } + /// + /// Specifies the to use. + /// + /// A delegate to a method to get the to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingBusBuilder WithMessageMonitoring(Func monitoring) + { + MessageMonitoring = monitoring ?? throw new ArgumentNullException(nameof(monitoring)); + return this; + } + /// /// Specifies the to use. /// @@ -253,6 +274,11 @@ private IAwsClientFactoryProxy CreateFactoryProxy() ServiceResolver?.ResolveService() ?? new AwsClientFactoryProxy(); } + private IMessageMonitor CreateMessageMonitor() + { + return MessageMonitoring?.Invoke() ?? ServiceResolver?.ResolveService() ?? new NullOpMessageMonitor(); + } + private IMessageSerializationFactory CreateMessageSerializationFactory() { return MessageSerializationFactory?.Invoke() ?? ServiceResolver?.ResolveService() ?? new NewtonsoftSerializationFactory(); @@ -266,7 +292,7 @@ private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory logger var fluent = new JustSayingFluently(bus, queueCreator, proxy, loggerFactory); IMessageSerializationFactory serializationFactory = CreateMessageSerializationFactory(); - IMessageMonitor messageMonitor = ServiceResolver?.ResolveService() ?? new NullOpMessageMonitor(); + IMessageMonitor messageMonitor = CreateMessageMonitor(); fluent.WithSerializationFactory(serializationFactory) .WithMonitoring(messageMonitor); From 95774a6faeddc7634c3f36b7fb0ebbfd45238468 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 19:15:51 +0000 Subject: [PATCH 19/43] Add IMessageLockAsync to builder Provide a way to register an IMessageLockAsync. --- JustSaying/Fluent/MessagingBusBuilder.cs | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index 1722e7962..1269e980c 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -1,6 +1,7 @@ using System; using JustSaying.AwsTools; using JustSaying.AwsTools.QueueCreation; +using JustSaying.Messaging.MessageHandling; using JustSaying.Messaging.MessageSerialization; using JustSaying.Messaging.Monitoring; using Microsoft.Extensions.Logging; @@ -42,6 +43,11 @@ public class MessagingBusBuilder /// private Func MessageMonitoring { get; set; } + /// + /// Gets or sets a delegate to a method to create the to use. + /// + private Func MessageLock { get; set; } + /// /// Gets or sets a delegate to a method to create the to use. /// @@ -161,6 +167,22 @@ public MessagingBusBuilder WithLoggerFactory(Func loggerFactory) return this; } + /// + /// Specifies the to use. + /// + /// A delegate to a method to get the to use. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingBusBuilder WithMessageLock(Func messageLock) + { + MessageLock = messageLock ?? throw new ArgumentNullException(nameof(messageLock)); + return this; + } + /// /// Specifies the to use. /// @@ -297,6 +319,11 @@ private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory logger fluent.WithSerializationFactory(serializationFactory) .WithMonitoring(messageMonitor); + if (MessageLock != null) + { + fluent.WithMessageLockStoreOf(MessageLock()); + } + return fluent; } From 52fb475fd4d7bd4e85d7753c2762985a100c55a2 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 19:18:41 +0000 Subject: [PATCH 20/43] Add null checks Add null checks on the message builder. Fix some incorrect XML documentation. --- JustSaying/Fluent/MessagingBusBuilder.cs | 33 +++++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index 1269e980c..f835873dd 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -151,8 +151,18 @@ public MessagingBusBuilder Subscriptions(Action configure) /// /// The current . /// + /// + /// is . + /// public MessagingBusBuilder WithLoggerFactory(ILoggerFactory loggerFactory) - => WithLoggerFactory(() => loggerFactory); + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + return WithLoggerFactory(() => loggerFactory); + } /// /// Specifies the to use. @@ -161,9 +171,12 @@ public MessagingBusBuilder WithLoggerFactory(ILoggerFactory loggerFactory) /// /// The current . /// + /// + /// is . + /// public MessagingBusBuilder WithLoggerFactory(Func loggerFactory) { - LoggerFactory = loggerFactory; + LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); return this; } @@ -174,7 +187,7 @@ public MessagingBusBuilder WithLoggerFactory(Func loggerFactory) /// /// The current . /// - /// + /// /// is . /// public MessagingBusBuilder WithMessageLock(Func messageLock) @@ -190,7 +203,7 @@ public MessagingBusBuilder WithMessageLock(Func messageLock) /// /// The current . /// - /// + /// /// is . /// public MessagingBusBuilder WithMessageMonitoring(Func monitoring) @@ -206,9 +219,12 @@ public MessagingBusBuilder WithMessageMonitoring(Func monitorin /// /// The current . /// + /// + /// is . + /// public MessagingBusBuilder WithNamingStrategy(Func strategy) { - NamingStrategy = strategy; + NamingStrategy = strategy ?? throw new ArgumentNullException(nameof(strategy)); return this; } @@ -219,7 +235,7 @@ public MessagingBusBuilder WithNamingStrategy(Func strategy) /// /// The current . /// - /// + /// /// is . /// public MessagingBusBuilder WithMessageSerializationFactory(Func factory) @@ -235,9 +251,12 @@ public MessagingBusBuilder WithMessageSerializationFactory(Func /// The current . /// + /// + /// is . + /// public MessagingBusBuilder WithServiceResolver(IServiceResolver serviceResolver) { - ServiceResolver = serviceResolver; + ServiceResolver = serviceResolver ?? throw new ArgumentNullException(nameof(serviceResolver)); return this; } From 8f3ba38c576c54394b55126f5a247f5e1f99f733 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 19:35:41 +0000 Subject: [PATCH 21/43] Move default services to own type Move the creation if default built-in services to DefaultServiceResolver if a custom IServiceResolver is not in use. --- JustSaying/Fluent/DefaultServiceResolver.cs | 89 +++++++++++++++++++ JustSaying/Fluent/MessagingBusBuilder.cs | 49 ++-------- .../Fluent/MessagingConfigurationBuilder.cs | 2 +- JustSaying/Fluent/SubscriptionsBuilder.cs | 2 +- 4 files changed, 99 insertions(+), 43 deletions(-) create mode 100644 JustSaying/Fluent/DefaultServiceResolver.cs diff --git a/JustSaying/Fluent/DefaultServiceResolver.cs b/JustSaying/Fluent/DefaultServiceResolver.cs new file mode 100644 index 000000000..e1917763e --- /dev/null +++ b/JustSaying/Fluent/DefaultServiceResolver.cs @@ -0,0 +1,89 @@ +using System; +using JustSaying.AwsTools; +using JustSaying.Messaging.MessageSerialization; +using JustSaying.Messaging.Monitoring; +using Microsoft.Extensions.Logging; + +namespace JustSaying.Fluent +{ + /// + /// A class representing the built-in implementation of . This class cannot be inherited. + /// + internal sealed class DefaultServiceResolver : IServiceResolver + { + /// + public T ResolveService() + => (T)ResolveService(typeof(T)); + + private object ResolveService(Type desiredType) + { + if (desiredType == typeof(ILoggerFactory)) + { + return new NullLoggerFactory(); + } + else if (desiredType == typeof(IAwsClientFactoryProxy)) + { + return new AwsClientFactoryProxy(); + } + else if (desiredType == typeof(IHandlerResolver)) + { + return null; // Special case - must be provided by the consumer + } + else if (desiredType == typeof(IMessagingConfig)) + { + return new MessagingConfig(); + } + else if (desiredType == typeof(IMessageMonitor)) + { + return new NullOpMessageMonitor(); + } + else if (desiredType == typeof(IMessageSerializationFactory)) + { + return new NewtonsoftSerializationFactory(); + } + else if (desiredType == typeof(IMessageSerializationRegister)) + { + return new MessageSerializationRegister(ResolveService()); + } + else if (desiredType == typeof(IMessageSubjectProvider)) + { + return new NonGenericMessageSubjectProvider(); + } + + throw new NotSupportedException($"Resolving a service of type {desiredType.Name} is not supported."); + } + + private sealed class NullLoggerFactory : ILoggerFactory + { + public void AddProvider(ILoggerProvider provider) + { + } + + public ILogger CreateLogger(string categoryName) + { + return new NullLogger(); + } + + public void Dispose() + { + } + + private sealed class NullLogger : ILogger + { + public IDisposable BeginScope(TState state) + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return false; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + } + } + } + } +} diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index f835873dd..583033295 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -14,9 +14,9 @@ namespace JustSaying.Fluent public class MessagingBusBuilder { /// - /// Gets the optional to use. + /// Gets the to use. /// - internal IServiceResolver ServiceResolver { get; private set; } + internal IServiceResolver ServiceResolver { get; private set; } = new DefaultServiceResolver(); /// /// Gets or sets the builder to use for creating an AWS client factory. @@ -273,7 +273,7 @@ public IMessagingBus Build() config.Validate(); ILoggerFactory loggerFactory = - LoggerFactory?.Invoke() ?? ServiceResolver?.ResolveService() ?? new NullLoggerFactory(); + LoggerFactory?.Invoke() ?? ServiceResolver.ResolveService(); JustSayingBus bus = CreateBus(config, loggerFactory); JustSayingFluently fluent = CreateFluent(bus, loggerFactory); @@ -296,7 +296,7 @@ public IMessagingBus Build() private JustSayingBus CreateBus(IMessagingConfig config, ILoggerFactory loggerFactory) { IMessageSerializationRegister register = - SerializationRegister?.Invoke() ?? ServiceResolver?.ResolveService() ?? new MessageSerializationRegister(config.MessageSubjectProvider); + SerializationRegister?.Invoke() ?? ServiceResolver.ResolveService(); return new JustSayingBus(config, register, loggerFactory); } @@ -305,24 +305,24 @@ private IMessagingConfig CreateConfig() { return MessagingConfig != null ? MessagingConfig.Build() : - ServiceResolver?.ResolveService() ?? new MessagingConfig(); + ServiceResolver.ResolveService(); } private IAwsClientFactoryProxy CreateFactoryProxy() { return ClientFactoryBuilder != null ? new AwsClientFactoryProxy(new Lazy(ClientFactoryBuilder.Build)) : - ServiceResolver?.ResolveService() ?? new AwsClientFactoryProxy(); + ServiceResolver.ResolveService(); } private IMessageMonitor CreateMessageMonitor() { - return MessageMonitoring?.Invoke() ?? ServiceResolver?.ResolveService() ?? new NullOpMessageMonitor(); + return MessageMonitoring?.Invoke() ?? ServiceResolver.ResolveService(); } private IMessageSerializationFactory CreateMessageSerializationFactory() { - return MessageSerializationFactory?.Invoke() ?? ServiceResolver?.ResolveService() ?? new NewtonsoftSerializationFactory(); + return MessageSerializationFactory?.Invoke() ?? ServiceResolver.ResolveService(); } private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory loggerFactory) @@ -345,38 +345,5 @@ private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory logger return fluent; } - - private sealed class NullLoggerFactory : ILoggerFactory - { - public void AddProvider(ILoggerProvider provider) - { - } - - public ILogger CreateLogger(string categoryName) - { - return new NullLogger(); - } - - public void Dispose() - { - } - - private sealed class NullLogger : ILogger - { - public IDisposable BeginScope(TState state) - { - return null; - } - - public bool IsEnabled(LogLevel logLevel) - { - return false; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - } - } - } } } diff --git a/JustSaying/Fluent/MessagingConfigurationBuilder.cs b/JustSaying/Fluent/MessagingConfigurationBuilder.cs index 6f0e9b3db..b9fa3abf7 100644 --- a/JustSaying/Fluent/MessagingConfigurationBuilder.cs +++ b/JustSaying/Fluent/MessagingConfigurationBuilder.cs @@ -321,7 +321,7 @@ public MessagingConfigurationBuilder WithRegion(RegionEndpoint region) /// public IMessagingConfig Build() { - var config = BusBuilder.ServiceResolver?.ResolveService() ?? new MessagingConfig(); + var config = BusBuilder.ServiceResolver.ResolveService(); if (Regions?.Count > 0) { diff --git a/JustSaying/Fluent/SubscriptionsBuilder.cs b/JustSaying/Fluent/SubscriptionsBuilder.cs index 0dba85980..8448b2634 100644 --- a/JustSaying/Fluent/SubscriptionsBuilder.cs +++ b/JustSaying/Fluent/SubscriptionsBuilder.cs @@ -64,7 +64,7 @@ public SubscriptionsBuilder WithSubscription(Action> c /// internal void Configure(JustSayingFluently bus) { - IHandlerResolver resolver = Parent.ServiceResolver?.ResolveService(); + IHandlerResolver resolver = Parent.ServiceResolver.ResolveService(); if (resolver == null) { From ac373627aed2387becca2dd23306e4b5ae7c9b40 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 19:45:59 +0000 Subject: [PATCH 22/43] Move services to new ServicesBuilder Move services like locks, monitoring and logging to new ServicesBuilder. --- JustSaying/Fluent/MessagingBusBuilder.cs | 157 +++++------------------ JustSaying/Fluent/ServicesBuilder.cs | 156 ++++++++++++++++++++++ 2 files changed, 185 insertions(+), 128 deletions(-) create mode 100644 JustSaying/Fluent/ServicesBuilder.cs diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index 583033295..9d5db4957 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -1,7 +1,6 @@ using System; using JustSaying.AwsTools; using JustSaying.AwsTools.QueueCreation; -using JustSaying.Messaging.MessageHandling; using JustSaying.Messaging.MessageSerialization; using JustSaying.Messaging.Monitoring; using Microsoft.Extensions.Logging; @@ -29,39 +28,14 @@ public class MessagingBusBuilder private MessagingConfigurationBuilder MessagingConfig { get; set; } /// - /// Gets or sets the builder to use for subscriptions. - /// - private SubscriptionsBuilder SubscriptionBuilder { get; set; } - - /// - /// Gets or sets a delegate to a method to create the to use. - /// - private Func LoggerFactory { get; set; } - - /// - /// Gets or sets a delegate to a method to create the to use. - /// - private Func MessageMonitoring { get; set; } - - /// - /// Gets or sets a delegate to a method to create the to use. + /// Gets or sets the builder to use for services. /// - private Func MessageLock { get; set; } + private ServicesBuilder ServicesBuilder { get; set; } /// - /// Gets or sets a delegate to a method to create the to use. - /// - private Func MessageSerializationFactory { get; set; } - - /// - /// Gets or sets a delegate to a method to create the to use. - /// - private Func NamingStrategy { get; set; } - - /// - /// Gets or sets a delegate to a method to create the to use. + /// Gets or sets the builder to use for subscriptions. /// - private Func SerializationRegister { get; set; } + private SubscriptionsBuilder SubscriptionBuilder { get; set; } /// /// Configures the factory for AWS clients. @@ -118,129 +92,56 @@ public MessagingBusBuilder Messaging(Action confi } /// - /// Configures the subscriptions. + /// Configures the services. /// - /// A delegate to a method to use to configure subscriptions. + /// A delegate to a method to use to configure JustSaying services. /// /// The current . /// /// /// is . /// - public MessagingBusBuilder Subscriptions(Action configure) + public MessagingBusBuilder Services(Action configure) { if (configure == null) { throw new ArgumentNullException(nameof(configure)); } - if (SubscriptionBuilder == null) + if (ServicesBuilder == null) { - SubscriptionBuilder = new SubscriptionsBuilder(this); + ServicesBuilder = new ServicesBuilder(this); } - configure(SubscriptionBuilder); + configure(ServicesBuilder); return this; } /// - /// Specifies the to use. + /// Configures the subscriptions. /// - /// The to use. + /// A delegate to a method to use to configure subscriptions. /// /// The current . /// /// - /// is . + /// is . /// - public MessagingBusBuilder WithLoggerFactory(ILoggerFactory loggerFactory) + public MessagingBusBuilder Subscriptions(Action configure) { - if (loggerFactory == null) + if (configure == null) { - throw new ArgumentNullException(nameof(loggerFactory)); + throw new ArgumentNullException(nameof(configure)); } - return WithLoggerFactory(() => loggerFactory); - } - - /// - /// Specifies the to use. - /// - /// A delegate to a method to get the to use. - /// - /// The current . - /// - /// - /// is . - /// - public MessagingBusBuilder WithLoggerFactory(Func loggerFactory) - { - LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); - return this; - } - - /// - /// Specifies the to use. - /// - /// A delegate to a method to get the to use. - /// - /// The current . - /// - /// - /// is . - /// - public MessagingBusBuilder WithMessageLock(Func messageLock) - { - MessageLock = messageLock ?? throw new ArgumentNullException(nameof(messageLock)); - return this; - } - - /// - /// Specifies the to use. - /// - /// A delegate to a method to get the to use. - /// - /// The current . - /// - /// - /// is . - /// - public MessagingBusBuilder WithMessageMonitoring(Func monitoring) - { - MessageMonitoring = monitoring ?? throw new ArgumentNullException(nameof(monitoring)); - return this; - } + if (SubscriptionBuilder == null) + { + SubscriptionBuilder = new SubscriptionsBuilder(this); + } - /// - /// Specifies the to use. - /// - /// A delegate to a method to get the to use. - /// - /// The current . - /// - /// - /// is . - /// - public MessagingBusBuilder WithNamingStrategy(Func strategy) - { - NamingStrategy = strategy ?? throw new ArgumentNullException(nameof(strategy)); - return this; - } + configure(SubscriptionBuilder); - /// - /// Specifies the to use. - /// - /// A delegate to a method to get the to use. - /// - /// The current . - /// - /// - /// is . - /// - public MessagingBusBuilder WithMessageSerializationFactory(Func factory) - { - MessageSerializationFactory = factory ?? throw new ArgumentNullException(nameof(factory)); return this; } @@ -273,14 +174,14 @@ public IMessagingBus Build() config.Validate(); ILoggerFactory loggerFactory = - LoggerFactory?.Invoke() ?? ServiceResolver.ResolveService(); + ServicesBuilder?.LoggerFactory?.Invoke() ?? ServiceResolver.ResolveService(); JustSayingBus bus = CreateBus(config, loggerFactory); JustSayingFluently fluent = CreateFluent(bus, loggerFactory); - if (NamingStrategy != null) + if (ServicesBuilder?.NamingStrategy != null) { - fluent.WithNamingStrategy(NamingStrategy); + fluent.WithNamingStrategy(ServicesBuilder.NamingStrategy); } // TODO Publishers @@ -296,7 +197,7 @@ public IMessagingBus Build() private JustSayingBus CreateBus(IMessagingConfig config, ILoggerFactory loggerFactory) { IMessageSerializationRegister register = - SerializationRegister?.Invoke() ?? ServiceResolver.ResolveService(); + ServicesBuilder?.SerializationRegister?.Invoke() ?? ServiceResolver.ResolveService(); return new JustSayingBus(config, register, loggerFactory); } @@ -317,12 +218,12 @@ private IAwsClientFactoryProxy CreateFactoryProxy() private IMessageMonitor CreateMessageMonitor() { - return MessageMonitoring?.Invoke() ?? ServiceResolver.ResolveService(); + return ServicesBuilder?.MessageMonitoring?.Invoke() ?? ServiceResolver.ResolveService(); } private IMessageSerializationFactory CreateMessageSerializationFactory() { - return MessageSerializationFactory?.Invoke() ?? ServiceResolver.ResolveService(); + return ServicesBuilder?.MessageSerializationFactory?.Invoke() ?? ServiceResolver.ResolveService(); } private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory loggerFactory) @@ -338,9 +239,9 @@ private JustSayingFluently CreateFluent(JustSayingBus bus, ILoggerFactory logger fluent.WithSerializationFactory(serializationFactory) .WithMonitoring(messageMonitor); - if (MessageLock != null) + if (ServicesBuilder?.MessageLock != null) { - fluent.WithMessageLockStoreOf(MessageLock()); + fluent.WithMessageLockStoreOf(ServicesBuilder.MessageLock()); } return fluent; diff --git a/JustSaying/Fluent/ServicesBuilder.cs b/JustSaying/Fluent/ServicesBuilder.cs new file mode 100644 index 000000000..e1fa6a0f0 --- /dev/null +++ b/JustSaying/Fluent/ServicesBuilder.cs @@ -0,0 +1,156 @@ +using System; +using JustSaying.Messaging.MessageHandling; +using JustSaying.Messaging.MessageSerialization; +using JustSaying.Messaging.Monitoring; +using Microsoft.Extensions.Logging; + +namespace JustSaying.Fluent +{ + /// + /// Defines a builder for services used by JustSaying. This class cannot be inherited. + /// + public sealed class ServicesBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The that owns this instance. + internal ServicesBuilder(MessagingBusBuilder busBuilder) + { + BusBuilder = busBuilder; + } + + /// + internal MessagingBusBuilder BusBuilder { get; } + + /// + /// Gets or sets a delegate to a method to create the to use. + /// + internal Func LoggerFactory { get; private set; } + + /// + /// Gets or sets a delegate to a method to create the to use. + /// + internal Func MessageMonitoring { get; private set; } + + /// + /// Gets or sets a delegate to a method to create the to use. + /// + internal Func MessageLock { get; private set; } + + /// + /// Gets or sets a delegate to a method to create the to use. + /// + internal Func MessageSerializationFactory { get; private set; } + + /// + /// Gets or sets a delegate to a method to create the to use. + /// + internal Func NamingStrategy { get; private set; } + + /// + /// Gets or sets a delegate to a method to create the to use. + /// + internal Func SerializationRegister { get; private set; } + + /// + /// Specifies the to use. + /// + /// The to use. + /// + /// The current . + /// + /// + /// is . + /// + public ServicesBuilder WithLoggerFactory(ILoggerFactory loggerFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + return WithLoggerFactory(() => loggerFactory); + } + + /// + /// Specifies the to use. + /// + /// A delegate to a method to get the to use. + /// + /// The current . + /// + /// + /// is . + /// + public ServicesBuilder WithLoggerFactory(Func loggerFactory) + { + LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + return this; + } + + /// + /// Specifies the to use. + /// + /// A delegate to a method to get the to use. + /// + /// The current . + /// + /// + /// is . + /// + public ServicesBuilder WithMessageLock(Func messageLock) + { + MessageLock = messageLock ?? throw new ArgumentNullException(nameof(messageLock)); + return this; + } + + /// + /// Specifies the to use. + /// + /// A delegate to a method to get the to use. + /// + /// The current . + /// + /// + /// is . + /// + public ServicesBuilder WithMessageMonitoring(Func monitoring) + { + MessageMonitoring = monitoring ?? throw new ArgumentNullException(nameof(monitoring)); + return this; + } + + /// + /// Specifies the to use. + /// + /// A delegate to a method to get the to use. + /// + /// The current . + /// + /// + /// is . + /// + public ServicesBuilder WithNamingStrategy(Func strategy) + { + NamingStrategy = strategy ?? throw new ArgumentNullException(nameof(strategy)); + return this; + } + + /// + /// Specifies the to use. + /// + /// A delegate to a method to get the to use. + /// + /// The current . + /// + /// + /// is . + /// + public ServicesBuilder WithMessageSerializationFactory(Func factory) + { + MessageSerializationFactory = factory ?? throw new ArgumentNullException(nameof(factory)); + return this; + } + } +} From 6bca0637917f4aab1b7202e58258d4c6986c6126 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 19:46:42 +0000 Subject: [PATCH 23/43] Update test to use Services() Update the integration test to use the new Services() method. --- .../Fluent/MessagingBusBuilderTests.cs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 5ae0e1840..87163bc1a 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using JustSaying.Fluent; using JustSaying.Messaging.MessageHandling; +using JustSaying.Messaging.Monitoring; using JustSaying.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -36,7 +37,9 @@ public void Can_Create_Messaging_Bus_Fluently() (options) => options.WithRegions("eu-west-1", "eu-central-1") .WithActiveRegion("eu-west-1")) .Subscriptions( - (options) => options.WithSubscription((p) => p.IntoQueue("foo"))); + (options) => options.WithSubscription((p) => p.IntoQueue("foo"))) + .Services( + (options) => options.WithMessageMonitoring(() => new MyMonitor())); }) .AddJustSayingHandler(); @@ -74,5 +77,36 @@ public Task Handle(MyMessage message) return Task.FromResult(true); } } + + private sealed class MyMonitor : IMessageMonitor + { + public void HandleException(Type messageType) + { + } + + public void HandleThrottlingTime(long handleTimeMs) + { + } + + public void HandleTime(long handleTimeMs) + { + } + + public void IncrementThrottlingStatistic() + { + } + + public void IssuePublishingMessage() + { + } + + public void PublishMessageTime(long handleTimeMs) + { + } + + public void ReceiveMessageTime(long handleTimeMs, string queueName, string region) + { + } + } } } From 9aeef4c82e114e9921a26d6755caeddd8b2a92e6 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 20:13:07 +0000 Subject: [PATCH 24/43] Add publication support Add initial support to the builder for topic and queue publishing. --- .../Fluent/MessagingBusBuilderTests.cs | 3 + JustSaying/Fluent/IPublicationBuilder`1.cs | 19 +++ JustSaying/Fluent/MessagingBusBuilder.cs | 39 +++++- JustSaying/Fluent/PublicationsBuilder.cs | 129 ++++++++++++++++++ .../Fluent/QueuePublicationBuilder`1.cs | 50 +++++++ .../Fluent/TopicPublicationBuilder`1.cs | 50 +++++++ JustSaying/JustSayingFluently.cs | 2 +- 7 files changed, 289 insertions(+), 3 deletions(-) create mode 100644 JustSaying/Fluent/IPublicationBuilder`1.cs create mode 100644 JustSaying/Fluent/PublicationsBuilder.cs create mode 100644 JustSaying/Fluent/QueuePublicationBuilder`1.cs create mode 100644 JustSaying/Fluent/TopicPublicationBuilder`1.cs diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 87163bc1a..352e3b80d 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -36,6 +36,9 @@ public void Can_Create_Messaging_Bus_Fluently() .Messaging( (options) => options.WithRegions("eu-west-1", "eu-central-1") .WithActiveRegion("eu-west-1")) + .Publications( + (options) => options.WithQueue() + .WithTopic()) .Subscriptions( (options) => options.WithSubscription((p) => p.IntoQueue("foo"))) .Services( diff --git a/JustSaying/Fluent/IPublicationBuilder`1.cs b/JustSaying/Fluent/IPublicationBuilder`1.cs new file mode 100644 index 000000000..26becdcb8 --- /dev/null +++ b/JustSaying/Fluent/IPublicationBuilder`1.cs @@ -0,0 +1,19 @@ +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// Defines a builder for a publication. + /// + /// + /// The type of the messages to publish. + /// + internal interface IPublicationBuilder + where T : Message + { + /// + /// Configures the publication for the . + /// + void Configure(JustSayingFluently bus); + } +} diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/Fluent/MessagingBusBuilder.cs index 9d5db4957..5946a0c44 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/Fluent/MessagingBusBuilder.cs @@ -27,6 +27,11 @@ public class MessagingBusBuilder /// private MessagingConfigurationBuilder MessagingConfig { get; set; } + /// + /// Gets or sets the builder to use for publications. + /// + private PublicationsBuilder PublicationsBuilder { get; set; } + /// /// Gets or sets the builder to use for services. /// @@ -91,6 +96,33 @@ public MessagingBusBuilder Messaging(Action confi return this; } + /// + /// Configures the publications. + /// + /// A delegate to a method to use to configure publications. + /// + /// The current . + /// + /// + /// is . + /// + public MessagingBusBuilder Publications(Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + if (PublicationsBuilder == null) + { + PublicationsBuilder = new PublicationsBuilder(this); + } + + configure(PublicationsBuilder); + + return this; + } + /// /// Configures the services. /// @@ -184,8 +216,11 @@ public IMessagingBus Build() fluent.WithNamingStrategy(ServicesBuilder.NamingStrategy); } - // TODO Publishers - // TODO Where do topic/queue names come in? + if (PublicationsBuilder != null) + { + PublicationsBuilder.Configure(fluent); + } + if (SubscriptionBuilder != null) { SubscriptionBuilder.Configure(fluent); diff --git a/JustSaying/Fluent/PublicationsBuilder.cs b/JustSaying/Fluent/PublicationsBuilder.cs new file mode 100644 index 000000000..21b99b1c3 --- /dev/null +++ b/JustSaying/Fluent/PublicationsBuilder.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for publications. This class cannot be inherited. + /// + public sealed class PublicationsBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The that owns this instance. + internal PublicationsBuilder(MessagingBusBuilder parent) + { + Parent = parent; + } + + /// + /// Gets the parent of this builder. + /// + internal MessagingBusBuilder Parent { get; } + + /// + /// Gets the configured publication builders. + /// + private IList> Publications { get; } = new List>(); + + /// + /// Configures a publisher for a queue. + /// + /// + /// The current . + /// + /// + /// is . + /// + public PublicationsBuilder WithQueue() + where T : Message + { + Publications.Add(new QueuePublicationBuilder()); + return this; + } + + /// + /// Configures a publisher for a queue. + /// + /// A delegate to a method to use to configure a queue. + /// + /// The current . + /// + /// + /// is . + /// + public PublicationsBuilder WithQueue(Action> configure) + where T : Message + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new QueuePublicationBuilder(); + + configure(builder); + + Publications.Add(builder); + + return this; + } + + /// + /// Configures a publisher for a topic. + /// + /// + /// The current . + /// + /// + /// is . + /// + public PublicationsBuilder WithTopic() + where T : Message + { + Publications.Add(new TopicPublicationBuilder()); + return this; + } + + /// + /// Configures a publisher for a topic. + /// + /// A delegate to a method to use to configure a topic. + /// + /// The current . + /// + /// + /// is . + /// + public PublicationsBuilder WithTopic(Action> configure) + where T : Message + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new TopicPublicationBuilder(); + + configure(builder); + + Publications.Add(builder); + + return this; + } + + /// + /// Configures the publications for the . + /// + /// The to configure publications for. + internal void Configure(JustSayingFluently bus) + { + foreach (IPublicationBuilder builder in Publications) + { + builder.Configure(bus); + } + } + } +} diff --git a/JustSaying/Fluent/QueuePublicationBuilder`1.cs b/JustSaying/Fluent/QueuePublicationBuilder`1.cs new file mode 100644 index 000000000..8eea26b67 --- /dev/null +++ b/JustSaying/Fluent/QueuePublicationBuilder`1.cs @@ -0,0 +1,50 @@ +using System; +using JustSaying.AwsTools.QueueCreation; +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for a queue publication. This class cannot be inherited. + /// + /// + /// The type of the message published to the queue. + /// + public sealed class QueuePublicationBuilder : IPublicationBuilder + where T : Message + { + /// + /// Initializes a new instance of the class. + /// + internal QueuePublicationBuilder() + { + } + + /// + /// Gets or sets a delegate to a method to use to configure SQS writes. + /// + private Action ConfigureWrites { get; set; } + + /// + /// Configures the SQS write configuration. + /// + /// A delegate to a method to use to configure SQS writes. + /// + /// The current . + /// + /// + /// is . + /// + public QueuePublicationBuilder WithWriteConfiguration(Action configure) + { + ConfigureWrites = configure ?? throw new ArgumentNullException(nameof(configure)); + return this; + } + + /// + void IPublicationBuilder.Configure(JustSayingFluently bus) + { + bus.WithSqsMessagePublisher(ConfigureWrites); + } + } +} diff --git a/JustSaying/Fluent/TopicPublicationBuilder`1.cs b/JustSaying/Fluent/TopicPublicationBuilder`1.cs new file mode 100644 index 000000000..6aac5af6b --- /dev/null +++ b/JustSaying/Fluent/TopicPublicationBuilder`1.cs @@ -0,0 +1,50 @@ +using System; +using JustSaying.AwsTools.QueueCreation; +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for a topic publication. This class cannot be inherited. + /// + /// + /// The type of the message. + /// + public sealed class TopicPublicationBuilder : IPublicationBuilder + where T : Message + { + /// + /// Initializes a new instance of the class. + /// + internal TopicPublicationBuilder() + { + } + + /// + /// Gets or sets a delegate to a method to use to configure SNS writes. + /// + private Action ConfigureWrites { get; set; } + + /// + /// Configures the SNS write configuration. + /// + /// A delegate to a method to use to configure SNS writes. + /// + /// The current . + /// + /// + /// is . + /// + public TopicPublicationBuilder WithWriteConfiguration(Action configure) + { + ConfigureWrites = configure ?? throw new ArgumentNullException(nameof(configure)); + return this; + } + + /// + void IPublicationBuilder.Configure(JustSayingFluently bus) + { + bus.WithSnsMessagePublisher(ConfigureWrites); + } + } +} diff --git a/JustSaying/JustSayingFluently.cs b/JustSaying/JustSayingFluently.cs index 37a3a2b61..4837092fd 100644 --- a/JustSaying/JustSayingFluently.cs +++ b/JustSaying/JustSayingFluently.cs @@ -122,7 +122,7 @@ public IHaveFulfilledPublishRequirements WithSqsMessagePublisher(Action Date: Mon, 26 Nov 2018 21:25:46 +0000 Subject: [PATCH 25/43] Extend test to publish and subscribe Extend the integration tests to publish a message and then validate that it is received. --- .../Fluent/MessagingBusBuilderTests.cs | 70 +++++++++++++++---- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 352e3b80d..797823922 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -2,11 +2,13 @@ using System.Threading; using System.Threading.Tasks; using JustSaying.Fluent; +using JustSaying.Messaging; using JustSaying.Messaging.MessageHandling; using JustSaying.Messaging.Monitoring; using JustSaying.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Shouldly; using Xunit; using Xunit.Abstractions; @@ -22,9 +24,11 @@ public MessagingBusBuilderTests(ITestOutputHelper outputHelper) private ITestOutputHelper OutputHelper { get; } [AwsFact] - public void Can_Create_Messaging_Bus_Fluently() + public async Task Can_Create_Messaging_Bus_Fluently() { // Arrange + var handler2 = new Handler2(); + var services = new ServiceCollection() .AddLogging((p) => p.AddXUnit(OutputHelper)) .AddJustSaying( @@ -37,20 +41,43 @@ public void Can_Create_Messaging_Bus_Fluently() (options) => options.WithRegions("eu-west-1", "eu-central-1") .WithActiveRegion("eu-west-1")) .Publications( - (options) => options.WithQueue() - .WithTopic()) + (options) => options.WithQueue() + .WithTopic()) .Subscriptions( - (options) => options.WithSubscription((p) => p.IntoQueue("foo"))) + (options) => options.WithSubscription((p) => p.IntoQueue("foo")) + .WithSubscription((p) => p.IntoQueue("bar"))) .Services( (options) => options.WithMessageMonitoring(() => new MyMonitor())); }) - .AddJustSayingHandler(); + .AddJustSayingHandler() + .AddSingleton>(handler2); IServiceProvider serviceProvider = services.BuildServiceProvider(); + IMessagingBus bus = serviceProvider.GetRequiredService(); - // Assert - var bus = serviceProvider.GetRequiredService(); - bus.Start(new CancellationToken(canceled: true)); + using (var source = new CancellationTokenSource(TimeSpan.FromSeconds(30))) + { + // Act + bus.Start(source.Token); + + IMessagePublisher publisher = bus as IMessagePublisher; // HACK For now before first-class support + publisher.ShouldNotBeNull(); + + var message1 = new Message1(); + var message2 = new Message2(); + + await publisher.PublishAsync(message1); + await publisher.PublishAsync(message2); + + // Assert + while (!source.IsCancellationRequested && handler2.Count == 0) + { + await Task.Delay(TimeSpan.FromSeconds(1), source.Token); + } + + handler2.Count.ShouldBeGreaterThanOrEqualTo(1); + handler2.LastId.ShouldBe(message2.Id); + } } [Fact] @@ -60,7 +87,7 @@ public void Can_Create_Messaging_Bus() var services = new ServiceCollection() .AddLogging((p) => p.AddXUnit(OutputHelper)) .AddJustSaying("eu-west-1") - .AddJustSayingHandler(); + .AddJustSayingHandler(); IServiceProvider serviceProvider = services.BuildServiceProvider(); @@ -69,18 +96,37 @@ public void Can_Create_Messaging_Bus() bus.Start(new CancellationToken(canceled: true)); } - private sealed class MyMessage : Message + private sealed class Message1 : Message { } - private sealed class MyHandler : IHandlerAsync + private sealed class Message2 : Message { - public Task Handle(MyMessage message) + } + + private sealed class Handler1 : IHandlerAsync + { + public Task Handle(Message1 message) { return Task.FromResult(true); } } + private sealed class Handler2 : IHandlerAsync + { + internal int Count { get; set; } + + internal Guid LastId { get; set; } + + public Task Handle(Message2 message) + { + Count++; + LastId = message.Id; + + return Task.FromResult(true); + } + } + private sealed class MyMonitor : IMessageMonitor { public void HandleException(Type messageType) From 5cb1df2ba4053eac184357f3c611ff3217f3e930 Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 21:51:46 +0000 Subject: [PATCH 26/43] Add missing method for IsPointToPoint Add missing builder method for setting IsPointToPoint to true. --- JustSaying/Fluent/SubscriptionBuilder`1.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/JustSaying/Fluent/SubscriptionBuilder`1.cs b/JustSaying/Fluent/SubscriptionBuilder`1.cs index bbd272018..35a537146 100644 --- a/JustSaying/Fluent/SubscriptionBuilder`1.cs +++ b/JustSaying/Fluent/SubscriptionBuilder`1.cs @@ -35,6 +35,18 @@ internal SubscriptionBuilder() /// private Action ConfigureReads { get; set; } + /// + /// Configures that a point-to-point subscription is being used. + /// + /// + /// The current . + /// + public SubscriptionBuilder AsPointToPoint() + { + IsPointToPoint = true; // TODO Means SQS not SNS, factor this away better + return this; + } + /// /// Configures that the default queue name should be used. /// From 1a1c447daac3ff5fbcfc821309e7e338eb2b03db Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 26 Nov 2018 22:01:44 +0000 Subject: [PATCH 27/43] Separate queues and topic subscriptions Make the distinction between topics and queues clearer for subscriptions. --- .../Fluent/MessagingBusBuilderTests.cs | 4 +- ...der`1.cs => QueueSubscriptionBuilder`1.cs} | 62 +++------- JustSaying/Fluent/SubscriptionsBuilder.cs | 35 +++++- .../Fluent/TopicSubscriptionBuilder`1.cs | 112 ++++++++++++++++++ 4 files changed, 162 insertions(+), 51 deletions(-) rename JustSaying/Fluent/{SubscriptionBuilder`1.cs => QueueSubscriptionBuilder`1.cs} (58%) create mode 100644 JustSaying/Fluent/TopicSubscriptionBuilder`1.cs diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 797823922..81b148b20 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -44,8 +44,8 @@ public async Task Can_Create_Messaging_Bus_Fluently() (options) => options.WithQueue() .WithTopic()) .Subscriptions( - (options) => options.WithSubscription((p) => p.IntoQueue("foo")) - .WithSubscription((p) => p.IntoQueue("bar"))) + (options) => options.ForQueue((p) => p.WithName("foo")) + .ForTopic((p) => p.WithName("bar"))) .Services( (options) => options.WithMessageMonitoring(() => new MyMonitor())); }) diff --git a/JustSaying/Fluent/SubscriptionBuilder`1.cs b/JustSaying/Fluent/QueueSubscriptionBuilder`1.cs similarity index 58% rename from JustSaying/Fluent/SubscriptionBuilder`1.cs rename to JustSaying/Fluent/QueueSubscriptionBuilder`1.cs index 35a537146..36769a99e 100644 --- a/JustSaying/Fluent/SubscriptionBuilder`1.cs +++ b/JustSaying/Fluent/QueueSubscriptionBuilder`1.cs @@ -5,26 +5,21 @@ namespace JustSaying.Fluent { /// - /// A class representing a builder for a subscription. This class cannot be inherited. + /// A class representing a builder for a queue subscription. This class cannot be inherited. /// /// /// The type of the message. /// - public sealed class SubscriptionBuilder : ISubscriptionBuilder + public sealed class QueueSubscriptionBuilder : ISubscriptionBuilder where T : Message { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - internal SubscriptionBuilder() + internal QueueSubscriptionBuilder() { } - /// - /// Gets or sets a value indicating whether to use a point-to-point subscription. - /// - private bool IsPointToPoint { get; set; } - /// /// Gets or sets the queue name. /// @@ -35,38 +30,26 @@ internal SubscriptionBuilder() /// private Action ConfigureReads { get; set; } - /// - /// Configures that a point-to-point subscription is being used. - /// - /// - /// The current . - /// - public SubscriptionBuilder AsPointToPoint() - { - IsPointToPoint = true; // TODO Means SQS not SNS, factor this away better - return this; - } - /// /// Configures that the default queue name should be used. /// /// - /// The current . + /// The current . /// - public SubscriptionBuilder IntoDefaultQueue() - => IntoQueue(string.Empty); + public QueueSubscriptionBuilder WithDefaultQueue() + => WithName(string.Empty); /// /// Configures the name of the queue. /// /// The name of the queue to subscribe to. /// - /// The current . + /// The current . /// /// /// is . /// - public SubscriptionBuilder IntoQueue(string name) + public QueueSubscriptionBuilder WithName(string name) { QueueName = name ?? throw new ArgumentNullException(nameof(name)); return this; @@ -77,12 +60,12 @@ public SubscriptionBuilder IntoQueue(string name) /// /// A delegate to a method to use to configure SQS reads. /// - /// The current . + /// The current . /// /// /// is . /// - public SubscriptionBuilder WithReadConfiguration(Action configure) + public QueueSubscriptionBuilder WithReadConfiguration(Action configure) { if (configure == null) { @@ -102,12 +85,12 @@ public SubscriptionBuilder WithReadConfiguration(Action /// A delegate to a method to use to configure SQS reads. /// - /// The current . + /// The current . /// /// /// is . /// - public SubscriptionBuilder WithReadConfiguration(Action configure) + public QueueSubscriptionBuilder WithReadConfiguration(Action configure) { ConfigureReads = configure ?? throw new ArgumentNullException(nameof(configure)); return this; @@ -116,24 +99,13 @@ public SubscriptionBuilder WithReadConfiguration(Action /// void ISubscriptionBuilder.Configure(JustSayingFluently bus, IHandlerResolver resolver) { - ISubscriberIntoQueue queue; - - if (IsPointToPoint) - { - queue = bus.WithSqsPointToPointSubscriber(); - } - else - { - queue = bus.WithSqsTopicSubscriber(); - } - - var configured = queue - .IntoQueue(QueueName) - .WithMessageHandler(resolver); + var queue = bus.WithSqsPointToPointSubscriber() + .IntoQueue(QueueName) + .WithMessageHandler(resolver); if (ConfigureReads != null) { - configured.ConfigureSubscriptionWith(ConfigureReads); + queue.ConfigureSubscriptionWith(ConfigureReads); } } } diff --git a/JustSaying/Fluent/SubscriptionsBuilder.cs b/JustSaying/Fluent/SubscriptionsBuilder.cs index 8448b2634..565490d25 100644 --- a/JustSaying/Fluent/SubscriptionsBuilder.cs +++ b/JustSaying/Fluent/SubscriptionsBuilder.cs @@ -29,16 +29,16 @@ internal SubscriptionsBuilder(MessagingBusBuilder parent) private IList> Subscriptions { get; } = new List>(); /// - /// Configures a subscription. + /// Configures a queue subscription. /// - /// A delegate to a method to use to configure a subscription. + /// A delegate to a method to use to configure a queue subscription. /// /// The current . /// /// /// is . /// - public SubscriptionsBuilder WithSubscription(Action> configure) + public SubscriptionsBuilder ForQueue(Action> configure) where T : Message { if (configure == null) @@ -46,7 +46,34 @@ public SubscriptionsBuilder WithSubscription(Action> c throw new ArgumentNullException(nameof(configure)); } - var builder = new SubscriptionBuilder(); + var builder = new QueueSubscriptionBuilder(); + + configure(builder); + + Subscriptions.Add(builder); + + return this; + } + + /// + /// Configures a topic subscription. + /// + /// A delegate to a method to use to configure a topic subscription. + /// + /// The current . + /// + /// + /// is . + /// + public SubscriptionsBuilder ForTopic(Action> configure) + where T : Message + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new TopicSubscriptionBuilder(); configure(builder); diff --git a/JustSaying/Fluent/TopicSubscriptionBuilder`1.cs b/JustSaying/Fluent/TopicSubscriptionBuilder`1.cs new file mode 100644 index 000000000..a56c0855e --- /dev/null +++ b/JustSaying/Fluent/TopicSubscriptionBuilder`1.cs @@ -0,0 +1,112 @@ +using System; +using JustSaying.AwsTools.QueueCreation; +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for a topic subscription. This class cannot be inherited. + /// + /// + /// The type of the message. + /// + public sealed class TopicSubscriptionBuilder : ISubscriptionBuilder + where T : Message + { + /// + /// Initializes a new instance of the class. + /// + internal TopicSubscriptionBuilder() + { + } + + /// + /// Gets or sets the topic name. + /// + private string TopicName { get; set; } = string.Empty; + + /// + /// Gets or sets a delegate to a method to use to configure SQS reads. + /// + private Action ConfigureReads { get; set; } + + /// + /// Configures that the default topic name should be used. + /// + /// + /// The current . + /// + public TopicSubscriptionBuilder IntoDefaultTopic() + => WithName(string.Empty); + + /// + /// Configures the name of the topic. + /// + /// The name of the topic to subscribe to. + /// + /// The current . + /// + /// + /// is . + /// + public TopicSubscriptionBuilder WithName(string name) + { + TopicName = name ?? throw new ArgumentNullException(nameof(name)); + return this; + } + + /// + /// Configures the SQS read configuration. + /// + /// A delegate to a method to use to configure SQS reads. + /// + /// The current . + /// + /// + /// is . + /// + public TopicSubscriptionBuilder WithReadConfiguration(Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new SqsReadConfigurationBuilder(); + + configure(builder); + + ConfigureReads = builder.Configure; + return this; + } + + /// + /// Configures the SQS read configuration. + /// + /// A delegate to a method to use to configure SQS reads. + /// + /// The current . + /// + /// + /// is . + /// + public TopicSubscriptionBuilder WithReadConfiguration(Action configure) + { + ConfigureReads = configure ?? throw new ArgumentNullException(nameof(configure)); + return this; + } + + /// + void ISubscriptionBuilder.Configure(JustSayingFluently bus, IHandlerResolver resolver) + { + var topic = bus.WithSqsTopicSubscriber() + .IntoQueue(TopicName) + .WithMessageHandler(resolver); + + if (ConfigureReads != null) + { + topic.ConfigureSubscriptionWith(ConfigureReads); + } + } + } +} From e735f5e4e5433ed99da881166cd4cac6c54f8954 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 13:07:23 +0000 Subject: [PATCH 28/43] Move builder to root namespace Move MessagingBusBuilder to the root namespace for discoverability. In simple cases a consumer could even configure a bus without even having to include the JustSaying.Fluent namespace. --- JustSaying/{Fluent => }/MessagingBusBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename JustSaying/{Fluent => }/MessagingBusBuilder.cs (99%) diff --git a/JustSaying/Fluent/MessagingBusBuilder.cs b/JustSaying/MessagingBusBuilder.cs similarity index 99% rename from JustSaying/Fluent/MessagingBusBuilder.cs rename to JustSaying/MessagingBusBuilder.cs index 5946a0c44..a2090a94d 100644 --- a/JustSaying/Fluent/MessagingBusBuilder.cs +++ b/JustSaying/MessagingBusBuilder.cs @@ -1,11 +1,12 @@ using System; using JustSaying.AwsTools; using JustSaying.AwsTools.QueueCreation; +using JustSaying.Fluent; using JustSaying.Messaging.MessageSerialization; using JustSaying.Messaging.Monitoring; using Microsoft.Extensions.Logging; -namespace JustSaying.Fluent +namespace JustSaying { /// /// A class representing a builder for an . From 44448ffbc719bca233eb1bde92536043a3dc7eab Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 13:17:54 +0000 Subject: [PATCH 29/43] Move IServiceCollectionExtensions Move IServiceCollectionExtensions to the same space as the DI container itself so it's more discoverable. At a later date it should probably be in it's own JustSaying extensions NuGet package. --- .../Fluent/MessagingBusBuilderTests.cs | 1 - .../IServiceCollectionExtensions.cs | 19 +++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) rename JustSaying/{Fluent => Microsoft/Extensions/DependencyInjection}/IServiceCollectionExtensions.cs (89%) diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 81b148b20..5fe6fe749 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -1,7 +1,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using JustSaying.Fluent; using JustSaying.Messaging; using JustSaying.Messaging.MessageHandling; using JustSaying.Messaging.Monitoring; diff --git a/JustSaying/Fluent/IServiceCollectionExtensions.cs b/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs similarity index 89% rename from JustSaying/Fluent/IServiceCollectionExtensions.cs rename to JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs index 7e77f500e..f62096185 100644 --- a/JustSaying/Fluent/IServiceCollectionExtensions.cs +++ b/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs @@ -1,15 +1,21 @@ using System; +using System.ComponentModel; +using JustSaying; using JustSaying.AwsTools; using JustSaying.AwsTools.QueueCreation; +using JustSaying.Fluent; using JustSaying.Messaging.MessageHandling; using JustSaying.Messaging.MessageSerialization; using JustSaying.Messaging.Monitoring; using JustSaying.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -namespace JustSaying.Fluent +namespace Microsoft.Extensions.DependencyInjection { + /// + /// A class containing extension methods for the interface. This class cannot be inherited. + /// + [EditorBrowsable(EditorBrowsableState.Never)] public static class IServiceCollectionExtensions { // TODO This is here for convenience while protyping, would probably live elsewhere @@ -27,12 +33,9 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, throw new ArgumentNullException(nameof(regions)); } - return services - .AddJustSaying( - (builder) => - { - builder.Messaging((options) => options.WithRegions(regions)); - }); + return services.AddJustSaying( + (builder) => builder.Messaging( + (options) => options.WithRegions(regions))); } public static IServiceCollection AddJustSaying(this IServiceCollection services, Action configure) From 1909a8983b319f5c11c2e8da4863a1fbf3b3b087 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 13:21:24 +0000 Subject: [PATCH 30/43] Add XML documentation Add XML documentation to the extension methods. --- .../IServiceCollectionExtensions.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs b/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs index f62096185..68bee7047 100644 --- a/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs +++ b/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs @@ -21,6 +21,17 @@ public static class IServiceCollectionExtensions // TODO This is here for convenience while protyping, would probably live elsewhere // so we don't need to force the dependency on MS' DI types + /// + /// Adds JustSaying services to the service collection. + /// + /// The to add JustSaying services to. + /// The AWS region(s) to configure. + /// + /// The specified by . + /// + /// + /// or is . + /// public static IServiceCollection AddJustSaying(this IServiceCollection services, params string[] regions) { if (services == null) @@ -38,6 +49,17 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, (options) => options.WithRegions(regions))); } + /// + /// Adds JustSaying services to the service collection. + /// + /// The to add JustSaying services to. + /// A delegate to a method to use to configure JustSaying. + /// + /// The specified by . + /// + /// + /// or is . + /// public static IServiceCollection AddJustSaying(this IServiceCollection services, Action configure) { if (services == null) @@ -53,6 +75,17 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, return services.AddJustSaying((builder, _) => configure(builder)); } + /// + /// Adds JustSaying services to the service collection. + /// + /// The to add JustSaying services to. + /// A delegate to a method to use to configure JustSaying. + /// + /// The specified by . + /// + /// + /// or is . + /// public static IServiceCollection AddJustSaying(this IServiceCollection services, Action configure) { if (services == null) @@ -100,6 +133,18 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, return services; } + /// + /// Adds a JustSaying message handler to the service collection. + /// + /// The type of the message handled. + /// The type of the message handler to register. + /// The to add the message handler to. + /// + /// The specified by . + /// + /// + /// is . + /// public static IServiceCollection AddJustSayingHandler(this IServiceCollection services) where TMessage : Message where THandler : class, IHandlerAsync From bebbbf7179549a388fe022fc21235ed3e735156e Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 14:00:43 +0000 Subject: [PATCH 31/43] Split listen and publish Have different builder methods for publishing and subscribing. --- .../Fluent/MessagingBusBuilderTests.cs | 9 ++-- JustSaying/MessagingBusBuilder.cs | 42 +++++++++++++++---- .../IServiceCollectionExtensions.cs | 20 +++++++-- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 5fe6fe749..7af215bde 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -52,15 +52,14 @@ public async Task Can_Create_Messaging_Bus_Fluently() .AddSingleton>(handler2); IServiceProvider serviceProvider = services.BuildServiceProvider(); - IMessagingBus bus = serviceProvider.GetRequiredService(); + + IMessagePublisher publisher = serviceProvider.GetRequiredService(); + IMessagingBus listener = serviceProvider.GetRequiredService(); using (var source = new CancellationTokenSource(TimeSpan.FromSeconds(30))) { // Act - bus.Start(source.Token); - - IMessagePublisher publisher = bus as IMessagePublisher; // HACK For now before first-class support - publisher.ShouldNotBeNull(); + listener.Start(source.Token); var message1 = new Message1(); var message2 = new Message2(); diff --git a/JustSaying/MessagingBusBuilder.cs b/JustSaying/MessagingBusBuilder.cs index a2090a94d..842b1b0c8 100644 --- a/JustSaying/MessagingBusBuilder.cs +++ b/JustSaying/MessagingBusBuilder.cs @@ -2,6 +2,7 @@ using JustSaying.AwsTools; using JustSaying.AwsTools.QueueCreation; using JustSaying.Fluent; +using JustSaying.Messaging; using JustSaying.Messaging.MessageSerialization; using JustSaying.Messaging.Monitoring; using Microsoft.Extensions.Logging; @@ -9,9 +10,10 @@ namespace JustSaying { /// - /// A class representing a builder for an . + /// A class representing a builder for instances of + /// and . This class cannot be inherited. /// - public class MessagingBusBuilder + public sealed class MessagingBusBuilder { /// /// Gets the to use. @@ -195,12 +197,12 @@ public MessagingBusBuilder WithServiceResolver(IServiceResolver serviceResolver) } /// - /// Creates a new instance of . + /// Creates a new instance of . /// /// - /// The created instance of + /// The created instance of /// - public IMessagingBus Build() + public IMessagePublisher BuildPublisher() { IMessagingConfig config = CreateConfig(); @@ -209,8 +211,8 @@ public IMessagingBus Build() ILoggerFactory loggerFactory = ServicesBuilder?.LoggerFactory?.Invoke() ?? ServiceResolver.ResolveService(); - JustSayingBus bus = CreateBus(config, loggerFactory); - JustSayingFluently fluent = CreateFluent(bus, loggerFactory); + JustSayingBus publisher = CreateBus(config, loggerFactory); + JustSayingFluently fluent = CreateFluent(publisher, loggerFactory); if (ServicesBuilder?.NamingStrategy != null) { @@ -222,6 +224,32 @@ public IMessagingBus Build() PublicationsBuilder.Configure(fluent); } + return publisher; + } + + /// + /// Creates a new instance of . + /// + /// + /// The created instance of + /// + public IMessagingBus BuildSubscribers() + { + IMessagingConfig config = CreateConfig(); + + config.Validate(); + + ILoggerFactory loggerFactory = + ServicesBuilder?.LoggerFactory?.Invoke() ?? ServiceResolver.ResolveService(); + + JustSayingBus bus = CreateBus(config, loggerFactory); + JustSayingFluently fluent = CreateFluent(bus, loggerFactory); + + if (ServicesBuilder?.NamingStrategy != null) + { + fluent.WithNamingStrategy(ServicesBuilder.NamingStrategy); + } + if (SubscriptionBuilder != null) { SubscriptionBuilder.Configure(fluent); diff --git a/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs b/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs index 68bee7047..8a1725186 100644 --- a/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs +++ b/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs @@ -103,7 +103,6 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, services.TryAddSingleton((p) => p.GetRequiredService()); services.TryAddSingleton((p) => p.GetRequiredService()); - services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton((p) => new AwsClientFactoryProxy(p.GetRequiredService)); services.TryAddSingleton(); @@ -121,13 +120,26 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, services.TryAddSingleton( (serviceProvider) => { - var builder = serviceProvider - .GetRequiredService() + var builder = new MessagingBusBuilder() .WithServiceResolver(new ServiceProviderResolver(serviceProvider)); configure(builder, serviceProvider); - return builder.Build(); + return builder; + }); + + services.TryAddSingleton( + (serviceProvider) => + { + var builder = serviceProvider.GetRequiredService(); + return builder.BuildPublisher(); + }); + + services.TryAddSingleton( + (serviceProvider) => + { + var builder = serviceProvider.GetRequiredService(); + return builder.BuildSubscribers(); }); return services; From d136bca39e022dff293a2c4c30f957e3628342c1 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 14:27:06 +0000 Subject: [PATCH 32/43] Add configuration "callback" extensibility Add an interface allowing types to be called by DI during builder creation. --- .../Fluent/MessagingBusBuilderTests.cs | 43 +++++++++++++++++++ .../IMessageBusConfigurationContributor.cs | 16 +++++++ .../IServiceCollectionExtensions.cs | 27 ++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 JustSaying/Fluent/IMessageBusConfigurationContributor.cs diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 7af215bde..81810be9b 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using JustSaying.Fluent; using JustSaying.Messaging; using JustSaying.Messaging.MessageHandling; using JustSaying.Messaging.Monitoring; @@ -94,6 +95,48 @@ public void Can_Create_Messaging_Bus() bus.Start(new CancellationToken(canceled: true)); } + [Fact] + public void Can_Create_Messaging_Bus_With_Contributors() + { + // Arrange + var services = new ServiceCollection() + .AddLogging((p) => p.AddXUnit(OutputHelper)) + .AddJustSaying() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddJustSayingHandler(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + // Assert + var bus = serviceProvider.GetRequiredService(); + bus.Start(new CancellationToken(canceled: true)); + } + + private sealed class Configuration1 : IMessageBusConfigurationContributor + { + public Configuration1(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + private IServiceProvider ServiceProvider { get; } + + public void Configure(MessagingBusBuilder builder) + { + builder.Services((p) => p.WithMessageMonitoring(ServiceProvider.GetRequiredService)); + } + } + + private sealed class Configuration2 : IMessageBusConfigurationContributor + { + public void Configure(MessagingBusBuilder builder) + { + builder.Messaging((p) => p.WithRegion("eu-west-1")); + } + } + private sealed class Message1 : Message { } diff --git a/JustSaying/Fluent/IMessageBusConfigurationContributor.cs b/JustSaying/Fluent/IMessageBusConfigurationContributor.cs new file mode 100644 index 000000000..24bac3d02 --- /dev/null +++ b/JustSaying/Fluent/IMessageBusConfigurationContributor.cs @@ -0,0 +1,16 @@ +namespace JustSaying.Fluent +{ + //// TODO I don't like this name, but we can give it a better name later + + /// + /// Defines a method for configuring an instance of . + /// + public interface IMessageBusConfigurationContributor + { + /// + /// Configures an . + /// + /// The builder to configure. + void Configure(MessagingBusBuilder builder); + } +} diff --git a/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs b/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs index 8a1725186..0a0c8f9e2 100644 --- a/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs +++ b/JustSaying/Microsoft/Extensions/DependencyInjection/IServiceCollectionExtensions.cs @@ -21,6 +21,26 @@ public static class IServiceCollectionExtensions // TODO This is here for convenience while protyping, would probably live elsewhere // so we don't need to force the dependency on MS' DI types + /// + /// Adds JustSaying services to the service collection. + /// + /// The to add JustSaying services to. + /// + /// The specified by . + /// + /// + /// is . + /// + public static IServiceCollection AddJustSaying(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + return services.AddJustSaying((_) => { }); + } + /// /// Adds JustSaying services to the service collection. /// @@ -125,6 +145,13 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services, configure(builder, serviceProvider); + var contributors = serviceProvider.GetServices(); + + foreach (var contributor in contributors) + { + contributor.Configure(builder); + } + return builder; }); From 39c3fc9fc49b70359bbcec98618ef50e0582dc0d Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 14:36:46 +0000 Subject: [PATCH 33/43] Fix duplication regions Fix duplicate regions (I think). --- JustSaying/Fluent/MessagingConfigurationBuilder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/JustSaying/Fluent/MessagingConfigurationBuilder.cs b/JustSaying/Fluent/MessagingConfigurationBuilder.cs index b9fa3abf7..305a458eb 100644 --- a/JustSaying/Fluent/MessagingConfigurationBuilder.cs +++ b/JustSaying/Fluent/MessagingConfigurationBuilder.cs @@ -327,7 +327,10 @@ public IMessagingConfig Build() { foreach (string region in Regions) { - config.Regions.Add(region); + if (!config.Regions.Contains(region)) + { + config.Regions.Add(region); + } } } From 34791c42ae61aee818c4b1366a8b342800ce8e45 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 18:25:26 +0000 Subject: [PATCH 34/43] Add duplicate check Don't add a region to the builder if it is already registered. --- JustSaying/Fluent/MessagingConfigurationBuilder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/JustSaying/Fluent/MessagingConfigurationBuilder.cs b/JustSaying/Fluent/MessagingConfigurationBuilder.cs index 305a458eb..d7cbec1b0 100644 --- a/JustSaying/Fluent/MessagingConfigurationBuilder.cs +++ b/JustSaying/Fluent/MessagingConfigurationBuilder.cs @@ -289,7 +289,11 @@ public MessagingConfigurationBuilder WithRegion(string region) Regions = new List(); } - Regions.Add(region); + if (!Regions.Contains(region)) + { + Regions.Add(region); + } + return this; } From 311b86ea2cd287ff05d9471f69265f5614575d24 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 18:38:05 +0000 Subject: [PATCH 35/43] Extend builder integration tests Extend the tests for builders to test topics and queues. --- .../Fluent/MessagingBusBuilderTests.cs | 120 +++++++++++------- JustSaying/Fluent/SubscriptionsBuilder.cs | 27 ++++ 2 files changed, 104 insertions(+), 43 deletions(-) diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 81810be9b..ce9593a63 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -23,34 +23,63 @@ public MessagingBusBuilderTests(ITestOutputHelper outputHelper) private ITestOutputHelper OutputHelper { get; } - [AwsFact] - public async Task Can_Create_Messaging_Bus_Fluently() + [Fact] + public async Task Can_Create_Messaging_Bus_Fluently_For_A_Queue() { // Arrange - var handler2 = new Handler2(); + var services = new ServiceCollection() + .AddLogging((p) => p.AddXUnit(OutputHelper)) + .AddJustSaying( + (builder) => + { + builder.Client((options) => options.WithBasicCredentials("accessKey", "secretKey").WithServiceUrl("http://localhost:4100")) + .Messaging((options) => options.WithRegions("eu-west-1")) + .Publications((options) => options.WithQueue()) + .Subscriptions((options) => options.ForQueue()) + .Services((options) => options.WithMessageMonitoring(() => new MyMonitor())); + }) + .AddJustSayingHandler(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + IMessagePublisher publisher = serviceProvider.GetRequiredService(); + IMessagingBus listener = serviceProvider.GetRequiredService(); + + using (var source = new CancellationTokenSource(TimeSpan.FromSeconds(30))) + { + // Act + listener.Start(source.Token); + + var message = new QueueMessage(); + await publisher.PublishAsync(message, source.Token); + + // Assert + while (!source.IsCancellationRequested && QueueHandler.Count == 0) + { + await Task.Delay(TimeSpan.FromSeconds(1), source.Token); + } + + QueueHandler.Count.ShouldBeGreaterThanOrEqualTo(1); + QueueHandler.LastId.ShouldBe(message.Id); + } + } + + [Fact] + public async Task Can_Create_Messaging_Bus_Fluently_For_A_Topic() + { + // Arrange var services = new ServiceCollection() .AddLogging((p) => p.AddXUnit(OutputHelper)) .AddJustSaying( (builder) => { - builder.Client( - (options) => options.WithBasicCredentials("accessKey", "secretKey") - .WithServiceUrl("http://localhost:4100")) - .Messaging( - (options) => options.WithRegions("eu-west-1", "eu-central-1") - .WithActiveRegion("eu-west-1")) - .Publications( - (options) => options.WithQueue() - .WithTopic()) - .Subscriptions( - (options) => options.ForQueue((p) => p.WithName("foo")) - .ForTopic((p) => p.WithName("bar"))) - .Services( - (options) => options.WithMessageMonitoring(() => new MyMonitor())); + builder.Client((options) => options.WithBasicCredentials("accessKey", "secretKey").WithServiceUrl("http://localhost:4100")) + .Messaging((options) => options.WithRegions("eu-west-1")) + .Publications((options) => options.WithTopic()) + .Subscriptions((options) => options.ForTopic()); }) - .AddJustSayingHandler() - .AddSingleton>(handler2); + .AddJustSayingHandler(); IServiceProvider serviceProvider = services.BuildServiceProvider(); @@ -62,20 +91,18 @@ public async Task Can_Create_Messaging_Bus_Fluently() // Act listener.Start(source.Token); - var message1 = new Message1(); - var message2 = new Message2(); + var message = new TopicMessage(); - await publisher.PublishAsync(message1); - await publisher.PublishAsync(message2); + await publisher.PublishAsync(message, source.Token); // Assert - while (!source.IsCancellationRequested && handler2.Count == 0) + while (!source.IsCancellationRequested && TopicHandler.Count == 0) { await Task.Delay(TimeSpan.FromSeconds(1), source.Token); } - handler2.Count.ShouldBeGreaterThanOrEqualTo(1); - handler2.LastId.ShouldBe(message2.Id); + TopicHandler.Count.ShouldBeGreaterThanOrEqualTo(1); + TopicHandler.LastId.ShouldBe(message.Id); } } @@ -86,7 +113,7 @@ public void Can_Create_Messaging_Bus() var services = new ServiceCollection() .AddLogging((p) => p.AddXUnit(OutputHelper)) .AddJustSaying("eu-west-1") - .AddJustSayingHandler(); + .AddJustSayingHandler(); IServiceProvider serviceProvider = services.BuildServiceProvider(); @@ -102,10 +129,10 @@ public void Can_Create_Messaging_Bus_With_Contributors() var services = new ServiceCollection() .AddLogging((p) => p.AddXUnit(OutputHelper)) .AddJustSaying() - .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() - .AddJustSayingHandler(); + .AddJustSayingHandler(); IServiceProvider serviceProvider = services.BuildServiceProvider(); @@ -114,9 +141,9 @@ public void Can_Create_Messaging_Bus_With_Contributors() bus.Start(new CancellationToken(canceled: true)); } - private sealed class Configuration1 : IMessageBusConfigurationContributor + private sealed class MessagingContributor : IMessageBusConfigurationContributor { - public Configuration1(IServiceProvider serviceProvider) + public MessagingContributor(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; } @@ -129,7 +156,7 @@ public void Configure(MessagingBusBuilder builder) } } - private sealed class Configuration2 : IMessageBusConfigurationContributor + private sealed class RegionContributor : IMessageBusConfigurationContributor { public void Configure(MessagingBusBuilder builder) { @@ -137,29 +164,36 @@ public void Configure(MessagingBusBuilder builder) } } - private sealed class Message1 : Message + private sealed class QueueMessage : Message { } - private sealed class Message2 : Message + private sealed class QueueHandler : IHandlerAsync { - } + internal static int Count { get; set; } - private sealed class Handler1 : IHandlerAsync - { - public Task Handle(Message1 message) + internal static Guid LastId { get; set; } + + public Task Handle(QueueMessage message) { + Count++; + LastId = message.Id; + return Task.FromResult(true); } } - private sealed class Handler2 : IHandlerAsync + private sealed class TopicMessage : Message + { + } + + private sealed class TopicHandler : IHandlerAsync { - internal int Count { get; set; } + internal static int Count { get; set; } - internal Guid LastId { get; set; } + internal static Guid LastId { get; set; } - public Task Handle(Message2 message) + public Task Handle(TopicMessage message) { Count++; LastId = message.Id; diff --git a/JustSaying/Fluent/SubscriptionsBuilder.cs b/JustSaying/Fluent/SubscriptionsBuilder.cs index 565490d25..8fa0c750b 100644 --- a/JustSaying/Fluent/SubscriptionsBuilder.cs +++ b/JustSaying/Fluent/SubscriptionsBuilder.cs @@ -28,6 +28,21 @@ internal SubscriptionsBuilder(MessagingBusBuilder parent) /// private IList> Subscriptions { get; } = new List>(); + /// + /// Configures a queue subscription for the default queue. + /// + /// + /// The current . + /// + /// + /// is . + /// + public SubscriptionsBuilder ForQueue() + where T : Message + { + return ForQueue((p) => p.WithDefaultQueue()); + } + /// /// Configures a queue subscription. /// @@ -55,6 +70,18 @@ public SubscriptionsBuilder ForQueue(Action> conf return this; } + /// + /// Configures a topic subscription for the default topic name. + /// + /// + /// The current . + /// + public SubscriptionsBuilder ForTopic() + where T : Message + { + return ForTopic((p) => p.IntoDefaultTopic()); + } + /// /// Configures a topic subscription. /// From 6555ff7eeeebc046ec6539161a4728835eaaf96b Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 18:46:02 +0000 Subject: [PATCH 36/43] Extend integration tests Extend the integration tests and shorten the timeout. --- .../Fluent/MessagingBusBuilderTests.cs | 75 +++++++++++++++---- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index ce9593a63..083908b6c 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Shouldly; -using Xunit; using Xunit.Abstractions; namespace JustSaying.IntegrationTests @@ -23,7 +22,7 @@ public MessagingBusBuilderTests(ITestOutputHelper outputHelper) private ITestOutputHelper OutputHelper { get; } - [Fact] + [AwsFact] public async Task Can_Create_Messaging_Bus_Fluently_For_A_Queue() { // Arrange @@ -45,7 +44,7 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Queue() IMessagePublisher publisher = serviceProvider.GetRequiredService(); IMessagingBus listener = serviceProvider.GetRequiredService(); - using (var source = new CancellationTokenSource(TimeSpan.FromSeconds(30))) + using (var source = new CancellationTokenSource(TimeSpan.FromSeconds(20))) { // Act listener.Start(source.Token); @@ -65,7 +64,7 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Queue() } } - [Fact] + [AwsFact] public async Task Can_Create_Messaging_Bus_Fluently_For_A_Topic() { // Arrange @@ -86,7 +85,7 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Topic() IMessagePublisher publisher = serviceProvider.GetRequiredService(); IMessagingBus listener = serviceProvider.GetRequiredService(); - using (var source = new CancellationTokenSource(TimeSpan.FromSeconds(30))) + using (var source = new CancellationTokenSource(TimeSpan.FromSeconds(20))) { // Act listener.Start(source.Token); @@ -106,7 +105,7 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Topic() } } - [Fact] + [AwsFact] public void Can_Create_Messaging_Bus() { // Arrange @@ -117,28 +116,63 @@ public void Can_Create_Messaging_Bus() IServiceProvider serviceProvider = services.BuildServiceProvider(); - // Assert - var bus = serviceProvider.GetRequiredService(); - bus.Start(new CancellationToken(canceled: true)); + IMessagePublisher publisher = serviceProvider.GetRequiredService(); + IMessagingBus listener = serviceProvider.GetRequiredService(); + + using (var source = new CancellationTokenSource(TimeSpan.FromSeconds(20))) + { + // Act + listener.Start(source.Token); + } } - [Fact] - public void Can_Create_Messaging_Bus_With_Contributors() + [AwsFact] + public async Task Can_Create_Messaging_Bus_With_Contributors() { // Arrange var services = new ServiceCollection() .AddLogging((p) => p.AddXUnit(OutputHelper)) .AddJustSaying() + .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() - .AddSingleton() - .AddJustSayingHandler(); + .AddJustSayingHandler() + .AddSingleton(); IServiceProvider serviceProvider = services.BuildServiceProvider(); - // Assert - var bus = serviceProvider.GetRequiredService(); - bus.Start(new CancellationToken(canceled: true)); + IMessagePublisher publisher = serviceProvider.GetRequiredService(); + IMessagingBus listener = serviceProvider.GetRequiredService(); + + using (var source = new CancellationTokenSource(TimeSpan.FromSeconds(20))) + { + // Act + listener.Start(source.Token); + + var message = new QueueMessage(); + + await publisher.PublishAsync(message, source.Token); + + // Assert + while (!source.IsCancellationRequested && QueueHandler.Count == 0) + { + await Task.Delay(TimeSpan.FromSeconds(1), source.Token); + } + + QueueHandler.Count.ShouldBeGreaterThanOrEqualTo(1); + QueueHandler.LastId.ShouldBe(message.Id); + } + } + + private sealed class AwsContributor : IMessageBusConfigurationContributor + { + public void Configure(MessagingBusBuilder builder) + { + builder.Client( + (options) => options.WithSessionCredentials("accessKeyId", "secretKeyId", "token") + .WithServiceUrl("http://localhost:4100")); + } } private sealed class MessagingContributor : IMessageBusConfigurationContributor @@ -156,6 +190,15 @@ public void Configure(MessagingBusBuilder builder) } } + private sealed class QueueContributor : IMessageBusConfigurationContributor + { + public void Configure(MessagingBusBuilder builder) + { + builder.Publications((p) => p.WithQueue()) + .Subscriptions((p) => p.ForQueue()); + } + } + private sealed class RegionContributor : IMessageBusConfigurationContributor { public void Configure(MessagingBusBuilder builder) From 0013cf7905e405069602a99d76b01c78f82ebdf9 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 18:47:33 +0000 Subject: [PATCH 37/43] Use SimulatorUrl Use the SimulatorUrl property in the integration tests. --- .../Fluent/MessagingBusBuilderTests.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 083908b6c..778679254 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -6,6 +6,7 @@ using JustSaying.Messaging.MessageHandling; using JustSaying.Messaging.Monitoring; using JustSaying.Models; +using JustSaying.TestingFramework; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Shouldly; @@ -31,7 +32,7 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Queue() .AddJustSaying( (builder) => { - builder.Client((options) => options.WithBasicCredentials("accessKey", "secretKey").WithServiceUrl("http://localhost:4100")) + builder.Client((options) => options.WithBasicCredentials("accessKey", "secretKey").WithServiceUri(TestEnvironment.SimulatorUrl)) .Messaging((options) => options.WithRegions("eu-west-1")) .Publications((options) => options.WithQueue()) .Subscriptions((options) => options.ForQueue()) @@ -73,7 +74,7 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Topic() .AddJustSaying( (builder) => { - builder.Client((options) => options.WithBasicCredentials("accessKey", "secretKey").WithServiceUrl("http://localhost:4100")) + builder.Client((options) => options.WithBasicCredentials("accessKey", "secretKey").WithServiceUri(TestEnvironment.SimulatorUrl)) .Messaging((options) => options.WithRegions("eu-west-1")) .Publications((options) => options.WithTopic()) .Subscriptions((options) => options.ForTopic()); @@ -171,7 +172,7 @@ public void Configure(MessagingBusBuilder builder) { builder.Client( (options) => options.WithSessionCredentials("accessKeyId", "secretKeyId", "token") - .WithServiceUrl("http://localhost:4100")); + .WithServiceUri(TestEnvironment.SimulatorUrl)); } } From 0df22cbde54caeed535880fac88ebae353267b24 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 19:11:05 +0000 Subject: [PATCH 38/43] Add builder for configuring writes Add infrastructure to configure SNS and SQS writes. --- .../Fluent/QueuePublicationBuilder`1.cs | 25 ++++ .../Fluent/SnsWriteConfigurationBuilder.cs | 45 ++++++ JustSaying/Fluent/SqsConfigurationBuilder.cs | 137 ++++++++++++++++++ .../Fluent/SqsReadConfigurationBuilder.cs | 92 +----------- .../Fluent/SqsWriteConfigurationBuilder.cs | 51 +++++++ .../Fluent/TopicPublicationBuilder`1.cs | 25 ++++ .../Fluent/TopicSubscriptionBuilder`1.cs | 10 +- 7 files changed, 293 insertions(+), 92 deletions(-) create mode 100644 JustSaying/Fluent/SnsWriteConfigurationBuilder.cs create mode 100644 JustSaying/Fluent/SqsConfigurationBuilder.cs create mode 100644 JustSaying/Fluent/SqsWriteConfigurationBuilder.cs diff --git a/JustSaying/Fluent/QueuePublicationBuilder`1.cs b/JustSaying/Fluent/QueuePublicationBuilder`1.cs index 8eea26b67..71ef211fd 100644 --- a/JustSaying/Fluent/QueuePublicationBuilder`1.cs +++ b/JustSaying/Fluent/QueuePublicationBuilder`1.cs @@ -25,6 +25,31 @@ internal QueuePublicationBuilder() /// private Action ConfigureWrites { get; set; } + /// + /// Configures the SQS write configuration. + /// + /// A delegate to a method to use to configure SQS writes. + /// + /// The current . + /// + /// + /// is . + /// + public QueuePublicationBuilder WithWriteConfiguration(Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new SqsWriteConfigurationBuilder(); + + configure(builder); + + ConfigureWrites = builder.Configure; + return this; + } + /// /// Configures the SQS write configuration. /// diff --git a/JustSaying/Fluent/SnsWriteConfigurationBuilder.cs b/JustSaying/Fluent/SnsWriteConfigurationBuilder.cs new file mode 100644 index 000000000..07468374e --- /dev/null +++ b/JustSaying/Fluent/SnsWriteConfigurationBuilder.cs @@ -0,0 +1,45 @@ +using System; +using JustSaying.AwsTools.QueueCreation; +using JustSaying.Models; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for configuring instances of . This class cannot be inherited. + /// + public sealed class SnsWriteConfigurationBuilder + { + /// + /// Gets or sets the error callback to use. + /// + private Func OnError { get; set; } + + /// + /// Configures an error handler to use. + /// + /// A delegate to a method to call when an error occurs. + /// + /// The current . + /// + /// + /// is . + /// + public SnsWriteConfigurationBuilder WithErrorHandler(Func action) + { + OnError = action ?? throw new ArgumentNullException(nameof(action)); + return this; + } + + /// + /// Configures the specified . + /// + /// The configuration to configure. + internal void Configure(SnsWriteConfiguration config) + { + if (OnError != null) + { + config.HandleException = OnError; + } + } + } +} diff --git a/JustSaying/Fluent/SqsConfigurationBuilder.cs b/JustSaying/Fluent/SqsConfigurationBuilder.cs new file mode 100644 index 000000000..e4ca67d3c --- /dev/null +++ b/JustSaying/Fluent/SqsConfigurationBuilder.cs @@ -0,0 +1,137 @@ +using System; +using JustSaying.AwsTools.QueueCreation; + +namespace JustSaying.Fluent +{ + /// + /// Defines the base class for a builder for instances of . + /// + /// The type of the configuration. + /// The type of the builder. + public abstract class SqsConfigurationBuilder + where TConfiguration : SqsBasicConfiguration + where TBuilder : SqsConfigurationBuilder + { + /// + /// Initializes a new instance of the class. + /// + internal SqsConfigurationBuilder() + { + } + + /// + /// Gets the current . + /// + protected abstract TBuilder Self { get; } + + /// + /// Gets or sets a value indicating whether to opt-out of error queues. + /// + private bool? ErrorQueueOptOut { get; set; } + + /// + /// Gets or sets the message retention value to use. + /// + private TimeSpan? MessageRetention { get; set; } + + /// + /// Gets or sets the visibility timeout value to use. + /// + private TimeSpan? VisibilityTimeout { get; set; } + + /// + /// Configures that an error queue should be used. + /// + /// + /// The current . + /// + public TBuilder WithErrorQueue() + => WithErrorQueueOptOut(false); + + /// + /// Configures that no error queue should be used. + /// + /// + /// The current . + /// + public TBuilder WithNoErrorQueue() + => WithErrorQueueOptOut(true); + + /// + /// Configures whether to opt-out of an error queue. + /// + /// Whether or not to opt-out of an error queue. + /// + /// The current . + /// + public TBuilder WithErrorQueueOptOut(bool value) + { + ErrorQueueOptOut = value; + return Self; + } + + /// + /// Configures the message retention period to use. + /// + /// The value to use for the message retention. + /// + /// The current . + /// + public TBuilder WithMessageRetention(TimeSpan value) + { + MessageRetention = value; + return Self; + } + + /// + /// Configures the visibility timeout to use. + /// + /// The value to use for the visibility timeout. + /// + /// The current . + /// + public TBuilder WithVisibilityTimeout(TimeSpan value) + { + VisibilityTimeout = value; + return Self; + } + + /// + /// Configures the specified . + /// + /// The configuration to configure. + internal virtual void Configure(TConfiguration config) + { + // TODO Which ones should be configurable? All, or just the important ones? + // config.BaseQueueName = default; + // config.BaseTopicName = default; + // config.DeliveryDelay = default; + // config.ErrorQueueRetentionPeriod = default; + // config.FilterPolicy = default; + // config.MessageBackoffStrategy = default; + // config.MessageProcessingStrategy = default; + // config.PublishEndpoint = default; + // config.QueueName = default; + // config.RetryCountBeforeSendingToErrorQueue = default; + // config.ServerSideEncryption = default; + // config.Topic = default; + + if (ErrorQueueOptOut.HasValue) + { + config.ErrorQueueOptOut = ErrorQueueOptOut.Value; + } + + if (MessageRetention.HasValue) + { + config.MessageRetention = MessageRetention.Value; + } + + if (VisibilityTimeout.HasValue) + { + config.VisibilityTimeout = VisibilityTimeout.Value; + } + + config.Validate(); + } + } +} diff --git a/JustSaying/Fluent/SqsReadConfigurationBuilder.cs b/JustSaying/Fluent/SqsReadConfigurationBuilder.cs index 064711ee5..c7a4aee98 100644 --- a/JustSaying/Fluent/SqsReadConfigurationBuilder.cs +++ b/JustSaying/Fluent/SqsReadConfigurationBuilder.cs @@ -7,12 +7,10 @@ namespace JustSaying.Fluent /// /// A class representing a builder for configuring instances of . This class cannot be inherited. /// - public sealed class SqsReadConfigurationBuilder + public sealed class SqsReadConfigurationBuilder : SqsConfigurationBuilder { - /// - /// Gets or sets a value indicating whether to opt-out of error queues. - /// - private bool? ErrorQueueOptOut { get; set; } + /// + protected override SqsReadConfigurationBuilder Self => this; /// /// Gets or sets the instance position value to use. @@ -24,11 +22,6 @@ public sealed class SqsReadConfigurationBuilder /// private int? MaximumAllowedMessagesInflight { get; set; } - /// - /// Gets or sets the message retention value to use. - /// - private TimeSpan? MessageRetention { get; set; } - /// /// Gets or sets the error callback to use. /// @@ -39,11 +32,6 @@ public sealed class SqsReadConfigurationBuilder /// private string TopicSourceAccountId { get; set; } - /// - /// Gets or sets the visibility timeout value to use. - /// - private TimeSpan? VisibilityTimeout { get; set; } - /// /// Configures an error handler to use. /// @@ -81,37 +69,6 @@ public SqsReadConfigurationBuilder WithErrorHandler(Action a return this; } - /// - /// Configures that an error queue should be used. - /// - /// - /// The current . - /// - public SqsReadConfigurationBuilder WithErrorQueue() - => WithErrorQueueOptOut(false); - - /// - /// Configures that no error queue should be used. - /// - /// - /// The current . - /// - public SqsReadConfigurationBuilder WithNoErrorQueue() - => WithErrorQueueOptOut(true); - - /// - /// Configures whether to opt-out of an error queue. - /// - /// Whether or not to opt-out of an error queue. - /// - /// The current . - /// - public SqsReadConfigurationBuilder WithErrorQueueOptOut(bool value) - { - ErrorQueueOptOut = value; - return this; - } - /// /// Configures the instance position to use. /// @@ -138,19 +95,6 @@ public SqsReadConfigurationBuilder WithMaximumMessagesInflight(int value) return this; } - /// - /// Configures the message retention period to use. - /// - /// The value to use for the message retention. - /// - /// The current . - /// - public SqsReadConfigurationBuilder WithMessageRetention(TimeSpan value) - { - MessageRetention = value; - return this; - } - /// /// Configures the account Id to use for the topic source. /// @@ -164,24 +108,11 @@ public SqsReadConfigurationBuilder WithTopicSourceAccount(string id) return this; } - /// - /// Configures the visibility timeout to use. - /// - /// The value to use for the visibility timeout. - /// - /// The current . - /// - public SqsReadConfigurationBuilder WithVisibilityTimeout(TimeSpan value) - { - VisibilityTimeout = value; - return this; - } - /// /// Configures the specified . /// /// The configuration to configure. - internal void Configure(SqsReadConfiguration config) + internal override void Configure(SqsReadConfiguration config) { // TODO Which ones should be configurable? All, or just the important ones? // config.BaseQueueName = default; @@ -197,10 +128,7 @@ internal void Configure(SqsReadConfiguration config) // config.ServerSideEncryption = default; // config.Topic = default; - if (ErrorQueueOptOut.HasValue) - { - config.ErrorQueueOptOut = ErrorQueueOptOut.Value; - } + base.Configure(config); if (InstancePosition.HasValue) { @@ -212,11 +140,6 @@ internal void Configure(SqsReadConfiguration config) config.MaxAllowedMessagesInFlight = MaximumAllowedMessagesInflight.Value; } - if (MessageRetention.HasValue) - { - config.MessageRetention = MessageRetention.Value; - } - if (OnError != null) { config.OnError = OnError; @@ -227,11 +150,6 @@ internal void Configure(SqsReadConfiguration config) config.TopicSourceAccount = TopicSourceAccountId; } - if (VisibilityTimeout.HasValue) - { - config.VisibilityTimeout = VisibilityTimeout.Value; - } - config.Validate(); } } diff --git a/JustSaying/Fluent/SqsWriteConfigurationBuilder.cs b/JustSaying/Fluent/SqsWriteConfigurationBuilder.cs new file mode 100644 index 000000000..e7c5579ae --- /dev/null +++ b/JustSaying/Fluent/SqsWriteConfigurationBuilder.cs @@ -0,0 +1,51 @@ +using System; +using JustSaying.AwsTools.QueueCreation; + +namespace JustSaying.Fluent +{ + /// + /// A class representing a builder for configuring instances of . This class cannot be inherited. + /// + public sealed class SqsWriteConfigurationBuilder : SqsConfigurationBuilder + { + /// + protected override SqsWriteConfigurationBuilder Self => this; + + /// + /// Gets or sets the queue name to use. + /// + private string QueueName { get; set; } + + /// + /// Configures the queue name to use. + /// + /// The value to use for the message retention. + /// + /// The current . + /// + /// + /// is . + /// + public SqsWriteConfigurationBuilder WithQueueName(string name) + { + QueueName = name ?? throw new ArgumentNullException(nameof(name)); + return this; + } + + /// + /// Configures the specified . + /// + /// The configuration to configure. + internal override void Configure(SqsWriteConfiguration config) + { + base.Configure(config); + + if (QueueName != null) + { + config.QueueName = QueueName; + } + + config.Validate(); + } + } +} diff --git a/JustSaying/Fluent/TopicPublicationBuilder`1.cs b/JustSaying/Fluent/TopicPublicationBuilder`1.cs index 6aac5af6b..1e36b175a 100644 --- a/JustSaying/Fluent/TopicPublicationBuilder`1.cs +++ b/JustSaying/Fluent/TopicPublicationBuilder`1.cs @@ -25,6 +25,31 @@ internal TopicPublicationBuilder() /// private Action ConfigureWrites { get; set; } + /// + /// Configures the SNS write configuration. + /// + /// A delegate to a method to use to configure SNS writes. + /// + /// The current . + /// + /// + /// is . + /// + public TopicPublicationBuilder WithWriteConfiguration(Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = new SnsWriteConfigurationBuilder(); + + configure(builder); + + ConfigureWrites = builder.Configure; + return this; + } + /// /// Configures the SNS write configuration. /// diff --git a/JustSaying/Fluent/TopicSubscriptionBuilder`1.cs b/JustSaying/Fluent/TopicSubscriptionBuilder`1.cs index a56c0855e..168ec5511 100644 --- a/JustSaying/Fluent/TopicSubscriptionBuilder`1.cs +++ b/JustSaying/Fluent/TopicSubscriptionBuilder`1.cs @@ -26,7 +26,7 @@ internal TopicSubscriptionBuilder() private string TopicName { get; set; } = string.Empty; /// - /// Gets or sets a delegate to a method to use to configure SQS reads. + /// Gets or sets a delegate to a method to use to configure SNS reads. /// private Action ConfigureReads { get; set; } @@ -56,9 +56,9 @@ public TopicSubscriptionBuilder WithName(string name) } /// - /// Configures the SQS read configuration. + /// Configures the SNS read configuration. /// - /// A delegate to a method to use to configure SQS reads. + /// A delegate to a method to use to configure SNS reads. /// /// The current . /// @@ -81,9 +81,9 @@ public TopicSubscriptionBuilder WithReadConfiguration(Action - /// Configures the SQS read configuration. + /// Configures the SNS read configuration. /// - /// A delegate to a method to use to configure SQS reads. + /// A delegate to a method to use to configure SNS reads. /// /// The current . /// From 0bca671f61a491ee658216a546f352f91f9f4f1a Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 19:23:13 +0000 Subject: [PATCH 39/43] Fix test concurrency Fix concurrency issues in the tests for the builder. --- .../Fluent/MessagingBusBuilderTests.cs | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 778679254..24a6db0ae 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Concurrent; +using System.Linq; using System.Threading; using System.Threading.Tasks; using JustSaying.Fluent; @@ -55,13 +57,12 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Queue() await publisher.PublishAsync(message, source.Token); // Assert - while (!source.IsCancellationRequested && QueueHandler.Count == 0) + while (!source.IsCancellationRequested && !QueueHandler.MessageIds.Contains(message.Id)) { await Task.Delay(TimeSpan.FromSeconds(1), source.Token); } - QueueHandler.Count.ShouldBeGreaterThanOrEqualTo(1); - QueueHandler.LastId.ShouldBe(message.Id); + QueueHandler.MessageIds.ShouldContain(message.Id); } } @@ -96,13 +97,12 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Topic() await publisher.PublishAsync(message, source.Token); // Assert - while (!source.IsCancellationRequested && TopicHandler.Count == 0) + while (!source.IsCancellationRequested && !TopicHandler.MessageIds.Contains(message.Id)) { await Task.Delay(TimeSpan.FromSeconds(1), source.Token); } - TopicHandler.Count.ShouldBeGreaterThanOrEqualTo(1); - TopicHandler.LastId.ShouldBe(message.Id); + TopicHandler.MessageIds.ShouldContain(message.Id); } } @@ -156,13 +156,12 @@ public async Task Can_Create_Messaging_Bus_With_Contributors() await publisher.PublishAsync(message, source.Token); // Assert - while (!source.IsCancellationRequested && QueueHandler.Count == 0) + while (!source.IsCancellationRequested && !QueueHandler.MessageIds.Contains(message.Id)) { await Task.Delay(TimeSpan.FromSeconds(1), source.Token); } - QueueHandler.Count.ShouldBeGreaterThanOrEqualTo(1); - QueueHandler.LastId.ShouldBe(message.Id); + QueueHandler.MessageIds.ShouldContain(message.Id); } } @@ -214,15 +213,11 @@ private sealed class QueueMessage : Message private sealed class QueueHandler : IHandlerAsync { - internal static int Count { get; set; } - - internal static Guid LastId { get; set; } + internal static ConcurrentBag MessageIds { get; } = new ConcurrentBag(); public Task Handle(QueueMessage message) { - Count++; - LastId = message.Id; - + MessageIds.Add(message.Id); return Task.FromResult(true); } } @@ -233,15 +228,11 @@ private sealed class TopicMessage : Message private sealed class TopicHandler : IHandlerAsync { - internal static int Count { get; set; } - - internal static Guid LastId { get; set; } + internal static ConcurrentBag MessageIds { get; } = new ConcurrentBag(); public Task Handle(TopicMessage message) { - Count++; - LastId = message.Id; - + MessageIds.Add(message.Id); return Task.FromResult(true); } } From 170ef618bba646df2729a80f98a07c0c43dda566 Mon Sep 17 00:00:00 2001 From: martincostello Date: Tue, 27 Nov 2018 19:23:29 +0000 Subject: [PATCH 40/43] Rename parameter Rename parameter as it's not an Action. --- JustSaying/Fluent/SnsWriteConfigurationBuilder.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/JustSaying/Fluent/SnsWriteConfigurationBuilder.cs b/JustSaying/Fluent/SnsWriteConfigurationBuilder.cs index 07468374e..61f32e06d 100644 --- a/JustSaying/Fluent/SnsWriteConfigurationBuilder.cs +++ b/JustSaying/Fluent/SnsWriteConfigurationBuilder.cs @@ -12,21 +12,21 @@ public sealed class SnsWriteConfigurationBuilder /// /// Gets or sets the error callback to use. /// - private Func OnError { get; set; } + private Func Handler { get; set; } /// /// Configures an error handler to use. /// - /// A delegate to a method to call when an error occurs. + /// A delegate to a method to call when an error occurs. /// /// The current . /// /// - /// is . + /// is . /// - public SnsWriteConfigurationBuilder WithErrorHandler(Func action) + public SnsWriteConfigurationBuilder WithErrorHandler(Func handler) { - OnError = action ?? throw new ArgumentNullException(nameof(action)); + Handler = handler ?? throw new ArgumentNullException(nameof(handler)); return this; } @@ -36,9 +36,9 @@ public SnsWriteConfigurationBuilder WithErrorHandler(FuncThe configuration to configure. internal void Configure(SnsWriteConfiguration config) { - if (OnError != null) + if (Handler != null) { - config.HandleException = OnError; + config.HandleException = Handler; } } } From 536a98924816175969a25adaa9a609c1ace84547 Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Thu, 29 Nov 2018 08:37:35 +0000 Subject: [PATCH 41/43] Remove commented out [Obsolete] attributes Remove the commented-out [Obsolete] attributes as we'll likely delete this class as part of v7 anyway. --- JustSaying/CreateMeABus.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/JustSaying/CreateMeABus.cs b/JustSaying/CreateMeABus.cs index 90d0dffbc..c688d6256 100644 --- a/JustSaying/CreateMeABus.cs +++ b/JustSaying/CreateMeABus.cs @@ -9,11 +9,9 @@ public static class CreateMeABus /// /// Allows to override default globally. /// - ////[Obsolete("Use the BusBuilder class to create message buses.")] public static Func DefaultClientFactory { get; set; } = () => new DefaultAwsClientFactory(); - ////[Obsolete("Use the BusBuilder class to create message buses.")] public static JustSayingFluentlyDependencies WithLogging(ILoggerFactory loggerFactory) => new JustSayingFluentlyDependencies { LoggerFactory = loggerFactory}; } From 06f56a29d7169fd89cae314b6b8a703b9df687b7 Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Mon, 10 Dec 2018 10:04:53 +0000 Subject: [PATCH 42/43] Wait less Wait for 200ms instead of 1 second. --- .../Fluent/MessagingBusBuilderTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs index 24a6db0ae..0d1979e99 100644 --- a/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs +++ b/JustSaying.IntegrationTests/Fluent/MessagingBusBuilderTests.cs @@ -59,7 +59,7 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Queue() // Assert while (!source.IsCancellationRequested && !QueueHandler.MessageIds.Contains(message.Id)) { - await Task.Delay(TimeSpan.FromSeconds(1), source.Token); + await Task.Delay(TimeSpan.FromSeconds(0.2), source.Token); } QueueHandler.MessageIds.ShouldContain(message.Id); @@ -99,7 +99,7 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Topic() // Assert while (!source.IsCancellationRequested && !TopicHandler.MessageIds.Contains(message.Id)) { - await Task.Delay(TimeSpan.FromSeconds(1), source.Token); + await Task.Delay(TimeSpan.FromSeconds(0.2), source.Token); } TopicHandler.MessageIds.ShouldContain(message.Id); @@ -158,7 +158,7 @@ public async Task Can_Create_Messaging_Bus_With_Contributors() // Assert while (!source.IsCancellationRequested && !QueueHandler.MessageIds.Contains(message.Id)) { - await Task.Delay(TimeSpan.FromSeconds(1), source.Token); + await Task.Delay(TimeSpan.FromSeconds(0.2), source.Token); } QueueHandler.MessageIds.ShouldContain(message.Id); From 1b61c41c69aa58c89e1c638c21f07e245550032d Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Thu, 13 Dec 2018 10:14:32 +0000 Subject: [PATCH 43/43] Use var instead of explicit type Use var instead of the explicit type for the handler resolver. --- JustSaying/Fluent/SubscriptionsBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JustSaying/Fluent/SubscriptionsBuilder.cs b/JustSaying/Fluent/SubscriptionsBuilder.cs index 8fa0c750b..d3f55fe02 100644 --- a/JustSaying/Fluent/SubscriptionsBuilder.cs +++ b/JustSaying/Fluent/SubscriptionsBuilder.cs @@ -118,7 +118,7 @@ public SubscriptionsBuilder ForTopic(Action> conf /// internal void Configure(JustSayingFluently bus) { - IHandlerResolver resolver = Parent.ServiceResolver.ResolveService(); + var resolver = Parent.ServiceResolver.ResolveService(); if (resolver == null) {