├── Testing
├── Core
│ ├── GlobalUsings.cs
│ ├── AssemblyInfo.cs
│ ├── Messages
│ │ ├── BasicResponseMessage.cs
│ │ ├── NoChannelMessage.cs
│ │ ├── BasicMessage.cs
│ │ ├── CustomEncoderMessage.cs
│ │ ├── CustomEncryptorMessage.cs
│ │ ├── TimeoutMessage.cs
│ │ ├── CustomEncoderWithInjectionMessage.cs
│ │ ├── CustomEncryptorWithInjectionMessage.cs
│ │ ├── NamedAndVersionedMessage.cs
│ │ └── BasicQueryMessage.cs
│ ├── ServiceInjection
│ │ ├── InjectedService.cs
│ │ └── IInjectableService.cs
│ ├── ConnectionTests
│ │ ├── Middlewares
│ │ │ ├── InvalidMiddleware.cs
│ │ │ ├── InjectedChannelChangeMiddleware.cs
│ │ │ ├── ChannelChangeMiddleware.cs
│ │ │ └── ChannelChangeMiddlewareForBasicMessage.cs
│ │ └── SingleService
│ │ │ └── PingTests.cs
│ ├── Converters
│ │ ├── NoChannelMessageToBasicMessage.cs
│ │ └── BasicMessageToNameAndVersionMessage.cs
│ ├── MoqExtensions.cs
│ ├── Consumers
│ │ ├── BasicQueryConsumer.cs
│ │ ├── BasicMessageConsumer.cs
│ │ ├── BasicMessageConsumerIgnoringMessageType.cs
│ │ ├── BasicQueryAsyncConsumer.cs
│ │ └── BasicMessageAsyncConsumer.cs
│ ├── Encoders
│ │ ├── TestMessageEncoder.cs
│ │ └── TestMessageEncoderWithInjection.cs
│ ├── Encryptors
│ │ ├── TestMessageEncryptor.cs
│ │ └── TestMessageEncryptorWithInjection.cs
│ ├── Constants.cs
│ ├── CoreTesting.csproj
│ └── Helper.cs
└── CQRSTesting
│ ├── MSTestSettings.cs
│ ├── Messages
│ ├── BasicQueryResponse.cs
│ ├── BasicCommandResponse.cs
│ ├── BasicQuery.cs
│ ├── BasicCommand.cs
│ └── BasicResponseCommand.cs
│ ├── Constants.cs
│ ├── CQRSTesting.csproj
│ └── ICQRSBaseTests.cs
├── images
├── performance.jpg
└── open_telemetry.png
├── CQRS
├── CancellationRequest.cs
├── Interfaces
│ ├── Query
│ │ ├── IQuery.cs
│ │ ├── IQueryInvocationContext.cs
│ │ ├── IFilteredQueryProcessor.cs
│ │ └── IQueryProcessor.cs
│ ├── IProcessor.cs
│ ├── Command
│ │ ├── ICommand.cs
│ │ ├── ICommandInvocationContext.cs
│ │ ├── IFilteredCommandProcessor.cs
│ │ └── ICommandProcessor.cs
│ ├── IContextFilteredProcessor.cs
│ └── IProcessorRegistrar.cs
├── CQRS.csproj
├── Contexts
│ ├── QueryInvocationContext.cs
│ └── CommandInvocationContext.cs
├── Attributes
│ ├── CommandAttribute.cs
│ └── QueryAttribute.cs
├── Consumers
│ ├── CommandConsumer.cs
│ ├── QueryResponseConsumer.cs
│ ├── CommandResponseConsumer.cs
│ └── CancellationRequestConsumer.cs
├── Extensions
│ └── IContractConnectionExtension.cs
├── Registrars
│ ├── MappedConnectionRegistrar.cs
│ └── ContractedConnectionRegistrar.cs
└── Exceptions.cs
├── BenchMark
├── Constants.cs
├── Messages
│ ├── Announcement.cs
│ ├── EncodedAnnouncement.cs
│ └── AnnouncementCommand.cs
├── Encoders
│ ├── MyJsonContext.cs
│ └── EncodedAnnouncementEncoder.cs
├── BenchMark.csproj
├── InMemoryBenchmarks
│ ├── AnnouncementConsumer.cs
│ ├── AnnouncementCommandProcessor.cs
│ └── SubscribingInMemory.cs
├── Program.cs
└── PublishBenchmarks
│ └── FakePublishConnection.cs
├── Samples
├── InMemorySample
│ ├── Program.cs
│ └── InMemorySample.csproj
├── Messages
│ ├── ArrivalAnnouncement.cs
│ ├── StoredArrivalAnnouncement.cs
│ ├── Greeting.cs
│ ├── Messages.csproj
│ └── AnnouncementConsumer.cs
├── ActiveMQSample
│ ├── Program.cs
│ └── ActiveMQSample.csproj
├── RedisSample
│ ├── Program.cs
│ └── RedisSample.csproj
├── AzureServiceBusSample
│ ├── Program.cs
│ └── AzureServiceBusSample.csproj
├── KubeMQSample
│ ├── Program.cs
│ └── KubeMQSample.csproj
├── ZeroMQ
│ ├── Program.cs
│ └── ZeroMQ.csproj
├── HiveMQSample
│ ├── Program.cs
│ └── HiveMQSample.csproj
├── ApachePulsarSample
│ ├── Program.cs
│ └── ApachePulsarSample.csproj
├── AmazonSNQSSample
│ └── AmazonSNQSSample.csproj
├── KafkaSample
│ ├── KafkaSample.csproj
│ └── Program.cs
├── GooglePubSubSample
│ ├── GooglePubSubSample.csproj
│ └── Program.cs
├── RabbitMQSample
│ ├── RabbitMQSample.csproj
│ └── Program.cs
└── NATSSample
│ ├── NATSSample.csproj
│ └── Program.cs
├── Core
├── Interfaces
│ ├── Factories
│ │ ├── IMessageTypeFactory.cs
│ │ └── IMessageFactory.cs
│ └── Conversion
│ │ └── IConversionPath.cs
├── Middleware
│ ├── Metrics
│ │ ├── MetricEntryValue.cs
│ │ └── MessageMetric.cs
│ ├── MiddlewareInjectionOrderAttribute.cs
│ ├── ChannelMappingMiddleware.cs
│ └── Context.cs
├── Connections
│ ├── Records.cs
│ └── DecodeServiceMessageResult.cs
├── Messages
│ ├── RecievedMessage.cs
│ └── ErrorServiceMessage.cs
├── Defaults
│ ├── HalfEncoder.cs
│ ├── IntEncoder.cs
│ ├── BooleanEncoder.cs
│ ├── UIntEncoder.cs
│ ├── FloatEncoder.cs
│ ├── LongEncoder.cs
│ ├── ShortEncoder.cs
│ ├── ULongEncoder.cs
│ ├── DoubleEncoder.cs
│ ├── UShortEncoder.cs
│ ├── ByteEncoder.cs
│ ├── BitConverterHelper.cs
│ ├── ByteArrayEncoder.cs
│ ├── CharEncoder.cs
│ ├── JsonEncoder.cs
│ ├── StringEncoder.cs
│ └── DecimalEncoder.cs
├── EnumerableExtensions.cs
├── Core.csproj
├── Constants.cs
├── Factories
│ └── AConverter.cs
└── Subscriptions
│ └── SubscriptionCollection.cs
├── Connectors
├── NATS
│ ├── Options
│ │ └── SubscriptionConsumerConfig.cs
│ ├── Subscriptions
│ │ ├── IInternalServiceSubscription.cs
│ │ ├── PublishSubscription.cs
│ │ ├── QuerySubscription.cs
│ │ └── StreamSubscription.cs
│ ├── NATS.csproj
│ └── Exceptions.cs
├── KubeMQ
│ ├── Options
│ │ └── StoredChannelOptions.cs
│ ├── Messages
│ │ └── PingResponse.cs
│ ├── KubeMQ.csproj
│ ├── Utility.cs
│ ├── Interfaces
│ │ └── IKubeMQPingResult.cs
│ ├── Exceptions.cs
│ └── Subscriptions
│ │ └── PubSubscription.cs
├── InMemory
│ ├── Exceptions.cs
│ ├── InternalServiceMessage.cs
│ ├── InMemory.csproj
│ ├── Subscription.cs
│ ├── Readme.md
│ └── MessageGroup.cs
├── HiveMQ
│ ├── Exceptions.cs
│ └── HiveMQ.csproj
├── AzureServiceBus
│ ├── Exceptions.cs
│ └── AzureServiceBus.csproj
├── ZeroMQ
│ ├── ZeroMQ.csproj
│ └── Exceptions.cs
├── Redis
│ ├── Redis.csproj
│ └── Subscriptions
│ │ ├── PubSubscription.cs
│ │ └── QueryResponseSubscription.cs
├── ActiveMQ
│ ├── ActiveMQ.csproj
│ ├── ConsumerInstance.cs
│ ├── Subscriptions
│ │ └── SubscriptionBase.cs
│ └── Readme.md
├── ApachePulsar
│ └── ApachePulsar.csproj
├── GooglePubSub
│ ├── GooglePubSub.csproj
│ └── Subscription.cs
├── AmazonSNQS
│ └── AmazonSNQS.csproj
├── RabbitMQ
│ ├── RabbitMQ.csproj
│ └── Subscription.cs
└── Kafka
│ ├── Kafka.csproj
│ └── Exceptions.cs
├── Abstractions
├── Interfaces
│ ├── Middleware
│ │ ├── IMiddleware.cs
│ │ ├── ISpecificTypeMiddleware.cs
│ │ ├── IContext.cs
│ │ ├── IBeforeEncodeSpecificTypeMiddleware.cs
│ │ ├── IBeforeEncodeMiddleware.cs
│ │ ├── IAfterEncodeMiddleware.cs
│ │ ├── IBeforeDecodeMiddleware.cs
│ │ ├── IAfterDecodeSpecificTypeMiddleware.cs
│ │ ├── IAfterDecodeMiddleware.cs
│ │ └── Records.cs
│ ├── IContractedConnection.cs
│ ├── Encrypting
│ │ ├── Records.cs
│ │ ├── IMessageTypeEncryptor.cs
│ │ └── IMessageEncryptor.cs
│ ├── IMappedContractConnection.cs
│ ├── Service
│ │ ├── IServiceSubscription.cs
│ │ ├── IPingableMessageServiceConnection.cs
│ │ ├── IBulkPublishableMessageServiceConnection.cs
│ │ ├── IQueryResponseMessageServiceConnection.cs
│ │ ├── IQueryableMessageServiceConnection.cs
│ │ └── IInboxQueryableMessageServiceConnection.cs
│ ├── Consumers
│ │ ├── IBaseConsumer.cs
│ │ ├── IHeaderFilteredConsumer.cs
│ │ ├── IPubSubConsumer.cs
│ │ ├── IMessageFilteredConsumer.cs
│ │ ├── IPubSubAsyncConsumer.cs
│ │ ├── IQueryResponseConsumer.cs
│ │ └── IQueryResponseAsyncConsumer.cs
│ ├── ISubscription.cs
│ ├── Messages
│ │ └── IEncodedMessage.cs
│ ├── Conversion
│ │ └── IMessageConverter.cs
│ ├── Encoding
│ │ ├── IMessageTypeEncoder.cs
│ │ └── IMessageEncoder.cs
│ ├── IRecievedMessage.cs
│ └── IContractMetric.cs
├── Abstractions.csproj
├── Messages
│ ├── PingResult.cs
│ ├── QueryResponseMessage.cs
│ ├── TransmissionResult.cs
│ ├── ServiceQueryResult.cs
│ ├── QueryResult.cs
│ ├── ServiceMessage.cs
│ ├── MessageFilters.cs
│ ├── MultiTransmissionResult.cs
│ ├── RecievedInboxServiceMessage.cs
│ └── RecievedServiceMessage.cs
├── Enums.cs
└── Attributes
│ ├── MessageAttribute.cs
│ ├── ConsumerAttribute.cs
│ └── QueryMessageAttribute.cs
├── OpenTelemetry.md
├── Resiliency.md
├── LICENSE
├── .github
└── workflows
│ ├── unit-test-report.yml
│ └── unittests8x.yml
├── Clean-BuildFolders.ps1
└── Shared.props
/Testing/Core/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using MQContract.Messages;
2 |
--------------------------------------------------------------------------------
/Testing/CQRSTesting/MSTestSettings.cs:
--------------------------------------------------------------------------------
1 | [assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
2 |
--------------------------------------------------------------------------------
/Testing/Core/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: Parallelize(Workers = 25, Scope = ExecutionScope.ClassLevel)]
--------------------------------------------------------------------------------
/images/performance.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/roger-castaldo/MQContract/HEAD/images/performance.jpg
--------------------------------------------------------------------------------
/images/open_telemetry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/roger-castaldo/MQContract/HEAD/images/open_telemetry.png
--------------------------------------------------------------------------------
/CQRS/CancellationRequest.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.CQRS
2 | {
3 | internal record CancellationRequest(Guid CorrelationId,Guid MessageId)
4 | {}
5 | }
6 |
--------------------------------------------------------------------------------
/Testing/Core/Messages/BasicResponseMessage.cs:
--------------------------------------------------------------------------------
1 | namespace AutomatedTesting.Messages
2 | {
3 | public record BasicResponseMessage(string TestName) { }
4 | }
5 |
--------------------------------------------------------------------------------
/BenchMark/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace BenchMark
2 | {
3 | internal static class Constants
4 | {
5 | public const int PublishCount = 200;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Testing/CQRSTesting/Messages/BasicQueryResponse.cs:
--------------------------------------------------------------------------------
1 | namespace CQRSTesting.Messages
2 | {
3 | public record BasicQueryResponse(string Name)
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Testing/Core/Messages/NoChannelMessage.cs:
--------------------------------------------------------------------------------
1 | namespace AutomatedTesting.Messages
2 | {
3 | public record NoChannelMessage(string TestName)
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Testing/CQRSTesting/Messages/BasicCommandResponse.cs:
--------------------------------------------------------------------------------
1 | namespace CQRSTesting.Messages
2 | {
3 | public record BasicCommandResponse(string Name)
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Testing/Core/ServiceInjection/InjectedService.cs:
--------------------------------------------------------------------------------
1 | namespace AutomatedTesting.ServiceInjection
2 | {
3 | internal record InjectedService(string Name) : IInjectableService
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Samples/InMemorySample/Program.cs:
--------------------------------------------------------------------------------
1 | using Messages;
2 | using MQContract.InMemory;
3 |
4 | var serviceConnection = new Connection();
5 |
6 | await SampleExecution.ExecuteSample(serviceConnection, "InMemory");
--------------------------------------------------------------------------------
/Testing/Core/ServiceInjection/IInjectableService.cs:
--------------------------------------------------------------------------------
1 | namespace AutomatedTesting.ServiceInjection
2 | {
3 | internal interface IInjectableService
4 | {
5 | string Name { get; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Core/Interfaces/Factories/IMessageTypeFactory.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Factories
2 | {
3 | internal interface IMessageTypeFactory
4 | {
5 | bool IgnoreMessageHeader { get; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Testing/Core/Messages/BasicMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace AutomatedTesting.Messages
4 | {
5 | [Message(channel: "BasicMessage")]
6 | public record BasicMessage(string Name);
7 | }
8 |
--------------------------------------------------------------------------------
/Core/Middleware/Metrics/MetricEntryValue.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Middleware.Metrics
2 | {
3 | internal record MetricEntryValue(Type Type, string? Channel, bool Sent, int MessageSize, TimeSpan Duration)
4 | { }
5 | }
6 |
--------------------------------------------------------------------------------
/Samples/Messages/ArrivalAnnouncement.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace Messages
4 | {
5 | [Message(channel: "Arrivals")]
6 | public record ArrivalAnnouncement(string FirstName, string LastName) { }
7 | }
8 |
--------------------------------------------------------------------------------
/CQRS/Interfaces/Query/IQuery.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.CQRS.Interfaces.Query
2 | {
3 | ///
4 | /// Used to identify a query type
5 | ///
6 | public interface IQuery
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Testing/Core/Messages/CustomEncoderMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace AutomatedTesting.Messages
4 | {
5 | [Message(channel: "CustomEncoder")]
6 | public record CustomEncoderMessage(string TestName) { }
7 | }
8 |
--------------------------------------------------------------------------------
/BenchMark/Messages/Announcement.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace BenchMark.Messages
4 | {
5 | [Message(typeName:"Announcement", typeVersion:"1.0.0")]
6 | public record Announcement(string Message)
7 | { }
8 | }
9 |
--------------------------------------------------------------------------------
/Samples/Messages/StoredArrivalAnnouncement.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace Messages
4 | {
5 | [Message(channel: "StoredArrivals")]
6 | public record StoredArrivalAnnouncement(string FirstName, string LastName) { }
7 | }
8 |
--------------------------------------------------------------------------------
/Testing/Core/ConnectionTests/Middlewares/InvalidMiddleware.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Middleware;
2 |
3 | namespace AutomatedTesting.ConnectionTests.Middlewares
4 | {
5 | internal class InvalidMiddleware : IMiddleware
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Testing/Core/Messages/CustomEncryptorMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace AutomatedTesting.Messages
4 | {
5 | [Message(channel: "CustomEncryptorMessage")]
6 | public record CustomEncryptorMessage(string TestName) { }
7 | }
8 |
--------------------------------------------------------------------------------
/Testing/Core/Messages/TimeoutMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace AutomatedTesting.Messages
4 | {
5 | [QueryMessage(channel:"Timeout",responseTimeoutMilliseconds:500)]
6 | public record TimeoutMessage(string Name) { }
7 | }
8 |
--------------------------------------------------------------------------------
/Connectors/NATS/Options/SubscriptionConsumerConfig.cs:
--------------------------------------------------------------------------------
1 | using NATS.Client.JetStream.Models;
2 |
3 | namespace MQContract.NATS.Options
4 | {
5 | internal record SubscriptionConsumerConfig(string Channel, ConsumerConfig Configuration)
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Samples/ActiveMQSample/Program.cs:
--------------------------------------------------------------------------------
1 | using Messages;
2 | using MQContract.ActiveMQ;
3 |
4 | var serviceConnection = new Connection(new Uri("amqp:tcp://localhost:5672"), "artemis", "artemis");
5 |
6 | await SampleExecution.ExecuteSample(serviceConnection, "ActiveMQ");
7 |
--------------------------------------------------------------------------------
/Testing/Core/Messages/CustomEncoderWithInjectionMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace AutomatedTesting.Messages
4 | {
5 | [Message(channel: "CustomEncoderWithInjection")]
6 | public record CustomEncoderWithInjectionMessage(string TestName) { }
7 | }
8 |
--------------------------------------------------------------------------------
/BenchMark/Encoders/MyJsonContext.cs:
--------------------------------------------------------------------------------
1 | using BenchMark.Messages;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace BenchMark.Encoders
5 | {
6 | [JsonSerializable(typeof(EncodedAnnouncement))]
7 | public partial class MyJsonContext : JsonSerializerContext { }
8 | }
9 |
--------------------------------------------------------------------------------
/Testing/Core/Messages/CustomEncryptorWithInjectionMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace AutomatedTesting.Messages
4 | {
5 | [Message(channel: "CustomEncryptorWithInjection")]
6 | public record CustomEncryptorWithInjectionMessage(string TestName) { }
7 | }
8 |
--------------------------------------------------------------------------------
/Core/Connections/Records.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 | using System.Diagnostics;
3 |
4 | namespace MQContract.Connections
5 | {
6 | internal readonly record struct FilteredServiceMessage(ServiceMessage? ServiceMessage, Activity? Activity, MessageFilterResult FilterResult);
7 | }
8 |
--------------------------------------------------------------------------------
/Testing/CQRSTesting/Messages/BasicQuery.cs:
--------------------------------------------------------------------------------
1 | using MQContract.CQRS.Attributes;
2 | using MQContract.CQRS.Interfaces.Query;
3 |
4 | namespace CQRSTesting.Messages
5 | {
6 | [Query("BasicQuery")]
7 | public record BasicQuery(string Name) : IQuery
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Connectors/NATS/Subscriptions/IInternalServiceSubscription.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Service;
2 |
3 | namespace MQContract.NATS.Subscriptions
4 | {
5 | internal interface IInternalServiceSubscription : IServiceSubscription
6 | {
7 | void Run();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Middleware/IMiddleware.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Middleware
2 | {
3 | ///
4 | /// Base Middleware just used to limit Generic Types for Register Middleware
5 | ///
6 | public interface IMiddleware
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/BenchMark/Messages/EncodedAnnouncement.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace BenchMark.Messages
4 | {
5 | [Message(typeName:"EncodedAnnouncement",typeVersion:"1.0.0")]
6 | public record EncodedAnnouncement(string Message) : Announcement(Message)
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Testing/CQRSTesting/Messages/BasicCommand.cs:
--------------------------------------------------------------------------------
1 | using MQContract.CQRS.Attributes;
2 | using MQContract.CQRS.Interfaces.Command;
3 |
4 | namespace CQRSTesting.Messages
5 | {
6 | [Command("BasicCommand")]
7 | public record BasicCommand(string Name) : ICommand
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Testing/Core/Messages/NamedAndVersionedMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace AutomatedTesting.Messages
4 | {
5 | [Message(channel: "NamedAndVersioned",typeName:"VersionedMessage",typeVersion:"1.0.0.3")]
6 | public record NamedAndVersionedMessage(string TestName) { }
7 | }
8 |
--------------------------------------------------------------------------------
/BenchMark/Messages/AnnouncementCommand.cs:
--------------------------------------------------------------------------------
1 | using MQContract.CQRS.Attributes;
2 | using MQContract.CQRS.Interfaces.Command;
3 |
4 | namespace BenchMark.Messages
5 | {
6 | [Command(channel:"Announcements")]
7 | public record AnnouncementCommand(string Message) : ICommand
8 | { }
9 | }
10 |
--------------------------------------------------------------------------------
/Connectors/KubeMQ/Options/StoredChannelOptions.cs:
--------------------------------------------------------------------------------
1 | using static MQContract.KubeMQ.Connection;
2 |
3 | namespace MQContract.KubeMQ.Options
4 | {
5 | internal record StoredChannelOptions(string ChannelName, MessageReadStyle ReadStyle = MessageReadStyle.StartNewOnly, long ReadOffset = 0)
6 | { }
7 | }
8 |
--------------------------------------------------------------------------------
/Samples/Messages/Greeting.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace Messages
4 | {
5 | [QueryMessage(channel: "Greeting",typeName:"Nametag",typeVersion:"1.0.0.0",responseType:typeof(string),responseChannel:"Greeting.Response")]
6 | public record Greeting(string FirstName, string LastName) { }
7 | }
8 |
--------------------------------------------------------------------------------
/Testing/Core/Messages/BasicQueryMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace AutomatedTesting.Messages
4 | {
5 | [QueryMessage(channel: "BasicQueryMessage",responseType:typeof(BasicResponseMessage),responseChannel: "BasicQueryResponse")]
6 | public record BasicQueryMessage(string TypeName) { }
7 | }
8 |
--------------------------------------------------------------------------------
/Samples/RedisSample/Program.cs:
--------------------------------------------------------------------------------
1 | using Messages;
2 | using MQContract.Redis;
3 | using StackExchange.Redis;
4 |
5 | var conf = new ConfigurationOptions();
6 | conf.EndPoints.Add("localhost");
7 |
8 | var serviceConnection = new Connection(conf);
9 |
10 | await SampleExecution.ExecuteSample(serviceConnection, "Redis");
11 |
--------------------------------------------------------------------------------
/Testing/CQRSTesting/Messages/BasicResponseCommand.cs:
--------------------------------------------------------------------------------
1 | using MQContract.CQRS.Attributes;
2 | using MQContract.CQRS.Interfaces.Command;
3 |
4 | namespace CQRSTesting.Messages
5 | {
6 | [Command("BasicResponseCommand")]
7 | public record BasicResponseCommand(string Name) : ICommand
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Samples/AzureServiceBusSample/Program.cs:
--------------------------------------------------------------------------------
1 | using Messages;
2 | using MQContract.AzureServiceBus;
3 |
4 | var serviceConnection = new Connection(new("Endpoint=sb://localhost;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;"));
5 |
6 | await SampleExecution.ExecuteSample(serviceConnection, "AzureServiceBus");
--------------------------------------------------------------------------------
/Core/Messages/RecievedMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces;
2 | using System.Diagnostics;
3 |
4 | namespace MQContract.Messages
5 | {
6 | internal record ReceivedMessage(string ID, TMessage Message, MessageHeader Headers, DateTime ReceivedTimestamp, DateTime ProcessedTimestamp, Activity? Activity)
7 | : IReceivedMessage
8 | { }
9 | }
10 |
--------------------------------------------------------------------------------
/Connectors/InMemory/Exceptions.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.InMemory
2 | {
3 | ///
4 | /// Thrown when a message transmission has failed within the In Memory system
5 | ///
6 | public class TransmissionResultException : Exception
7 | {
8 | internal TransmissionResultException()
9 | : base("Unable to transmit") { }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Connectors/InMemory/InternalServiceMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 |
3 | namespace MQContract.InMemory
4 | {
5 | internal record InternalServiceMessage(string ID, string MessageTypeID, string Channel, MessageHeader Header, ReadOnlyMemory Data, Guid? CorrelationID = null, string? ReplyChannel = null)
6 | : ServiceMessage(ID, MessageTypeID, Channel, Header, Data)
7 | { }
8 | }
9 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/IContractedConnection.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces
2 | {
3 | ///
4 | /// The base representation of a Contract Connection, specifically a single service connection supporting contract connection
5 | ///
6 | public interface IContractedConnection : IContractConnection, IMetricContractConnection
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Connectors/HiveMQ/Exceptions.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.HiveMQ
2 | {
3 | ///
4 | /// Thrown when the service connection is unable to connect to the HiveMQTT server
5 | ///
6 | public class ConnectionFailedException : Exception
7 | {
8 | internal ConnectionFailedException(string? reason)
9 | : base($"Failed to connect: {reason}")
10 | { }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Core/Interfaces/Conversion/IConversionPath.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using MQContract.Interfaces.Messages;
3 |
4 | namespace MQContract.Interfaces.Conversion
5 | {
6 | internal interface IConversionPath
7 | {
8 | bool IsMatch(string metaData);
9 | ValueTask ConvertMessageAsync(ILogger? logger, IEncodedMessage message, Stream? dataStream = null);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Samples/KubeMQSample/Program.cs:
--------------------------------------------------------------------------------
1 | using Messages;
2 | using MQContract.KubeMQ;
3 |
4 | await using var serviceConnection = new Connection(new ConnectionOptions()
5 | {
6 | Logger=new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider().CreateLogger("Messages"),
7 | ClientId="KubeMQSample"
8 | })
9 | .RegisterStoredChannel("StoredArrivals");
10 |
11 | await SampleExecution.ExecuteSample(serviceConnection, "KubeMQ");
--------------------------------------------------------------------------------
/Testing/CQRSTesting/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace CQRSTesting
2 | {
3 | internal class Constants
4 | {
5 | public const string CorrelationIdTag = "mqcontract.cqrs.correlationid";
6 | public const string MessageIdTag = "mqcontract.cqrs.messageid";
7 | public const string CausationIdTag = "mqcontract.cqrs.causationid";
8 | public const string TypeTag = "mqcontract.cqrs.type";
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Connectors/AzureServiceBus/Exceptions.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.AzureServiceBus
2 | {
3 | ///
4 | /// Thrown when a bulk publish request exceeds the usable message batch size
5 | ///
6 | public class BulkTooLargeException : ArgumentException
7 | {
8 | internal BulkTooLargeException()
9 | : base("The bulk messages are too large for a batch.", "messages") { }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Core/Defaults/HalfEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class HalfEncoder : ABitEncoder
4 | {
5 | protected override int ByteSize => 2;
6 |
7 | protected override Half ConvertValue(ReadOnlySpan value)
8 | => BitConverter.ToHalf(value);
9 |
10 | protected override byte[] ConvertValue(Half value)
11 | => BitConverter.GetBytes(value);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Core/Defaults/IntEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class IntEncoder : ABitEncoder
4 | {
5 | protected override int ByteSize => sizeof(int);
6 |
7 | protected override int ConvertValue(ReadOnlySpan value)
8 | => BitConverter.ToInt32(value);
9 |
10 | protected override byte[] ConvertValue(int value)
11 | => BitConverter.GetBytes(value);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Samples/ZeroMQ/Program.cs:
--------------------------------------------------------------------------------
1 | // See https://aka.ms/new-console-template for more information
2 | using Messages;
3 | using MQContract.ZeroMQ;
4 |
5 | var serviceConnection = new Connection();
6 | serviceConnection.BindAsServer("tcp://localhost:8080");
7 | serviceConnection.BindInboxAddress("tcp://localhost:8081");
8 | serviceConnection.ConnectToServer("tcp://localhost:8080");
9 |
10 | await SampleExecution.ExecuteSample(serviceConnection, "ZeroMQ");
--------------------------------------------------------------------------------
/CQRS/CQRS.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract.CQRS
7 | MQContract.CQRS
8 | CQRS wrapper for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Core/Defaults/BooleanEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class BooleanEncoder : ABitEncoder
4 | {
5 | protected override int ByteSize => 1;
6 |
7 | protected override bool ConvertValue(ReadOnlySpan value)
8 | => BitConverter.ToBoolean(value);
9 |
10 | protected override byte[] ConvertValue(bool value)
11 | => BitConverter.GetBytes(value);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Samples/HiveMQSample/Program.cs:
--------------------------------------------------------------------------------
1 | using HiveMQtt.Client.Options;
2 | using Messages;
3 | using MQContract.HiveMQ;
4 |
5 | var serviceConnection = new Connection(new HiveMQClientOptions
6 | {
7 | Host = "127.0.0.1",
8 | Port = 1883,
9 | CleanStart = false, // <--- Set to false to receive messages queued on the broker
10 | ClientId = "HiveMQSample"
11 | });
12 |
13 | await SampleExecution.ExecuteSample(serviceConnection, "HiveMQ");
14 |
--------------------------------------------------------------------------------
/Core/Defaults/UIntEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class UIntEncoder : ABitEncoder
4 | {
5 | protected override int ByteSize => sizeof(uint);
6 |
7 | protected override uint ConvertValue(ReadOnlySpan value)
8 | => BitConverter.ToUInt32(value);
9 |
10 | protected override byte[] ConvertValue(uint value)
11 | => BitConverter.GetBytes(value);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Samples/Messages/Messages.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Core/Defaults/FloatEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class FloatEncoder : ABitEncoder
4 | {
5 | protected override int ByteSize => sizeof(float);
6 |
7 | protected override float ConvertValue(ReadOnlySpan value)
8 | => BitConverter.ToSingle(value);
9 |
10 | protected override byte[] ConvertValue(float value)
11 | => BitConverter.GetBytes(value);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Core/Defaults/LongEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class LongEncoder : ABitEncoder
4 | {
5 | protected override int ByteSize => sizeof(long);
6 |
7 | protected override long ConvertValue(ReadOnlySpan value)
8 | => BitConverter.ToInt64(value);
9 |
10 | protected override byte[] ConvertValue(long value)
11 | => BitConverter.GetBytes(value);
12 |
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Core/Defaults/ShortEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class ShortEncoder : ABitEncoder
4 | {
5 | protected override int ByteSize => sizeof(short);
6 |
7 | protected override short ConvertValue(ReadOnlySpan value)
8 | => BitConverter.ToInt16(value);
9 |
10 | protected override byte[] ConvertValue(short value)
11 | => BitConverter.GetBytes(value);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Core/Defaults/ULongEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class ULongEncoder : ABitEncoder
4 | {
5 | protected override int ByteSize => sizeof(ulong);
6 |
7 | protected override ulong ConvertValue(ReadOnlySpan value)
8 | => BitConverter.ToUInt64(value);
9 |
10 | protected override byte[] ConvertValue(ulong value)
11 | => BitConverter.GetBytes(value);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Core/Defaults/DoubleEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class DoubleEncoder : ABitEncoder
4 | {
5 | protected override int ByteSize => sizeof(double);
6 |
7 | protected override double ConvertValue(ReadOnlySpan value)
8 | => BitConverter.ToDouble(value);
9 |
10 | protected override byte[] ConvertValue(double value)
11 | => BitConverter.GetBytes(value);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Core/Defaults/UShortEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class UShortEncoder : ABitEncoder
4 | {
5 | protected override int ByteSize => sizeof(ushort);
6 |
7 | protected override ushort ConvertValue(ReadOnlySpan value)
8 | => BitConverter.ToUInt16(value);
9 |
10 | protected override byte[] ConvertValue(ushort value)
11 | => BitConverter.GetBytes(value);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Encrypting/Records.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Encrypting
2 | {
3 | ///
4 | /// Houses the returned results from a message encryption call
5 | ///
6 | /// Any additional headers to add to the message
7 | /// The resulting encrypted data
8 | public readonly record struct EncryptionResult(Dictionary? Headers, byte[] Data);
9 | }
10 |
--------------------------------------------------------------------------------
/Testing/Core/Converters/NoChannelMessageToBasicMessage.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using MQContract.Interfaces.Conversion;
3 |
4 | namespace AutomatedTesting.Converters
5 | {
6 | internal class NoChannelMessageToBasicMessage : IMessageConverter
7 | {
8 | public ValueTask ConvertAsync(NoChannelMessage source)
9 | => ValueTask.FromResult(new(source.TestName));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/IMappedContractConnection.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces
2 | {
3 | ///
4 | /// The representation of a Mapped Contract Connection which is built to use 1 or more service connections for the calls
5 | ///
6 | public interface IMappedContractConnection
7 | : IContractConnection, IMetricContractConnection, IMappableContractConnection
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Core/Interfaces/Factories/IMessageFactory.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Conversion;
2 | using MQContract.Messages;
3 |
4 | namespace MQContract.Interfaces.Factories
5 | {
6 | internal interface IMessageFactory : IMessageTypeFactory, IConversionPath
7 | {
8 | string? MessageChannel { get; }
9 | ValueTask ConvertMessageAsync(TMessage message, bool ignoreChannel, string? channel, MessageHeader messageHeader);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Middleware/ISpecificTypeMiddleware.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Middleware
2 | {
3 | ///
4 | /// Base Specific Type Middleware just used to limit Generic Types for Register Middleware
5 | ///
6 | #pragma warning disable S2326 // Unused type parameters should be removed
7 | public interface ISpecificTypeMiddleware
8 | #pragma warning restore S2326 // Unused type parameters should be removed
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Core/Defaults/ByteEncoder.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Encoding;
2 |
3 | namespace MQContract.Defaults
4 | {
5 | internal class ByteEncoder : IMessageTypeEncoder
6 | {
7 | ValueTask IMessageTypeEncoder.DecodeAsync(Stream stream)
8 | => ValueTask.FromResult((byte)stream.ReadByte());
9 |
10 | ValueTask IMessageTypeEncoder.EncodeAsync(byte message)
11 | => ValueTask.FromResult([message]);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Core/Defaults/BitConverterHelper.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal static class BitConverterHelper
4 | {
5 | public static async ValueTask StreamToByteArray(Stream stream)
6 | {
7 | using var bufferedStream = new BufferedStream(stream);
8 | using var memoryStream = new MemoryStream();
9 | await bufferedStream.CopyToAsync(memoryStream);
10 | return memoryStream.ToArray();
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/CQRS/Interfaces/IProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.CQRS.Interfaces
2 | {
3 | ///
4 | /// The base interface housing common calls for a Processor
5 | ///
6 | public interface IProcessor
7 | {
8 | ///
9 | /// Called when an error is supplied from the underlying Contract Connection
10 | ///
11 | /// The error that occured
12 | void ErrorRecieved(Exception error);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Testing/Core/Converters/BasicMessageToNameAndVersionMessage.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using MQContract.Interfaces.Conversion;
3 |
4 | namespace AutomatedTesting.Converters
5 | {
6 | internal class BasicMessageToNameAndVersionMessage : IMessageConverter
7 | {
8 | public ValueTask ConvertAsync(BasicMessage source)
9 | => ValueTask.FromResult(new(source.Name));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Service/IServiceSubscription.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Service
2 | {
3 | ///
4 | /// Represents an underlying service level subscription
5 | ///
6 | public interface IServiceSubscription
7 | {
8 | ///
9 | /// Called to end the subscription
10 | ///
11 | /// A task to allow for asynchronous ending of the subscription
12 | ValueTask EndAsync();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Core/Defaults/ByteArrayEncoder.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Encoding;
2 |
3 | namespace MQContract.Defaults
4 | {
5 | internal class ByteArrayEncoder : IMessageTypeEncoder
6 | {
7 | async ValueTask IMessageTypeEncoder.DecodeAsync(Stream stream)
8 | => await BitConverterHelper.StreamToByteArray(stream);
9 |
10 | ValueTask IMessageTypeEncoder.EncodeAsync(byte[] message)
11 | => ValueTask.FromResult(message);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Abstractions/Abstractions.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract.$(MSBuildProjectName)
7 | MQContract
8 | Abstractions for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/CQRS/Contexts/QueryInvocationContext.cs:
--------------------------------------------------------------------------------
1 | using MQContract.CQRS.Interfaces.Query;
2 | using MQContract.Interfaces;
3 |
4 | namespace MQContract.CQRS.Contexts
5 | {
6 | internal sealed class QueryInvocationContext(IReceivedMessage receivedMessage, CqrsConnection connection)
7 | : AInvocationContext(receivedMessage, connection), IQueryInvocationContext
8 | where TQuery : IQuery
9 | {
10 | TQuery IQueryInvocationContext.Query => Message;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/CQRS/Interfaces/Command/ICommand.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.CQRS.Interfaces.Command
2 | {
3 | ///
4 | /// Used to identify a command type
5 | ///
6 | public interface ICommand { }
7 |
8 | ///
9 | /// Used to identify a command type that is expected to provide a response
10 | ///
11 | /// The type of response expected from this command
12 | public interface ICommand : ICommand { }
13 | }
14 |
--------------------------------------------------------------------------------
/Core/Defaults/CharEncoder.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Encoding;
2 |
3 | namespace MQContract.Defaults
4 | {
5 | internal class CharEncoder : IMessageTypeEncoder
6 | {
7 | async ValueTask IMessageTypeEncoder.DecodeAsync(Stream stream)
8 | => BitConverter.ToChar(await BitConverterHelper.StreamToByteArray(stream));
9 |
10 | ValueTask IMessageTypeEncoder.EncodeAsync(char message)
11 | => ValueTask.FromResult(BitConverter.GetBytes(message));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Samples/ZeroMQ/ZeroMQ.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CQRS/Contexts/CommandInvocationContext.cs:
--------------------------------------------------------------------------------
1 | using MQContract.CQRS.Interfaces.Command;
2 | using MQContract.Interfaces;
3 |
4 | namespace MQContract.CQRS.Contexts
5 | {
6 | internal sealed class CommandInvocationContext(IReceivedMessage receivedMessage, CqrsConnection connection)
7 | : AInvocationContext(receivedMessage, connection), ICommandInvocationContext
8 | where TCommand : ICommand
9 | {
10 | TCommand ICommandInvocationContext.Command => Message;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Samples/ApachePulsarSample/Program.cs:
--------------------------------------------------------------------------------
1 | using DotPulsar;
2 | using Messages;
3 | using MQContract.ApachePulsar;
4 |
5 | #pragma warning disable S1075 // URIs should not be hardcoded
6 | //This is a sample program with a localhost connection so this is necessary
7 | var builder = PulsarClient.Builder()
8 | .ServiceUrl(new("pulsar://localhost:6650"));
9 | #pragma warning restore S1075 // URIs should not be hardcoded
10 |
11 | var serviceConnection = new Connection(builder!);
12 |
13 | await SampleExecution.ExecuteSample(serviceConnection, "ApachePulsar");
14 |
--------------------------------------------------------------------------------
/Samples/InMemorySample/InMemorySample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Abstractions/Messages/PingResult.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Messages
2 | {
3 | ///
4 | /// Houses the results from a Ping call against a given underlying service
5 | ///
6 | /// The host name of the service, if provided
7 | /// The version of the service running, if provided
8 | /// How long it took for the server to respond
9 | public record PingResult(string Host, string Version, TimeSpan ResponseTime)
10 | { }
11 | }
12 |
--------------------------------------------------------------------------------
/Samples/AmazonSNQSSample/AmazonSNQSSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Samples/KafkaSample/KafkaSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Samples/HiveMQSample/HiveMQSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CQRS/Interfaces/IContextFilteredProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.CQRS.Interfaces
2 | {
3 | ///
4 | /// Used to define a processor that will filter incoming calls based on the context
5 | ///
6 | public interface IContextFilteredProcessor : IProcessor
7 | {
8 | ///
9 | /// The filter callback that will be supplied a context instance and will return a
10 | /// filter type response
11 | ///
12 | Func> Filter { get; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Samples/ActiveMQSample/ActiveMQSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Samples/ApachePulsarSample/ApachePulsarSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Samples/GooglePubSubSample/GooglePubSubSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Connectors/ZeroMQ/ZeroMQ.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract.$(MSBuildProjectName)
7 | MQContract.$(MSBuildProjectName)
8 | ZeroMQ Connector for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/CQRS/Interfaces/Query/IQueryInvocationContext.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.CQRS.Interfaces.Query
2 | {
3 | ///
4 | /// Represents a given execution context for a query
5 | ///
6 | /// The type of query housed within this context
7 | public interface IQueryInvocationContext : IInvocationContext
8 | where TQuery : IQuery
9 | {
10 | ///
11 | /// The query for this invocation context instance
12 | ///
13 | TQuery Query { get; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Samples/AzureServiceBusSample/AzureServiceBusSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Consumers/IBaseConsumer.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Consumers
2 | {
3 | ///
4 | /// Represents the Base for all Consumer interfaces and contains the common method definition
5 | ///
6 | public interface IBaseConsumer
7 | {
8 | ///
9 | /// Called when an error is received from within the underlying subscription that is using this Consumer
10 | ///
11 | /// The error that occured
12 | void ErrorRecieved(Exception error);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/ISubscription.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces
2 | {
3 | ///
4 | /// This interface represents a Contract Connection Subscription and is used to house and end the subscription
5 | ///
6 | public interface ISubscription : IDisposable, IAsyncDisposable
7 | {
8 | ///
9 | /// Called to end (close off) the subscription
10 | ///
11 | /// A task that is ending the subscription and closing off the resources for it
12 | ValueTask EndAsync();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Abstractions/Messages/QueryResponseMessage.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Messages
2 | {
3 | ///
4 | /// Houses the Query Response Message to be sent back from a query call
5 | ///
6 | /// The type of message contained in the response
7 | /// The message to respond back with
8 | /// The headers to attach to the response
9 | public record QueryResponseMessage(TQueryResponse Message, Dictionary? Headers = null);
10 | }
11 |
--------------------------------------------------------------------------------
/Connectors/Redis/Redis.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract.$(MSBuildProjectName)
7 | MQContract.$(MSBuildProjectName)
8 | Redis Connector for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Connectors/NATS/NATS.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract.$(MSBuildProjectName)
7 | MQContract.$(MSBuildProjectName)
8 | NATS.io Connector for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Connectors/ActiveMQ/ActiveMQ.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MQContract.$(MSBuildProjectName)
6 | MQContract.$(MSBuildProjectName)
7 | ActiveMQ Connector for MQContract
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Connectors/ApachePulsar/ApachePulsar.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract.$(MSBuildProjectName)
7 | MQContract.$(MSBuildProjectName)
8 | Apache Pulsar Connector for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/CQRS/Interfaces/Command/ICommandInvocationContext.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.CQRS.Interfaces.Command
2 | {
3 | ///
4 | /// Represents a given execution context for a command
5 | ///
6 | /// The type of command housed within this context
7 | public interface ICommandInvocationContext : IInvocationContext
8 | where TCommand : ICommand
9 | {
10 | ///
11 | /// The command for this invocation context instance
12 | ///
13 | TCommand Command { get; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Connectors/GooglePubSub/GooglePubSub.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MQContract.$(MSBuildProjectName)
6 | MQContract.$(MSBuildProjectName)
7 | GooglePubSub Connector for MQContract
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Service/IPingableMessageServiceConnection.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 |
3 | namespace MQContract.Interfaces.Service
4 | {
5 | ///
6 | /// Extends the base MessageServiceConnection Interface to support service pinging
7 | ///
8 | public interface IPingableMessageServiceConnection : IMessageServiceConnection
9 | {
10 | ///
11 | /// Implemented Ping call if avaialble for the underlying service
12 | ///
13 | /// A Ping Result
14 | ValueTask PingAsync();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Consumers/IHeaderFilteredConsumer.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 |
3 | namespace MQContract.Interfaces.Consumers
4 | {
5 | ///
6 | /// Used to define a consumer that will filter out given messages using a header filter
7 | ///
8 | public interface IHeaderFilteredConsumer : IBaseConsumer
9 | {
10 | ///
11 | /// The filter callback to be invoked that will be supplied the current headers and expect back a filter type
12 | ///
13 | Func> Filter { get; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Connectors/InMemory/InMemory.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract.$(MSBuildProjectName)
7 | MQContract.$(MSBuildProjectName)
8 | In Memory Connector for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/BenchMark/BenchMark.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Connectors/AzureServiceBus/AzureServiceBus.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MQContract.$(MSBuildProjectName)
6 | MQContract.$(MSBuildProjectName)
7 | AzureServiceBus Connector for MQContract
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Testing/Core/MoqExtensions.cs:
--------------------------------------------------------------------------------
1 | using Moq.Language.Flow;
2 |
3 | namespace AutomatedTesting
4 | {
5 | public static class MoqExtensions
6 | {
7 | public static IReturnsResult ReturnsInOrder(
8 | this ISetup setup, params TResult[] results) where T : class
9 | {
10 | var queue = new Queue(results);
11 | #pragma warning disable CS8603 // Possible null reference return.
12 | return setup.Returns(() => queue.Count > 0 ? queue.Dequeue() : default);
13 | #pragma warning restore CS8603 // Possible null reference return.
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Abstractions/Messages/TransmissionResult.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Messages
2 | {
3 | ///
4 | /// Houses the result of a transmission into the system
5 | ///
6 | /// The unique ID of the message that was transmitted
7 | /// An error message if an error occured
8 | public record TransmissionResult(string ID, ErrorMessage? Error = null)
9 | {
10 | ///
11 | /// Flag to indicate if the result is an error
12 | ///
13 | public bool IsError => !string.IsNullOrWhiteSpace(Error?.Message);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Testing/Core/Consumers/BasicQueryConsumer.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using MQContract.Interfaces;
3 | using MQContract.Interfaces.Consumers;
4 |
5 | namespace AutomatedTesting.Consumers
6 | {
7 | internal class BasicQueryConsumer : IQueryResponseConsumer
8 | {
9 | void IBaseConsumer.ErrorRecieved(Exception error)
10 | { }
11 |
12 | QueryResponseMessage IQueryResponseConsumer.MessageReceived(IReceivedMessage message)
13 | => new(new(message.Message.TypeName));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/OpenTelemetry.md:
--------------------------------------------------------------------------------
1 | #OpenTelemetry
2 |
3 | To enable Open Telemetry support, simply call the EnableOpenTelemetry method:
4 | EnableOpenTelemetry(string activitySource = "MQContract", bool linkActivitiesAcrossSystems = true)
5 | Here you are able specify a custom activitySource is there is a desire, as well as indicate if the activities are to be linked across services. If this is enabled the system will pass across specific information within the service messages to tie the activities together. Below is an example screenshot from running a Query Response against a NATS service including linking the activities.
6 |
7 | 
--------------------------------------------------------------------------------
/Samples/RedisSample/RedisSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Testing/Core/ConnectionTests/Middlewares/InjectedChannelChangeMiddleware.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.ServiceInjection;
2 | using MQContract.Interfaces.Middleware;
3 |
4 | namespace AutomatedTesting.ContractConnectionTests.Middlewares
5 | {
6 | internal class InjectedChannelChangeMiddleware(IInjectableService service)
7 | : IBeforeEncodeMiddleware
8 | {
9 | ValueTask> IBeforeEncodeMiddleware.BeforeMessageEncodeAsync(IContext context, EncodableMessage message)
10 | => ValueTask.FromResult>(new(message.MessageHeader, message.Message, service.Name));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Connectors/AmazonSNQS/AmazonSNQS.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MQContract.$(MSBuildProjectName)
6 | MQContract.$(MSBuildProjectName)
7 | Amazon SNS/SQS Connector for MQContract
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Samples/RabbitMQSample/RabbitMQSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Connectors/ZeroMQ/Exceptions.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.ZeroMQ
2 | {
3 | ///
4 | /// Thrown when you have attempted to either create the inbox subscription or made a call to Query without setting up the inbox first
5 | ///
6 | public class UndefinedInboxException : Exception
7 | {
8 | internal UndefinedInboxException()
9 | : base("You must define the inbox connection address") { }
10 |
11 | internal static void ThrowIfNullOrWhiteSpace(string? inboxAddress)
12 | {
13 | if (string.IsNullOrWhiteSpace(inboxAddress))
14 | throw new UndefinedInboxException();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Core/EnumerableExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract
2 | {
3 | internal static class EnumerableExtensions
4 | {
5 | public static async ValueTask WhenAll(this IEnumerable tasks)
6 | {
7 | foreach (var t in tasks)
8 | await t;
9 | }
10 |
11 | public static async ValueTask> WhenAll(this IEnumerable items, Func> callback)
12 | {
13 | IEnumerable result = [];
14 | foreach (var t in items)
15 | result = result.Append(await callback(t));
16 | return result;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Testing/Core/ConnectionTests/Middlewares/ChannelChangeMiddleware.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Middleware;
2 |
3 | namespace AutomatedTesting.ContractConnectionTests.Middlewares
4 | {
5 | internal class ChannelChangeMiddleware : IBeforeEncodeMiddleware
6 | {
7 | public static string ChangeChannel(string? channel)
8 | => $"{channel}-Modified";
9 | ValueTask> IBeforeEncodeMiddleware.BeforeMessageEncodeAsync(IContext context, EncodableMessage message)
10 | => ValueTask.FromResult>(new(message.MessageHeader, message.Message, ChangeChannel(message.Channel)));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Abstractions/Messages/ServiceQueryResult.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Messages;
2 |
3 | namespace MQContract.Messages
4 | {
5 | ///
6 | /// Houses a result from a query call from the Service Connection Level
7 | ///
8 | /// The ID of the message
9 | /// The headers transmitted
10 | /// The type of message encoded
11 | /// The encoded data of the message
12 | public record ServiceQueryResult(string ID, MessageHeader Header, string MessageTypeID, ReadOnlyMemory Data)
13 | : IEncodedMessage
14 | {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Samples/KubeMQSample/KubeMQSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Connectors/RabbitMQ/RabbitMQ.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract.$(MSBuildProjectName)
7 | MQContract.$(MSBuildProjectName)
8 | RabbitMQ Connector for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Samples/RabbitMQSample/Program.cs:
--------------------------------------------------------------------------------
1 | using Messages;
2 | using MQContract.RabbitMQ;
3 | using RabbitMQ.Client;
4 |
5 | var factory = new ConnectionFactory()
6 | {
7 | HostName = "localhost",
8 | Port = 5672,
9 | UserName="guest",
10 | Password="guest",
11 | MaxInboundMessageBodySize=1024*1024*4
12 | };
13 |
14 | var serviceConnection = new Connection(factory);
15 | await serviceConnection.ExchangeDeclareAsync("Greeting", ExchangeType.Fanout);
16 | await serviceConnection.ExchangeDeclareAsync("StoredArrivals", ExchangeType.Fanout, true);
17 | await serviceConnection.ExchangeDeclareAsync("Arrivals", ExchangeType.Fanout);
18 |
19 | await SampleExecution.ExecuteSample(serviceConnection, "RabbitMQ");
--------------------------------------------------------------------------------
/Testing/Core/Encoders/TestMessageEncoder.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using MQContract.Interfaces.Encoding;
3 | using System.Text;
4 |
5 | namespace AutomatedTesting.Encoders
6 | {
7 | internal class TestMessageEncoder : IMessageTypeEncoder
8 | {
9 | public ValueTask DecodeAsync(Stream stream)
10 | => ValueTask.FromResult(new CustomEncoderMessage(Encoding.ASCII.GetString(new BinaryReader(stream).ReadBytes((int)stream.Length))));
11 |
12 | public ValueTask EncodeAsync(CustomEncoderMessage message)
13 | => ValueTask.FromResult(Encoding.ASCII.GetBytes(message.TestName));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Core/Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract
7 | MQContract
8 | Core for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Connectors/HiveMQ/HiveMQ.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MQContract.$(MSBuildProjectName)
6 | MQContract.$(MSBuildProjectName)
7 | HiveMQ Connector for MQContract
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Consumers/IPubSubConsumer.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Consumers
2 | {
3 | ///
4 | /// Represents a PubSub Message Consumer to be registered to the ContractConnection
5 | ///
6 | /// The type of Message that this Consumer will consume
7 | public interface IPubSubConsumer : IBaseConsumer
8 | {
9 | ///
10 | /// Called when a message is recieved from the underlying subscription that is using this Consumer
11 | ///
12 | /// The message that was received
13 | void MessageReceived(IReceivedMessage message);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Connectors/Kafka/Kafka.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract.$(MSBuildProjectName)
7 | MQContract.$(MSBuildProjectName)
8 | Kafka Connector for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Core/Messages/ErrorServiceMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace MQContract.Messages
4 | {
5 | internal static class ErrorServiceMessage
6 | {
7 | public const string MessageTypeID = "U-InternalServiceErrorMessage-1.0.0";
8 |
9 | public static ServiceMessage Produce(string channel, Exception error)
10 | => new(Guid.NewGuid().ToString(), MessageTypeID, channel, new MessageHeader([]), EncodeError(error));
11 |
12 | private static byte[] EncodeError(Exception error)
13 | => UTF8Encoding.UTF8.GetBytes(error.Message);
14 |
15 | public static QueryResponseException DecodeError(ReadOnlyMemory data)
16 | => new(UTF8Encoding.UTF8.GetString(data.ToArray()));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Samples/Messages/AnnouncementConsumer.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces;
2 | using MQContract.Interfaces.Consumers;
3 |
4 | namespace Messages
5 | {
6 | internal class AnnouncementConsumer : IPubSubConsumer
7 | {
8 | void IBaseConsumer.ErrorRecieved(Exception error)
9 | {
10 | Console.WriteLine($"Announcement error: {error.Message}");
11 | }
12 |
13 | void IPubSubConsumer.MessageReceived(IReceivedMessage message)
14 | {
15 | Console.WriteLine($"Announcing the arrival of {message.Message.LastName}, {message.Message.FirstName} in member 2 of the group.. [{message.ID},{message.ReceivedTimestamp}]");
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Connectors/KubeMQ/Messages/PingResponse.cs:
--------------------------------------------------------------------------------
1 | using MQContract.KubeMQ.Interfaces;
2 | using MQContract.Messages;
3 |
4 | namespace MQContract.KubeMQ.Messages
5 | {
6 | internal record PingResponse
7 | : PingResult, IKubeMQPingResult
8 | {
9 | private readonly MQContract.KubeMQ.SDK.Grpc.PingResult result;
10 | public PingResponse(MQContract.KubeMQ.SDK.Grpc.PingResult result, TimeSpan responseTime)
11 | : base(result.Host, result.Version, responseTime)
12 | {
13 | this.result=result;
14 | }
15 | public DateTime ServerStartTime => Utility.FromUnixTime(result.ServerStartTime);
16 |
17 | public TimeSpan ServerUpTime => TimeSpan.FromSeconds(result.ServerUpTimeSeconds);
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/BenchMark/InMemoryBenchmarks/AnnouncementConsumer.cs:
--------------------------------------------------------------------------------
1 | using BenchMark.Messages;
2 | using MQContract.Interfaces;
3 | using MQContract.Interfaces.Consumers;
4 |
5 | namespace BenchMark.InMemoryBenchmarks
6 | {
7 | internal class AnnouncementConsumer(int count, TaskCompletionSource completionSource)
8 | : IPubSubAsyncConsumer
9 | {
10 | void IBaseConsumer.ErrorRecieved(Exception error)
11 | {
12 | }
13 |
14 | ValueTask IPubSubAsyncConsumer.MessageReceivedAsync(IReceivedMessage message)
15 | {
16 | count--;
17 | if (count<=0)
18 | completionSource.TrySetResult();
19 | return ValueTask.CompletedTask;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/CQRS/Attributes/CommandAttribute.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Attributes;
2 |
3 | namespace MQContract.CQRS.Attributes
4 | {
5 | ///
6 | /// Use this attribute to specify the Channel, TypeName and or TypeVersion of the
7 | /// Command being defined
8 | ///
9 | /// The channel to be used
10 | /// The command type to use
11 | /// The command type version to use
12 | [AttributeUsage(AttributeTargets.Class,AllowMultiple =false,Inherited =true)]
13 | public class CommandAttribute(string channel, string? typeName = null, string? typeVersion = null)
14 | : MessageAttribute(channel,typeName,typeVersion)
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Consumers/IMessageFilteredConsumer.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 |
3 | namespace MQContract.Interfaces.Consumers
4 | {
5 | ///
6 | /// Used to define a consumer that will filter out messages of a given message type
7 | ///
8 | /// The type of message the filter understands
9 | public interface IMessageFilteredConsumer : IBaseConsumer
10 | {
11 | ///
12 | /// Provides the filter callback that will be supplied the message headers and current message
13 | /// and expects back a filter instruction
14 | ///
15 | Func> Filter { get; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Abstractions/Enums.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract
2 | {
3 | ///
4 | /// These are the possible message filtering responses when supplying a message filtering action
5 | ///
6 | public enum MessageFilterResult
7 | {
8 | ///
9 | /// Allow the message to continue through
10 | ///
11 | Allow,
12 | ///
13 | /// Do not allow the message to continue to the callback and Acknowledge it within the service
14 | ///
15 | DropAndAcknowledge,
16 | ///
17 | /// Do not allow the message to continue to the callback and do not Acknowledge it within the service
18 | ///
19 | DropAndDontAcknowledge
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/CQRS/Interfaces/Command/IFilteredCommandProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.CQRS.Interfaces.Command
2 | {
3 | ///
4 | /// Used to define a command processor that will filter incoming messages based on the command
5 | ///
6 | /// The type of command that this processor handles
7 | public interface IFilteredCommandProcessor : ICommandProcessor
8 | where TCommand : ICommand
9 | {
10 | ///
11 | /// The filter callback expected to return a filter result and will be supplied the current
12 | /// context and command instance
13 | ///
14 | Func> Filter { get; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Abstractions/Messages/QueryResult.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Messages
2 | {
3 | ///
4 | /// Houses the result from a Query call into the system
5 | ///
6 | /// The type of message in the response
7 | /// The unique ID of the message
8 | /// The response headers
9 | /// The resulting response if there was one
10 | /// The error message for the response if it failed and an error was returned
11 | public record QueryResult(string ID, MessageHeader Header, TQueryResponse? Result = default, ErrorMessage? Error = null)
12 | : TransmissionResult(ID, Error)
13 | { }
14 | }
15 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Encrypting/IMessageTypeEncryptor.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Encrypting
2 | {
3 | ///
4 | /// Used to define a specific message encryptor for the type T.
5 | /// This will override the global decryptor if specified for this connection
6 | /// as well as the default of not encrypting the message body
7 | ///
8 | /// The type of message that this encryptor supports
9 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S2326:Unused type parameters should be removed", Justification = "The generic type here is used to tag an encryptor specific to a message type")]
10 | public interface IMessageTypeEncryptor : IMessageEncryptor
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Messages/IEncodedMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 |
3 | namespace MQContract.Interfaces.Messages
4 | {
5 | ///
6 | /// Used to house an underlying message that has been encoded and is ready to be "shipped" into the underlying service layer
7 | ///
8 | public interface IEncodedMessage
9 | {
10 | ///
11 | /// The header for the given message
12 | ///
13 | MessageHeader Header { get; }
14 | ///
15 | /// The message type id to transmit across
16 | ///
17 | string MessageTypeID { get; }
18 | ///
19 | /// The encoded message
20 | ///
21 | ReadOnlyMemory Data { get; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Connectors/NATS/Exceptions.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.NATS
2 | {
3 | ///
4 | /// Thrown when an error occurs attempting to connect to the NATS server.
5 | /// Specifically this will be thrown when the Ping that is executed on each initial connection fails.
6 | ///
7 | public class UnableToConnectException : Exception
8 | {
9 | internal UnableToConnectException()
10 | : base("Unable to establish connection to the NATS host") { }
11 | }
12 |
13 | ///
14 | /// Thrown when a query response error is recieved through the system
15 | ///
16 | public class QueryAsyncReponseException : Exception
17 | {
18 | internal QueryAsyncReponseException(string error)
19 | : base(error) { }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Testing/Core/ConnectionTests/Middlewares/ChannelChangeMiddlewareForBasicMessage.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using MQContract.Interfaces.Middleware;
3 |
4 | namespace AutomatedTesting.ContractConnectionTests.Middlewares
5 | {
6 | internal class ChannelChangeMiddlewareForBasicMessage : IBeforeEncodeSpecificTypeMiddleware
7 | {
8 | public static string ChangeChannel(string? channel)
9 | => $"{channel}-ModifiedSpecifically";
10 | ValueTask> IBeforeEncodeSpecificTypeMiddleware.BeforeMessageEncodeAsync(IContext context, EncodableMessage message)
11 | => ValueTask.FromResult>(new(message.MessageHeader, message.Message, ChangeChannel(message.Channel)));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Testing/Core/Consumers/BasicMessageConsumer.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using MQContract.Interfaces;
3 | using MQContract.Interfaces.Consumers;
4 |
5 | namespace AutomatedTesting.Consumers
6 | {
7 | internal class BasicMessageConsumer : IPubSubConsumer
8 | {
9 | private static readonly List> messages = [];
10 |
11 | public static List> Messages => messages;
12 |
13 | public BasicMessageConsumer()
14 | {
15 | messages.Clear();
16 | }
17 |
18 | void IBaseConsumer.ErrorRecieved(Exception error)
19 | { }
20 |
21 | void IPubSubConsumer.MessageReceived(IReceivedMessage message)
22 | => messages.Add(message);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/BenchMark/Program.cs:
--------------------------------------------------------------------------------
1 | // See https://aka.ms/new-console-template for more information
2 | using BenchMark.InMemoryBenchmarks;
3 | using BenchmarkDotNet.Configs;
4 | using BenchmarkDotNet.Jobs;
5 | using BenchmarkDotNet.Running;
6 |
7 | BenchmarkRunner.Run(
8 | typeof(SubscribingInMemory).Assembly, // all benchmarks from given assembly are going to be executed
9 | ManualConfig
10 | .Create(
11 | DefaultConfig.Instance
12 | .AddJob(
13 | BenchmarkDotNet.Jobs.Job.Default
14 | .WithWarmupCount(5)
15 | .WithMinIterationCount(3)
16 | .WithIterationCount(32)
17 | .WithInvocationCount(16)
18 | .WithMaxIterationCount(16)
19 | )
20 | .WithOptions(ConfigOptions.DisableLogFile)
21 | )
22 | );
--------------------------------------------------------------------------------
/Testing/Core/Consumers/BasicMessageConsumerIgnoringMessageType.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using MQContract.Attributes;
3 | using MQContract.Interfaces;
4 | using MQContract.Interfaces.Consumers;
5 |
6 | namespace AutomatedTesting.Consumers
7 | {
8 | [Consumer(ignoreMessageTypeHeader: true)]
9 | internal class BasicMessageConsumerIgnoringMessageType(List> messages, List errors) : IPubSubConsumer
10 | {
11 | public BasicMessageConsumerIgnoringMessageType()
12 | : this([], []) { }
13 |
14 | void IBaseConsumer.ErrorRecieved(Exception error)
15 | => errors.Add(error);
16 |
17 | void IPubSubConsumer.MessageReceived(IReceivedMessage message)
18 | => messages.Add(message);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Consumers/IPubSubAsyncConsumer.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Consumers
2 | {
3 | ///
4 | /// Represents an Asynchronous PubSub Message Consumer to be registered to the ContractConnection
5 | ///
6 | /// The type of Message that this Consumer will consume
7 | public interface IPubSubAsyncConsumer : IBaseConsumer
8 | {
9 | ///
10 | /// Called when a message is recieved from the underlying subscription that is using this Consumer
11 | ///
12 | /// The message that was received
13 | /// A ValueTask for asynchronous operations
14 | ValueTask MessageReceivedAsync(IReceivedMessage message);
15 |
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/BenchMark/InMemoryBenchmarks/AnnouncementCommandProcessor.cs:
--------------------------------------------------------------------------------
1 | using BenchMark.Messages;
2 | using MQContract.CQRS.Interfaces;
3 | using MQContract.CQRS.Interfaces.Command;
4 |
5 | namespace BenchMark.InMemoryBenchmarks
6 | {
7 | internal class AnnouncementCommandProcessor(int count, TaskCompletionSource completionSource) : ICommandProcessor
8 | {
9 | void IProcessor.ErrorRecieved(Exception error)
10 | {
11 | }
12 |
13 | ValueTask ICommandProcessor.ProcessCommandAsync(ICommandInvocationContext invocationContext, CancellationToken cancellationToken)
14 | {
15 | count--;
16 | if (count<=0)
17 | completionSource.TrySetResult();
18 | return ValueTask.CompletedTask;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Abstractions/Messages/ServiceMessage.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Messages;
2 |
3 | namespace MQContract.Messages
4 | {
5 | ///
6 | /// Houses a service level message that would be supplied to the underlying Service Connection for transmission purposes
7 | ///
8 | /// The unique ID of the message
9 | /// An identifier that identifies the type of message encoded
10 | /// The channel to transmit the message on
11 | /// The headers to transmit with the message
12 | /// The content of the message
13 | public record ServiceMessage(string ID, string MessageTypeID, string Channel, MessageHeader Header, ReadOnlyMemory Data)
14 | : IEncodedMessage
15 | { }
16 | }
17 |
--------------------------------------------------------------------------------
/Core/Middleware/MiddlewareInjectionOrderAttribute.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Middleware;
2 |
3 | namespace MQContract.Middleware
4 | {
5 | [AttributeUsage(AttributeTargets.Class, AllowMultiple=true,Inherited=false)]
6 | #pragma warning disable S2326 // Unused type parameters should be removed
7 | //T is required here as this attribute is used for defining the index in a specific manner for the given type of middleware
8 | internal class MiddlewareInjectionOrderAttribute(int preIndex=0,int postIndex=0) : Attribute
9 | #pragma warning restore S2326 // Unused type parameters should be removed
10 | where TMiddleware : IMiddleware
11 | {
12 | public int GetIndex(MiddlewareCollection.InjectionPositions position)
13 | => (position == MiddlewareCollection.InjectionPositions.Pre ? preIndex : postIndex);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Testing/Core/Consumers/BasicQueryAsyncConsumer.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using MQContract.Attributes;
3 | using MQContract.Interfaces;
4 | using MQContract.Interfaces.Consumers;
5 |
6 | namespace AutomatedTesting.Consumers
7 | {
8 | [Consumer(channel:"AsyncBasicQueryMessage",group:"AsyncBasicQueryMessageGroup")]
9 | internal class BasicQueryAsyncConsumer : IQueryResponseAsyncConsumer
10 | {
11 | void IBaseConsumer.ErrorRecieved(Exception error)
12 | { }
13 |
14 | ValueTask> IQueryResponseAsyncConsumer.MessageReceivedAsync(IReceivedMessage message)
15 | => ValueTask.FromResult>(new(new(message.Message.TypeName)));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Core/Defaults/JsonEncoder.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Encoding;
2 | using System.Text.Json;
3 |
4 | namespace MQContract.Defaults
5 | {
6 | internal class JsonEncoder : IMessageTypeEncoder
7 | {
8 | private static JsonSerializerOptions JsonOptions => new()
9 | {
10 | WriteIndented=false,
11 | AllowTrailingCommas=true,
12 | PropertyNameCaseInsensitive=true,
13 | ReadCommentHandling=JsonCommentHandling.Skip
14 | };
15 |
16 | public async ValueTask DecodeAsync(Stream stream)
17 | => await JsonSerializer.DeserializeAsync(stream, options: JsonOptions);
18 |
19 | public ValueTask EncodeAsync(TMessage message)
20 | => ValueTask.FromResult(JsonSerializer.SerializeToUtf8Bytes(message, JsonOptions));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/CQRS/Interfaces/Query/IFilteredQueryProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.CQRS.Interfaces.Query
2 | {
3 | ///
4 | /// Used to define a query processor that will filter incoming messages based on the query
5 | ///
6 | /// The type of query that this processor handles
7 | /// The type of response that this query processor provides
8 | public interface IFilteredQueryProcessor : IQueryProcessor
9 | where TQuery : IQuery
10 | {
11 | ///
12 | /// The filter callback expected to return a filter result and will be supplied the current
13 | /// context and query instance
14 | ///
15 | Func> Filter { get; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Connectors/KubeMQ/KubeMQ.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MQContract.$(MSBuildProjectName)
7 | MQContract.$(MSBuildProjectName)
8 | KubeMQ Connector for MQContract
9 |
10 |
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Connectors/Redis/Subscriptions/PubSubscription.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 | using StackExchange.Redis;
3 |
4 | namespace MQContract.Redis.Subscriptions
5 | {
6 | internal class PubSubscription(Func messageReceived, Action errorReceived, IDatabase database, Guid connectionID, string channel, string? group)
7 | : SubscriptionBase(errorReceived, database, connectionID, channel, group)
8 | {
9 | protected override async ValueTask ProcessMessage(StreamEntry streamEntry, string channel, string? group)
10 | {
11 | (var message, _, _) = Connection.ConvertMessage(
12 | streamEntry.Values,
13 | channel,
14 | () => Acknowledge(streamEntry.Id)
15 | );
16 | await messageReceived(message).ConfigureAwait(false);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Connectors/NATS/Subscriptions/PublishSubscription.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 | using NATS.Client.Core;
3 |
4 | namespace MQContract.NATS.Subscriptions
5 | {
6 | internal class PublishSubscription(IAsyncEnumerable> asyncEnumerable,
7 | Func messageReceived, Action errorReceived)
8 | : SubscriptionBase()
9 | {
10 | protected override async Task RunAction()
11 | {
12 | await foreach (var msg in asyncEnumerable.WithCancellation(CancelToken))
13 | {
14 | try
15 | {
16 | await messageReceived(ExtractMessage(msg)).ConfigureAwait(false);
17 | }
18 | catch (Exception ex)
19 | {
20 | errorReceived(ex);
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Middleware/IContext.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace MQContract.Interfaces.Middleware
4 | {
5 | ///
6 | /// This is used to represent a Context for the middleware calls to use that exists from the start to the end of the message conversion process
7 | ///
8 | public interface IContext
9 | {
10 | ///
11 | /// Used to store and retreive values from the context during the conversion process.
12 | ///
13 | /// The unique key to use
14 | /// The value if it exists in the context
15 | object? this[string key] { get; set; }
16 | ///
17 | /// Houses the current activity (if set) for the given context for Open Telemetry usage
18 | ///
19 | Activity? Activity { get; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Core/Connections/DecodeServiceMessageResult.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 |
3 | namespace MQContract.Connections
4 | {
5 | internal record DecodeServiceMessageResult
6 | {
7 | public TMessage? Message { get; private init; } = default(TMessage?);
8 | public MessageHeader? Header { get; private init; } = null;
9 | public MessageFilterResult FilterResult { get; private init; } = MessageFilterResult.Allow;
10 |
11 | public static DecodeServiceMessageResult ProduceResult(TMessage message, MessageHeader messageHeader)
12 | => new()
13 | {
14 | Message = message,
15 | Header = messageHeader
16 | };
17 |
18 | public static DecodeServiceMessageResult ProduceResult(MessageFilterResult messageFilterResult)
19 | => new() { FilterResult = messageFilterResult };
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Core/Defaults/StringEncoder.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Encoding;
2 | using System.Text;
3 |
4 | namespace MQContract.Defaults
5 | {
6 | internal class StringEncoder : IMessageTypeEncoder
7 | {
8 | async ValueTask IMessageTypeEncoder.DecodeAsync(Stream stream)
9 | {
10 | using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: 8192, leaveOpen: true);
11 | return await reader.ReadToEndAsync();
12 | }
13 |
14 | ValueTask IMessageTypeEncoder.EncodeAsync(string message)
15 | {
16 | int byteCount = Encoding.UTF8.GetByteCount(message);
17 | byte[] bytes = new byte[byteCount];
18 | Encoding.UTF8.GetBytes(message.AsSpan(), bytes.AsSpan());
19 | return ValueTask.FromResult(bytes);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/CQRS/Consumers/CommandConsumer.cs:
--------------------------------------------------------------------------------
1 | using MQContract.CQRS.Contexts;
2 | using MQContract.CQRS.Interfaces.Command;
3 | using MQContract.Interfaces;
4 | using MQContract.Interfaces.Consumers;
5 |
6 | namespace MQContract.CQRS.Consumers
7 | {
8 | internal class CommandConsumer(ICommandProcessor commandProcessor, CqrsConnection connection) : IPubSubAsyncConsumer
9 | where TCommand : ICommand
10 | {
11 | void IBaseConsumer.ErrorRecieved(Exception error)
12 | => commandProcessor.ErrorRecieved(error);
13 |
14 | async ValueTask IPubSubAsyncConsumer.MessageReceivedAsync(IReceivedMessage message)
15 | {
16 | await using var context = new CommandInvocationContext(message, connection);
17 | await commandProcessor.ProcessCommandAsync(context,context.CancellationTokenSource.Token);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Samples/NATSSample/NATSSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Consumers/IQueryResponseConsumer.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 |
3 | namespace MQContract.Interfaces.Consumers
4 | {
5 | ///
6 | /// Represents a QueryResponse Message Consumer to be registered to the ContractConnection
7 | ///
8 | /// The type of Message that is received and will be consumed
9 | /// The type of Message that is returned as a response
10 | public interface IQueryResponseConsumer : IBaseConsumer
11 | {
12 | ///
13 | /// Called when a message is received from the underlying subscript that is using this Consumer
14 | ///
15 | /// The message that was received
16 | QueryResponseMessage MessageReceived(IReceivedMessage message);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Core/Middleware/Metrics/MessageMetric.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.Metrics;
2 |
3 | namespace MQContract.Middleware.Metrics
4 | {
5 | internal record MessageMetric(UpDownCounter Sent, UpDownCounter SentBytes, UpDownCounter Received, UpDownCounter ReceivedBytes,
6 | Histogram EncodingDuration, Histogram DecodingDuration)
7 | {
8 | public void AddEntry(MetricEntryValue entry)
9 | {
10 | if (entry.Sent)
11 | {
12 | Sent.Add(1);
13 | SentBytes.Add(entry.MessageSize);
14 | EncodingDuration.Record(entry.Duration.TotalMilliseconds);
15 | }
16 | else
17 | {
18 | Received.Add(1);
19 | ReceivedBytes.Add(entry.MessageSize);
20 | DecodingDuration.Record(entry.Duration.TotalMilliseconds);
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Connectors/KubeMQ/Utility.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.KubeMQ
2 | {
3 | internal static class Utility
4 | {
5 | internal static long ToUnixTime(DateTime timestamp)
6 | {
7 | return new DateTimeOffset(timestamp).ToUniversalTime().ToUnixTimeSeconds();
8 | }
9 |
10 | internal static DateTime FromUnixTime(long timestamp)
11 | {
12 | try
13 | {
14 | return DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime.ToLocalTime();
15 | }
16 | catch (Exception)
17 | {
18 | try
19 | {
20 | return DateTimeOffset.FromUnixTimeMilliseconds(timestamp/1000000).DateTime.ToLocalTime();
21 | }
22 | catch (Exception)
23 | {
24 | return DateTime.MaxValue;
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Conversion/IMessageConverter.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Conversion
2 | {
3 | ///
4 | /// Used to define a message converter. These are called upon if a
5 | /// message is received on a channel of type T but it is waiting for
6 | /// message of type V
7 | ///
8 | /// The source message type
9 | /// The destination message type
10 | public interface IMessageConverter
11 | {
12 | ///
13 | /// Called to convert a message from type T to type V
14 | ///
15 | /// The message to convert
16 | /// The source message converted to the destination type V
17 | ValueTask ConvertAsync(TSourceMessage source);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Service/IBulkPublishableMessageServiceConnection.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 |
3 | namespace MQContract.Interfaces.Service
4 | {
5 | ///
6 | /// Used to implement a service that supports bulk message publishing
7 | ///
8 | public interface IBulkPublishableMessageServiceConnection : IMessageServiceConnection
9 | {
10 | ///
11 | /// Implements a publish call to publish the given messages in bulk
12 | ///
13 | /// The message to publish
14 | /// A cancellation token
15 | ///
16 | /// A transmission result instance indicating the result for each message
17 | ValueTask> BulkPublishAsync(IEnumerable messages, CancellationToken cancellationToken = new CancellationToken());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Middleware/IBeforeEncodeSpecificTypeMiddleware.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Middleware
2 | {
3 | ///
4 | /// This interface represents a Middleware to execute Before a specific message type is encoded
5 | ///
6 | public interface IBeforeEncodeSpecificTypeMiddleware : ISpecificTypeMiddleware
7 | {
8 | ///
9 | /// This is the method invoked as part of the Middle Ware processing during message encoding
10 | ///
11 | /// A shared context that exists from the start of this encoding instance
12 | /// The message being encoded including headers and channel
13 | /// The message, channel and header to allow for changes if desired
14 | ValueTask> BeforeMessageEncodeAsync(IContext context, EncodableMessage message);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/BenchMark/PublishBenchmarks/FakePublishConnection.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Service;
2 | using MQContract.Messages;
3 |
4 | namespace BenchMark.PublishBenchmarks
5 |
6 | {
7 | internal class FakePublishConnection : IMessageServiceConnection
8 | {
9 | uint? IMessageServiceConnection.MaxMessageBodySize => 1024 * 1024;
10 |
11 | ValueTask IMessageServiceConnection.CloseAsync()
12 | => ValueTask.CompletedTask;
13 |
14 | ValueTask IMessageServiceConnection.PublishAsync(ServiceMessage message, CancellationToken cancellationToken)
15 | => ValueTask.FromResult(new(message.ID));
16 |
17 | ValueTask IMessageServiceConnection.SubscribeAsync(Func messageReceived, Action errorReceived, string channel, string? group, CancellationToken cancellationToken)
18 | {
19 | throw new NotImplementedException();
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Middleware/IBeforeEncodeMiddleware.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Interfaces.Middleware
2 | {
3 | ///
4 | /// This interface represents a Middleware to execute Before a message is encoded
5 | ///
6 | public interface IBeforeEncodeMiddleware : IMiddleware
7 | {
8 | ///
9 | /// This is the method invoked as part of the Middle Ware processing during message encoding
10 | ///
11 | /// The type of message being processed
12 | /// A shared context that exists from the start of this encoding instance
13 | /// The message being encoded including headers and channel
14 | /// The message, channel and header to allow for changes if desired
15 | ValueTask> BeforeMessageEncodeAsync(IContext context, EncodableMessage message);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/CQRS/Interfaces/IProcessorRegistrar.cs:
--------------------------------------------------------------------------------
1 | using MQContract.CQRS.Consumers;
2 | using MQContract.CQRS.Interfaces.Command;
3 | using MQContract.CQRS.Interfaces.Query;
4 | using MQContract.Messages;
5 |
6 | namespace MQContract.CQRS.Interfaces
7 | {
8 | internal interface IProcessorRegistrar
9 | {
10 | ValueTask RegisterCommandProcessorAsync(CommandConsumer processor, string? group = null, MessageFilters? messageFilters = null)
11 | where TCommand : ICommand;
12 |
13 | ValueTask RegisterCommandProcessorAsync(CommandResponseConsumer processor, string? group = null, MessageFilters? messageFilters = null)
14 | where TCommand : ICommand;
15 | ValueTask RegisterQueryProcessorAsync(QueryResponseConsumer processor, string? group = null, MessageFilters? messageFilters=null)
16 | where TQuery : IQuery;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/CQRS/Interfaces/Query/IQueryProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.CQRS.Interfaces.Query
2 | {
3 | ///
4 | /// Defines a query processor for the given type of query that expects the given response
5 | ///
6 | /// The type of query
7 | /// The type of response from the query
8 | public interface IQueryProcessor : IProcessor
9 | where TQuery : IQuery
10 | {
11 | ///
12 | /// the callback executed against the query
13 | ///
14 | /// The current invocation context for this query instance
15 | /// A cancellation token
16 | /// The result from the query execution
17 | ValueTask ProcessQueryAsync(IQueryInvocationContext invocationContext, CancellationToken cancellationToken);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Testing/Core/Consumers/BasicMessageAsyncConsumer.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using MQContract.Attributes;
3 | using MQContract.Interfaces;
4 | using MQContract.Interfaces.Consumers;
5 |
6 | namespace AutomatedTesting.Consumers
7 | {
8 | [Consumer(channel:"AsyncBasicMessage",group:"AsyncBasicMessageGroup")]
9 | internal class BasicMessageAsyncConsumer : IPubSubAsyncConsumer
10 | {
11 | private static readonly List> messages = [];
12 |
13 | public static List> Messages => messages;
14 |
15 | public BasicMessageAsyncConsumer()
16 | {
17 | messages.Clear();
18 | }
19 |
20 | void IBaseConsumer.ErrorRecieved(Exception error)
21 | { }
22 |
23 | ValueTask IPubSubAsyncConsumer.MessageReceivedAsync(IReceivedMessage message)
24 | {
25 | messages.Add(message);
26 | return ValueTask.CompletedTask;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Consumers/IQueryResponseAsyncConsumer.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 |
3 | namespace MQContract.Interfaces.Consumers
4 | {
5 | ///
6 | /// Represents an Asynchronous QueryResponse Message Consumer to be registered to the ContractConnection
7 | ///
8 | /// The type of Message that is received and will be consumed
9 | /// The type of Message that is returned as a response
10 | public interface IQueryResponseAsyncConsumer : IBaseConsumer
11 | {
12 | ///
13 | /// Called when a message is received from the underlying subscript that is using this Consumer
14 | ///
15 | /// The message that was received
16 | /// The Response to the given Query Message
17 | ValueTask> MessageReceivedAsync(IReceivedMessage message);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Core/Defaults/DecimalEncoder.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Defaults
2 | {
3 | internal class DecimalEncoder : ABitEncoder
4 | {
5 | private const int BitsPerDecimal = 4;
6 |
7 | protected override int ByteSize => BitsPerDecimal*sizeof(int);
8 |
9 | protected override decimal ConvertValue(ReadOnlySpan value)
10 | {
11 | var bits = new int[BitsPerDecimal];
12 | for (var i = 0; i
6 | /// This interface represents a Middleware to execute after a Message has been encoded to a ServiceMessage from the supplied Class
7 | ///
8 | public interface IAfterEncodeMiddleware : IMiddleware
9 | {
10 | ///
11 | /// This is the method invoked as part of the Middleware processing during message encoding
12 | ///
13 | /// The class of the message type that was encoded
14 | /// A shared context that exists from the start of this encode process instance
15 | /// The resulting encoded message
16 | /// The message to allow for changes if desired
17 | ValueTask AfterMessageEncodeAsync(Type messageType, IContext context, ServiceMessage message);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Resiliency.md:
--------------------------------------------------------------------------------
1 | #Resiliency
2 |
3 | To enable Resiliency in any connection you would need to call one of the instances of RegisterResiliencePolicy and supply the appropriate parameters. You can configure a policy around a given message type, a given message channel, the default and also specify a service connection name when using either the mapped or multi connection system. This allows a configuration down to a detailed level, and will prioritize the policy based on the channel first, the message type second and then the default finally. This also applies when using multiple service connections with the added level of this being attempted by the connection name first then falling back to non-connection specific. The reiliency is handled when a Transmission to an underlying service returns an Error that is not tagged as Fatal as Fatal errors cannot be retried or have their circuit broken. The fatal errors would typically be along the lines of invalid parameters or other critical library errors, not connectivity errors. The underlying system being used for this Resiliency is Polly.
--------------------------------------------------------------------------------
/Abstractions/Interfaces/Service/IQueryResponseMessageServiceConnection.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Messages;
2 |
3 | namespace MQContract.Interfaces.Service
4 | {
5 | ///
6 | /// Extends the base MessageServiceConnection Interface to Response Query messaging methodology if the underlying service supports it
7 | ///
8 | public interface IQueryResponseMessageServiceConnection : IQueryableMessageServiceConnection
9 | {
10 | ///
11 | /// Implements a call to submit a response query request into the underlying service
12 | ///
13 | /// The message to query with
14 | /// The timeout for recieving a response
15 | /// A cancellation token
16 | /// A Query Result instance based on what happened
17 | ValueTask QueryAsync(ServiceMessage message, TimeSpan timeout, CancellationToken cancellationToken = new CancellationToken());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Abstractions/Messages/MessageFilters.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.Messages
2 | {
3 | ///
4 | /// Houses a set of message filtering calls for a given type. This particular record can be passed in to pubsub consumers/subscriptions
5 | /// to implement some pre-callback message filtering when receiving messages
6 | ///
7 | /// The type of message that the subscription and filter represents
8 | /// A callback filter used to filter a message by headers. This call is made prior to attempting to convert the message into the appropriate type.
9 | /// A callback filter used to filter a message by the message and or headers. This call is made after the attempt to convert the message into the approriate type.
10 | public record MessageFilters(
11 | Func>? HeaderFilter = null,
12 | Func>? MessageFilter = null
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/Connectors/KubeMQ/Interfaces/IKubeMQPingResult.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract.KubeMQ.Interfaces
2 | {
3 | ///
4 | /// The definition for a PingResponse coming from KubeMQ that has a couple of extra properties available
5 | ///
6 | public interface IKubeMQPingResult
7 | {
8 | ///
9 | /// The host name for the server pinged
10 | ///
11 | string Host { get; }
12 | ///
13 | /// The current version of KubeMQ running on it
14 | ///
15 | string Version { get; }
16 | ///
17 | /// How long it took the server to respond to the request
18 | ///
19 | TimeSpan ResponseTime { get; }
20 | ///
21 | /// The Server Start Time of the host that was pinged
22 | ///
23 | DateTime ServerStartTime { get; }
24 | ///
25 | /// The Server Up Time of the host that was pinged
26 | ///
27 | TimeSpan ServerUpTime { get; }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Connectors/InMemory/Subscription.cs:
--------------------------------------------------------------------------------
1 | using MQContract.Interfaces.Service;
2 | using System.Threading.Channels;
3 |
4 | namespace MQContract.InMemory
5 | {
6 | internal class Subscription(MessageGroup group, Func messageRecieved) : IServiceSubscription
7 | {
8 | private readonly (Guid id, Channel channel) registration = group.Register();
9 |
10 | public void Start()
11 | {
12 | Task.Run(async () =>
13 | {
14 | while (await registration.channel.Reader.WaitToReadAsync())
15 | {
16 | var message = await registration.channel.Reader.ReadAsync();
17 | await messageRecieved(message).ConfigureAwait(false);
18 | }
19 | });
20 | }
21 |
22 | ValueTask IServiceSubscription.EndAsync()
23 | {
24 | registration.channel.Writer.TryComplete();
25 | group.Unregister(registration.id);
26 | return ValueTask.CompletedTask;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Testing/Core/Encoders/TestMessageEncoderWithInjection.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using AutomatedTesting.ServiceInjection;
3 | using MQContract.Interfaces.Encoding;
4 | using System.Text;
5 |
6 | namespace AutomatedTesting.Encoders
7 | {
8 | internal class TestMessageEncoderWithInjection(IInjectableService injectableService)
9 | : IMessageTypeEncoder
10 | {
11 | public ValueTask DecodeAsync(Stream stream)
12 | {
13 | var message = Encoding.ASCII.GetString(new BinaryReader(stream).ReadBytes((int)stream.Length));
14 | Assert.StartsWith($"{injectableService.Name}:", message);
15 | return ValueTask.FromResult(new CustomEncoderWithInjectionMessage(message.Substring($"{injectableService.Name}:".Length)));
16 | }
17 |
18 | public ValueTask EncodeAsync(CustomEncoderWithInjectionMessage message)
19 | => ValueTask.FromResult(Encoding.ASCII.GetBytes($"{injectableService.Name}:{message.TestName}"));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/BenchMark/Encoders/EncodedAnnouncementEncoder.cs:
--------------------------------------------------------------------------------
1 | using BenchMark.Messages;
2 | using MQContract.Interfaces.Encoding;
3 | using System.Text.Json;
4 |
5 | namespace BenchMark.Encoders
6 | {
7 | internal class EncodedAnnouncementEncoder : IMessageTypeEncoder
8 | {
9 | private static JsonSerializerOptions JsonOptions => new()
10 | {
11 | WriteIndented=false,
12 | AllowTrailingCommas=true,
13 | PropertyNameCaseInsensitive=true,
14 | ReadCommentHandling=JsonCommentHandling.Skip,
15 | TypeInfoResolver = MyJsonContext.Default
16 | };
17 |
18 | async ValueTask IMessageTypeEncoder.DecodeAsync(Stream stream)
19 | => await JsonSerializer.DeserializeAsync(stream,options:JsonOptions);
20 |
21 | ValueTask IMessageTypeEncoder.EncodeAsync(EncodedAnnouncement message)
22 | => ValueTask.FromResult(JsonSerializer.SerializeToUtf8Bytes(message, options: JsonOptions));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/CQRS/Consumers/QueryResponseConsumer.cs:
--------------------------------------------------------------------------------
1 | using MQContract.CQRS.Contexts;
2 | using MQContract.CQRS.Interfaces.Query;
3 | using MQContract.Interfaces;
4 | using MQContract.Interfaces.Consumers;
5 | using MQContract.Messages;
6 |
7 | namespace MQContract.CQRS.Consumers
8 | {
9 | internal class QueryResponseConsumer(IQueryProcessor queryProcessor, CqrsConnection connection)
10 | : IQueryResponseAsyncConsumer
11 | where TQuery : IQuery
12 | {
13 | void IBaseConsumer.ErrorRecieved(Exception error)
14 | => queryProcessor.ErrorRecieved(error);
15 |
16 | async ValueTask> IQueryResponseAsyncConsumer.MessageReceivedAsync(IReceivedMessage message)
17 | {
18 | await using var context = new QueryInvocationContext(message, connection);
19 | var result = await queryProcessor.ProcessQueryAsync(context, context.CancellationTokenSource.Token);
20 | return new(result, context.Headers);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Roger Castaldo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Core/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace MQContract
2 | {
3 | internal static class Constants
4 | {
5 | private const string BaseActivityName = "MQContract";
6 | public const string PublishActivityName = $"{BaseActivityName}.PublishMessage";
7 | public const string PublishQueryActivityName = $"{BaseActivityName}.PublishQueryMessage";
8 | public const string BulkPublishActivityName = $"{BaseActivityName}.BulkPublishMessages";
9 | public const string ConsumeActivityName = $"{BaseActivityName}.ConsumeMessage";
10 | public const string ConsumeQueryActivityName = $"{BaseActivityName}.ConsumeQueryMessage";
11 | public const string ProduceQueryResponseActivityName = $"{BaseActivityName}.ProduceQueryResponse";
12 | public const string ConsumeQueryResponseActivityName = $"{BaseActivityName}.ConsumeQueryResponse";
13 | public const string PublishBulkMessagesMessageEvent = "BulkMessagePublished";
14 | public const string MessageFilteredName = $"{BaseActivityName}.MessageFiltered";
15 |
16 | public const string BulkPublishCountTag = "mqcontract.bulkmessagecount";
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/CQRS/Extensions/IContractConnectionExtension.cs:
--------------------------------------------------------------------------------
1 | using MQContract.CQRS.Interfaces;
2 | using MQContract.Interfaces;
3 |
4 | namespace MQContract.CQRS.Extensions
5 | {
6 | ///
7 | /// Houses extension methods for the CQRS connections linked to a Contract Connection
8 | ///
9 | public static class IContractConnectionExtension
10 | {
11 | ///
12 | /// Creates a CQRS connection instance linked to the given contract connection
13 | /// WARNING: THe Contract Connection cannot be a MultiService style connection, it only supports the single instance or mapped.
14 | ///
15 | /// The contract connection it will be linked to.
16 | /// The channel to use for distributing cancelling token Cancel calls
17 | ///
18 | public static ICQRSConnection CreateCQRSConnection(this IContractConnection contractConnection, string? cancelationTokenChannel = null)
19 | => new CqrsConnection(contractConnection, cancelationTokenChannel);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Testing/Core/Encryptors/TestMessageEncryptor.cs:
--------------------------------------------------------------------------------
1 | using AutomatedTesting.Messages;
2 | using MQContract.Interfaces.Encrypting;
3 |
4 | namespace AutomatedTesting.Encryptors
5 | {
6 | internal class TestMessageEncryptor : IMessageTypeEncryptor
7 | {
8 | private const string HeaderKey = "TestMessageEncryptorKey";
9 | private const string HeaderValue = "TestMessageEncryptorValue";
10 |
11 | public ValueTask DecryptAsync(Stream stream, MessageHeader headers)
12 | {
13 | Assert.IsNotNull(headers);
14 | Assert.IsTrue(headers.Keys.Contains(HeaderKey));
15 | Assert.AreEqual(HeaderValue, headers[HeaderKey]);
16 | var data = new BinaryReader(stream).ReadBytes((int)stream.Length);
17 | return ValueTask.FromResult