From 9ad320dbe5fa970bc1a09d9bf76f56333c9c18af Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Fri, 3 Jun 2022 00:25:14 +0200 Subject: [PATCH 01/13] v3/main Update Consumer and Producer to work with Binary Protocol, Add logging for testing purposes --- examples/andyx-examples/Program.cs | 13 +++- examples/andyx-examples/SimpleConsumer.cs | 7 +- examples/andyx-examples/SimpleProducer.cs | 2 +- ...ient.Extensions.DependencyInjection.csproj | 6 +- .../Abstractions/ConsumerBase.cs | 70 +++++++++++-------- .../Abstractions/ProducerBase.cs | 44 +++++++----- .../Producers/IProducerOtherConfiguration.cs | 4 +- .../Andy.X.Client/Andy.X.Client.csproj | 16 +++-- .../Configurations/ConsumerConfiguration.cs | 45 +++++++++--- .../Consumers/AcknowledgeMessageArgs.cs | 25 ++++--- .../Events/Consumers/ConsumerConnectedArgs.cs | 11 ++- .../Consumers/ConsumerDisconnectedArgs.cs | 10 ++- .../Consumers/MessageInternalReceivedArgs.cs | 27 +++++-- .../Events/Consumers/MessageReceivedArgs.cs | 32 ++++++--- .../Events/Producers/ProducerConnectedArgs.cs | 14 +++- .../Producers/ProducerDisconnectedArgs.cs | 12 +++- .../Events/Producers/TransmitMessageArgs.cs | 23 ++++-- .../Consumers/ConsumerNodeProvider.cs | 21 ++++-- .../Producers/ProducerNodeProvider.cs | 14 +++- 19 files changed, 278 insertions(+), 118 deletions(-) diff --git a/examples/andyx-examples/Program.cs b/examples/andyx-examples/Program.cs index 8d713ec..a13eb79 100644 --- a/examples/andyx-examples/Program.cs +++ b/examples/andyx-examples/Program.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics; +using System.Threading; namespace andyx_examples { @@ -14,8 +16,17 @@ static void Main(string[] args) SimpleConsumer consumer = new SimpleConsumer(); // initialize Producer + Console.WriteLine($"Production started"); + Stopwatch stopwatch = Stopwatch.StartNew(); SimpleProducer producer = new SimpleProducer(); - producer.ProduceSimpleMessages(1, "Andy", "Hello for Andy X the newest distributed streaming platform!"); + for (int i = 0; i < 20000000; i++) + { + producer.ProduceSimpleMessages(i, "Andy", "Hello for Andy X the newest distributed streaming platform!"); + Thread.Sleep(2); + } + + stopwatch.Stop(); + Console.WriteLine($"Finished for : {stopwatch.Elapsed.TotalSeconds} sec"); Console.ReadLine(); } diff --git a/examples/andyx-examples/SimpleConsumer.cs b/examples/andyx-examples/SimpleConsumer.cs index e7646af..da7edfe 100644 --- a/examples/andyx-examples/SimpleConsumer.cs +++ b/examples/andyx-examples/SimpleConsumer.cs @@ -12,7 +12,7 @@ public class SimpleConsumer public SimpleConsumer() { XClient client = XClient.CreateConnection() - .ForService("localhost", 6541, NodeConnectionType.SSL) + .ForService("192.168.0.24", 6540, NodeConnectionType.NON_SSL) .AndTenant("default") .AndProduct("default") .Build(); @@ -31,10 +31,11 @@ public SimpleConsumer() .ConnectAsync() .Wait(); } - + int k = 0; private bool Consumer_MessageReceived(object sender, Andy.X.Client.Events.Consumers.MessageReceivedArgs e) { - Console.WriteLine($"Message arrived: payload as raw: '{e.Payload}'; payload as simpleMessage name='{e.GenericPayload.Name}'"); + k++; + Console.WriteLine($"index:{k} => Message arrived: payload as raw: '{e.Payload}'; payload as simpleMessage name='{e.GenericPayload.Name}'"); // Message acknowledged return true; diff --git a/examples/andyx-examples/SimpleProducer.cs b/examples/andyx-examples/SimpleProducer.cs index 2801b1b..d01942b 100644 --- a/examples/andyx-examples/SimpleProducer.cs +++ b/examples/andyx-examples/SimpleProducer.cs @@ -10,7 +10,7 @@ public class SimpleProducer public SimpleProducer() { XClient client = XClient.CreateConnection() - .ForService("localhost", 6541, NodeConnectionType.SSL) + .ForService("192.168.0.24", 6540, NodeConnectionType.NON_SSL) .AndTenant("default") .AndProduct("default") .Build(); diff --git a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj b/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj index 9801680..cb7fefa 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj +++ b/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj @@ -1,8 +1,8 @@ net6.0;net5;netcoreapp3.1;netstandard2.1;netstandard2.0 - 2.1.6 - 2.1.6 + 3.0.0 + 3.0.0 Buildersoft Andy X Buildersoft Buildersoft @@ -12,7 +12,7 @@ https://github.com/buildersoftdev/andyxclient andyx eda streaming distributed - 2.1.6 + 3.0.0-alpha1 license.md andyX.png Andy.X.Client.Extensions.Microsoft.DependencyInjection diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs index 59b295c..518cb64 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs @@ -3,6 +3,7 @@ using Andy.X.Client.Configurations; using Andy.X.Client.Events.Consumers; using Andy.X.Client.Extensions; +using MessagePack; using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; @@ -17,7 +18,7 @@ public abstract partial class ConsumerBase : IConsumerSubscriptionTypeConnection, IConsumerOtherConfiguration { - public delegate bool OnMessageReceivedHandler(object sender, MessageReceivedArgs e); + public delegate MessageAcknowledgement OnMessageReceivedHandler(object sender, MessageReceivedArgs e); public event OnMessageReceivedHandler MessageReceived; private readonly XClient xClient; @@ -55,6 +56,17 @@ public ConsumerBase(XClient xClient, ConsumerConfiguration consumerConfigurat .CreateLogger(typeof(T)); } + /// + /// Subscription name + /// + /// Subscription type + /// ConsumerBase + public ConsumerBase SubscriptionName(string subscriptionName) + { + consumerConfiguration.SubscriptionSettings.SubscriptionName = subscriptionName; + return this; + } + /// /// Subscription Type represents how the Consumer consumes messages /// Default value SubscriptionType=Exclusive @@ -63,7 +75,13 @@ public ConsumerBase(XClient xClient, ConsumerConfiguration consumerConfigurat /// ConsumerBase public ConsumerBase SubscriptionType(SubscriptionType subscriptionType) { - consumerConfiguration.SubscriptionType = subscriptionType; + consumerConfiguration.SubscriptionSettings.SubscriptionType = subscriptionType; + return this; + } + + public ConsumerBase SubscriptionMode(SubscriptionMode subscriptionMode) + { + consumerConfiguration.SubscriptionSettings.SubscriptionMode = subscriptionMode; return this; } @@ -83,32 +101,31 @@ public Consumer Build() return this as Consumer; } - public async Task AcknowledgeMessage(Guid messageId, bool isAcked = true) + public async Task AcknowledgeMessage(long ledgerId, long entryId, MessageAcknowledgement messageAcknowledgement) { await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() { - Tenant = xClient.GetClientConfiguration().Tenant, - Product = xClient.GetClientConfiguration().Product, - Component = consumerConfiguration.Component, - Topic = consumerConfiguration.Topic, - Consumer = consumerConfiguration.Name, - IsAcknowledged = isAcked, - MessageId = messageId + LedgerId = ledgerId, + EntryId = entryId, + + Acknowledgement = (int)messageAcknowledgement }); } private async void ConsumerNodeService_MessageInternalReceived(MessageInternalReceivedArgs obj) { - T parsedPayload = obj.MessageRaw.ToJson().TryJsonToObject(); + T parsedPayload = MessagePackSerializer.Deserialize(obj.Payload, MessagePack.Resolvers.ContractlessStandardResolver.Options); try { - bool? isMessageAcknowledged = MessageReceived?.Invoke(this, new MessageReceivedArgs(obj.Tenant, + MessageAcknowledgement? isMessageAcknowledged = MessageReceived?.Invoke(this, new MessageReceivedArgs(obj.Tenant, obj.Product, obj.Component, obj.Topic, - obj.Id, + obj.LedgerId, + obj.EntryId, + obj.MessageId, obj.Headers, - obj.MessageRaw, + obj.Payload, parsedPayload, obj.SentDate)); @@ -119,13 +136,9 @@ private async void ConsumerNodeService_MessageInternalReceived(MessageInternalRe { await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() { - Tenant = obj.Tenant, - Product = obj.Product, - Component = obj.Component, - Topic = obj.Topic, - Consumer = consumerConfiguration.Name, - IsAcknowledged = isMessageAcknowledged.Value, - MessageId = obj.Id + LedgerId = obj.LedgerId, + EntryId = obj.EntryId, + Acknowledgement = (int)isMessageAcknowledged.Value }); } } @@ -137,14 +150,11 @@ await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() { - Tenant = obj.Tenant, - Product = obj.Product, - Component = obj.Component, - Topic = obj.Topic, - Consumer = consumerConfiguration.Name, - IsAcknowledged = false, - MessageId = obj.Id + LedgerId = obj.LedgerId, + EntryId = obj.EntryId, + Acknowledgement = (int)MessageAcknowledgement.Unacknowledged }); + logger.LogError($"MessageReceived failed to process, message is not acknowledged. Error description: '{ex.Message}'"); } } @@ -231,7 +241,7 @@ public IConsumerInitialPositionConnection WithName(string name) /// Instance of ConsumerBase for SubscriptionType. public IConsumerSubscriptionTypeConnection WithInitialPosition(InitialPosition initialPosition) { - consumerConfiguration.InitialPosition = initialPosition; + consumerConfiguration.SubscriptionSettings.InitialPosition = initialPosition; return this; } @@ -243,7 +253,7 @@ public IConsumerSubscriptionTypeConnection WithInitialPosition(InitialPositio /// Instance of ConsumerBase. public IConsumerOtherConfiguration AndSubscriptionType(SubscriptionType subscriptionType) { - consumerConfiguration.SubscriptionType = subscriptionType; + consumerConfiguration.SubscriptionSettings.SubscriptionType = subscriptionType; return this; } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs index 4ef4669..4a80f87 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs @@ -9,6 +9,9 @@ using System.Collections.Generic; using Andy.X.Client.Builders; using Andy.X.Client.Abstractions.Producers; +using Andy.X.Client.Extensions; +using System.Text; +using MessagePack; namespace Andy.X.Client.Abstractions { @@ -32,7 +35,7 @@ public abstract partial class ProducerBase : private ConcurrentQueue unsentMessagesBuffer; private bool isUnsentMessagesProcessorWorking = false; - private Dictionary defaultHeaders; + private Dictionary defaultHeaders; public ProducerBase(XClient xClient) : this(xClient, new ProducerConfiguration()) { @@ -66,7 +69,7 @@ public ProducerBase(XClient xClient, ProducerConfiguration producerConfigurat if (producerConfiguration.RetryProducing == true) unsentMessagesBuffer = new ConcurrentQueue(); - defaultHeaders = new Dictionary(); + defaultHeaders = new Dictionary(); } /// @@ -119,17 +122,17 @@ private void ProducerNodeService_ProducerConnected(ProducerConnectedArgs obj) _logger.LogWarning($"andyx-client | Producer '{obj.ProducerName}|{obj.Id}' is connected"); } - public Guid Produce(T tObject, Dictionary headers = null) + public Guid Produce(T tObject, Dictionary headers = null) { return ProduceAsync(tObject, headers).Result; } - public List Produce(IList messages, Dictionary headers = null) + public List Produce(IList messages, Dictionary headers = null) { return ProduceAsync(messages, headers).Result; } - public async Task> ProduceAsync(IList messages, Dictionary headers = null) + public async Task> ProduceAsync(IList messages, Dictionary headers = null) { headers = AddDefaultHeaderIntoMessage(headers); var ids = new List(); @@ -137,16 +140,19 @@ public async Task> ProduceAsync(IList messages, Dictionary> ProduceAsync(IList messages, Dictionary(); } - public async Task ProduceAsync(T message, Dictionary headers = null) + public async Task ProduceAsync(T message, Dictionary headers = null) { headers = AddDefaultHeaderIntoMessage(headers); var messageArgs = new TransmitMessageArgs() { - Id = Guid.NewGuid(), + Id = Guid.NewGuid().ToString(), Tenant = _xClient.GetClientConfiguration().Tenant, Product = _xClient.GetClientConfiguration().Product, Component = _producerConfiguration.Component, Topic = _producerConfiguration.Topic, - MessageRaw = message, + // this one works too, but for now we will use ContractlessStandardResolver. When we will create the schema registry we will enable resolvers. + //Payload = MessagePackSerializer.Typeless.Serialize(message), + Payload = MessagePackSerializer.Serialize(message, MessagePack.Resolvers.ContractlessStandardResolver.Options), Headers = headers, - SentDate = DateTime.UtcNow, + SentDate = DateTimeOffset.UtcNow, }; if (producerNodeService.GetConnectionState() == HubConnectionState.Connected) @@ -194,7 +202,7 @@ public async Task ProduceAsync(T message, Dictionary heade try { await producerNodeService.TransmitMessage(messageArgs); - return messageArgs.Id; + return Guid.Parse(messageArgs.Id); } catch (Exception) { @@ -206,10 +214,10 @@ public async Task ProduceAsync(T message, Dictionary heade return Guid.Empty; } - private Dictionary AddDefaultHeaderIntoMessage(Dictionary headers) + private Dictionary AddDefaultHeaderIntoMessage(Dictionary headers) { if (headers == null) - headers = new Dictionary(); + headers = new Dictionary(); foreach (var defaultHeader in defaultHeaders) { @@ -353,7 +361,7 @@ public IProducerOtherConfiguration WithName(string name) /// key string value /// value object for the header /// Instance of ProducerBase. - public IProducerOtherConfiguration AddDefaultHeader(string key, object value) + public IProducerOtherConfiguration AddDefaultHeader(string key, string value) { defaultHeaders.Add(key, value); return this; @@ -364,7 +372,7 @@ public IProducerOtherConfiguration AddDefaultHeader(string key, object value) /// /// Dictionary object for the headers /// Instance of ProducerBase. - public IProducerOtherConfiguration AddDefaultHeader(IDictionary headers) + public IProducerOtherConfiguration AddDefaultHeader(IDictionary headers) { foreach (var header in headers) { @@ -404,7 +412,7 @@ public Producer Build() { // Add default headers defaultHeaders.Add("andyx-client", "Andy X Client for .NET"); - defaultHeaders.Add("andyx-client-version", "v2.1"); + defaultHeaders.Add("andyx-client-version", "v3.0.0-alpha1"); defaultHeaders.Add("andyx-producer-name", _producerConfiguration.Name); defaultHeaders.Add("andyx-content-type", "application/json"); diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerOtherConfiguration.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerOtherConfiguration.cs index d22b4d8..3e559cf 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerOtherConfiguration.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerOtherConfiguration.cs @@ -5,8 +5,8 @@ namespace Andy.X.Client.Abstractions.Producers { public interface IProducerOtherConfiguration { - IProducerOtherConfiguration AddDefaultHeader(string key, object value); - IProducerOtherConfiguration AddDefaultHeader(IDictionary headers); + IProducerOtherConfiguration AddDefaultHeader(string key, string value); + IProducerOtherConfiguration AddDefaultHeader(IDictionary headers); IProducerOtherConfiguration RetryProducingIfFails(); IProducerOtherConfiguration HowManyTimesToTryProducing(int nTimesRetry); diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Andy.X.Client.csproj b/src/net/andy-x-client-lib/Andy.X.Client/Andy.X.Client.csproj index d81111a..85f182d 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Andy.X.Client.csproj +++ b/src/net/andy-x-client-lib/Andy.X.Client/Andy.X.Client.csproj @@ -2,8 +2,8 @@ net6.0;net5;netcoreapp3.1;netstandard2.1;netstandard2.0 - 2.1.6 - 2.1.6 + 3.0.0 + 3.0.0 Buildersoft Andy X Buildersoft Buildersoft @@ -14,7 +14,7 @@ https://github.com/buildersoftdev/andyxclient andyx eda streaming distributed - 2.1.6 + 3.0.0-alpha1 license.md andyX.png Buildersoft.Andy.X.Client @@ -76,11 +76,13 @@ - + + - - - + + + + diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/ConsumerConfiguration.cs b/src/net/andy-x-client-lib/Andy.X.Client/Configurations/ConsumerConfiguration.cs index b471f38..1150850 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/ConsumerConfiguration.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Configurations/ConsumerConfiguration.cs @@ -29,6 +29,20 @@ public class ConsumerConfiguration /// public bool IsTopicPersistent { get; set; } + public SubscriptionSettings SubscriptionSettings { get; set; } + + + public ConsumerConfiguration() + { + SubscriptionSettings = new SubscriptionSettings(); + IsTopicPersistent = true; + Name = "default"; + } + } + + public class SubscriptionSettings + { + public string SubscriptionName { get; set; } /// /// Subscription Type represents how the Consumer consumes messages /// Default value SubscriptionType=Exclusive @@ -42,32 +56,43 @@ public class ConsumerConfiguration /// Default value is Latest /// public InitialPosition InitialPosition { get; set; } + public SubscriptionMode SubscriptionMode { get; set; } - public ConsumerConfiguration() + public SubscriptionSettings() { - IsTopicPersistent = true; - - SubscriptionType = SubscriptionType.Exclusive; + SubscriptionType = SubscriptionType.Unique; + SubscriptionMode = SubscriptionMode.Resilient; InitialPosition = InitialPosition.Latest; - - Name = "default"; } } public enum SubscriptionType { /// - /// Only one consumer with the same name + /// Only one consumer /// - Exclusive, + Unique, /// - /// One consumer with one backup with the same name + /// One consumer with one backup /// Failover, /// - /// Shared to more than one consumer with the same name + /// Shared to more than one consumer. /// Shared } + + public enum SubscriptionMode + { + /// + /// Durable + /// + Resilient, + + /// + /// Non Durable + /// + NonResilient + } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs index 91c61ef..ac96d83 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs @@ -1,19 +1,26 @@ -using System; +using MessagePack; namespace Andy.X.Client.Abstractions { public abstract partial class ConsumerBase { - private class AcknowledgeMessageArgs + [MessagePackObject] + public class AcknowledgeMessageArgs { - public string Tenant { get; set; } - public string Product { get; set; } - public string Component { get; set; } - public string Topic { get; set; } + [Key(0)] + public long LedgerId { get; set; } + [Key(1)] + public long EntryId { get; set; } - public string Consumer { get; set; } - public bool IsAcknowledged { get; set; } - public Guid MessageId { get; set; } + [Key(2)] + public int Acknowledgement { get; set; } } } + + public enum MessageAcknowledgement + { + Acknowledged, + Unacknowledged, + Skipped + } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs index 6d532c4..07721d1 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs @@ -1,4 +1,4 @@ -using Andy.X.Client.Configurations; +using MessagePack; using System; @@ -6,14 +6,21 @@ namespace Andy.X.Client.Abstractions { public abstract partial class ConsumerBase { - private class ConsumerConnectedArgs + [MessagePackObject] + public class ConsumerConnectedArgs { + [Key(0)] public string Tenant { get; set; } + [Key(1)] public string Product { get; set; } + [Key(2)] public string Component { get; set; } + [Key(3)] public string Topic { get; set; } + [Key(4)] public Guid Id { get; set; } + [Key(5)] public string ConsumerName { get; set; } } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs index 255b8f5..85034c4 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs @@ -1,17 +1,25 @@ -using System; +using MessagePack; +using System; namespace Andy.X.Client.Abstractions { public abstract partial class ConsumerBase { + [MessagePackObject] private class ConsumerDisconnectedArgs { + [Key(0)] public string Tenant { get; set; } + [Key(1)] public string Product { get; set; } + [Key(2)] public string Component { get; set; } + [Key(3)] public string Topic { get; set; } + [Key(4)] public Guid Id { get; set; } + [Key(5)] public string ConsumerName { get; set; } } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs index cef223f..22906c3 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs @@ -1,22 +1,39 @@ -using System; +using MessagePack; +using System; using System.Collections.Generic; namespace Andy.X.Client.Abstractions { public abstract partial class ConsumerBase { + [MessagePackObject] public class MessageInternalReceivedArgs { + [Key(0)] public string Tenant { get; set; } + [Key(1)] public string Product { get; set; } + [Key(2)] public string Component { get; set; } + [Key(3)] public string Topic { get; set; } - public Guid Id { get; set; } - public object MessageRaw { get; set; } - public Dictionary Headers { get; set; } + [Key(4)] + public long LedgerId { get; set; } + [Key(5)] + public long EntryId { get; set; } - public DateTime SentDate { get; set; } + [Key(6)] + public Dictionary Headers { get; set; } + + [Key(7)] + public string MessageId { get; set; } + + [Key(8)] + public byte[] Payload { get; set; } + + [Key(9)] + public DateTimeOffset SentDate { get; set; } } } } \ No newline at end of file diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs index 8f69135..707e705 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs @@ -10,34 +10,50 @@ public class MessageReceivedArgs public string Component { get; private set; } public string Topic { get; private set; } - public Guid MessageId { get; private set; } + public long LedgerId { get; set; } + public long EntryId { get; set; } - public Dictionary Headers{ get; set; } + public string MessageId { get; private set; } + + public Dictionary Headers{ get; set; } // Summary: - // Gets the message data as a System.Object. - public object Payload { get; private set; } + // Gets the message data as a byte[]. + public byte[] Payload { get; private set; } public T GenericPayload { get; private set; } - public DateTime SentDate { get; private set; } - public DateTime ReceivedDate { get; private set; } + public DateTimeOffset SentDate { get; private set; } + public DateTimeOffset ReceivedDate { get; private set; } - public MessageReceivedArgs(string tenant, string product, string component, string topic, Guid messageId, Dictionary headers, object payload, T genericPayload, DateTime sentDate) + public MessageReceivedArgs(string tenant, + string product, + string component, + string topic, + long ledgerId, + long entryId, + string messageId, + Dictionary headers, + byte[] payload, + T genericPayload, + DateTimeOffset sentDate) { Tenant = tenant; Product = product; Component = component; Topic = topic; + LedgerId = ledgerId; + EntryId = entryId; + MessageId = messageId; Headers = headers; Payload = payload; GenericPayload = genericPayload; SentDate = sentDate; - ReceivedDate = DateTime.UtcNow; + ReceivedDate = DateTimeOffset.UtcNow; } } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs index fd40386..4436c03 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs @@ -1,17 +1,25 @@ -using System; +using MessagePack; +using System; namespace Andy.X.Client.Abstractions { public abstract partial class ProducerBase { - private class ProducerConnectedArgs + [MessagePackObject] + public class ProducerConnectedArgs { + [Key(0)] public string Tenant { get; set; } + [Key(1)] public string Product { get; set; } + [Key(2)] public string Component { get; set; } + [Key(3)] public string Topic { get; set; } - public Guid Id { get; set; } + [Key(4)] + public string Id { get; set; } + [Key(5)] public string ProducerName { get; set; } } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs index 3cda3b0..ed085f9 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs @@ -1,17 +1,25 @@ -using System; +using MessagePack; +using System; namespace Andy.X.Client.Abstractions { public abstract partial class ProducerBase { - private class ProducerDisconnectedArgs + [MessagePackObject] + public class ProducerDisconnectedArgs { + [Key(0)] public string Tenant { get; set; } + [Key(1)] public string Product { get; set; } + [Key(2)] public string Component { get; set; } + [Key(3)] public string Topic { get; set; } + [Key(4)] public Guid Id { get; set; } + [Key(5)] public string ProducerName { get; set; } } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/TransmitMessageArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/TransmitMessageArgs.cs index 3fcc01f..b9ce3db 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/TransmitMessageArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/TransmitMessageArgs.cs @@ -1,23 +1,34 @@ -using System; +using MessagePack; +using System; using System.Collections.Generic; namespace Andy.X.Client.Abstractions { public abstract partial class ProducerBase { - private class TransmitMessageArgs + [MessagePackObject] + public class TransmitMessageArgs { + [Key(0)] public string Tenant { get; set; } + [Key(1)] public string Product { get; set; } + [Key(2)] public string Component { get; set; } + [Key(3)] public string Topic { get; set; } - public Guid Id { get; set; } - public Dictionary Headers { get; set; } + [Key(4)] + public Dictionary Headers { get; set; } - public object MessageRaw { get; set; } + [Key(5)] + public string Id { get; set; } - public DateTime SentDate { get; set; } + [Key(6)] + public byte[] Payload { get; set; } + + [Key(7)] + public DateTimeOffset SentDate { get; set; } } private class RetryTransmitMessage diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs index 005e413..292210d 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs @@ -1,5 +1,8 @@ using Andy.X.Client.Configurations; +using MessagePack; using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Andy.X.Client.Abstractions { @@ -18,7 +21,7 @@ public ConsumerNodeProvider(XClientConfiguration xClientConfig, ConsumerConfigur this.consumerConfig = consumerConfig; _connection = new HubConnectionBuilder() - .WithUrl($"{xClientConfig.ServiceUrl}/realtime/v2/consumer", option => + .WithUrl($"{xClientConfig.ServiceUrl}/realtime/v3/consumer", option => { option.HttpMessageHandlerFactory = (message) => { @@ -32,13 +35,21 @@ public ConsumerNodeProvider(XClientConfiguration xClientConfig, ConsumerConfigur option.Headers["x-andyx-product"] = xClientConfig.Product; option.Headers["x-andyx-component"] = consumerConfig.Component; option.Headers["x-andyx-topic"] = consumerConfig.Topic; - option.Headers["x-andyx-consumer"] = consumerConfig.Name; - option.Headers["x-andyx-consumer-type"] = consumerConfig.SubscriptionType.ToString(); - + option.Headers["x-andyx-consumer-name"] = consumerConfig.Name; option.Headers["x-andyx-topic-is-persistent"] = consumerConfig.IsTopicPersistent.ToString(); - option.Headers["x-andyx-consumer-initial-position"] = consumerConfig.InitialPosition.ToString(); + + option.Headers["x-andyx-subscription-name"] = consumerConfig.SubscriptionSettings.SubscriptionName; + option.Headers["x-andyx-subscription-type"] = consumerConfig.SubscriptionSettings.SubscriptionType.ToString(); + option.Headers["x-andyx-subscription-mode"] = consumerConfig.SubscriptionSettings.SubscriptionMode.ToString(); + option.Headers["x-andyx-subscription-initial-position"] = consumerConfig.SubscriptionSettings.InitialPosition.ToString(); }) .WithAutomaticReconnect() + .AddMessagePackProtocol() + .ConfigureLogging(factory => + { + factory.AddConsole(); + factory.AddFilter("Console", level => level >= LogLevel.Trace); + }) .Build(); } public HubConnection GetHubConnection() diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs index dd77da0..f590e03 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs @@ -1,5 +1,9 @@ using Andy.X.Client.Configurations; +using MessagePack; +using MessagePack.Resolvers; using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Andy.X.Client.Abstractions { @@ -18,7 +22,7 @@ public ProducerNodeProvider(XClientConfiguration xClientConfig, ProducerConfigur this.producerConfig = producerConfig; _connection = new HubConnectionBuilder() - .WithUrl($"{xClientConfig.ServiceUrl}/realtime/v2/producer", option => + .WithUrl($"{xClientConfig.ServiceUrl}/realtime/v3/producer", option => { option.HttpMessageHandlerFactory = (message) => { @@ -32,11 +36,17 @@ public ProducerNodeProvider(XClientConfiguration xClientConfig, ProducerConfigur option.Headers["x-andyx-product"] = xClientConfig.Product; option.Headers["x-andyx-component"] = producerConfig.Component; option.Headers["x-andyx-topic"] = producerConfig.Topic; - option.Headers["x-andyx-producer"] = producerConfig.Name; + option.Headers["x-andyx-producer-name"] = producerConfig.Name; option.Headers["x-andyx-topic-is-persistent"] = producerConfig.IsTopicPersistent.ToString(); }) .WithAutomaticReconnect() + .AddMessagePackProtocol() + .ConfigureLogging(factory => + { + factory.AddConsole(); + factory.AddFilter("Console", level => level >= LogLevel.Trace); + }) .Build(); } From 7c162c1ce9526ebd395a18f84fb78a2a807c3a34 Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Sat, 9 Jul 2022 19:44:33 +0200 Subject: [PATCH 02/13] v3/no_ticket Implementation of NodeId, Add NodeId into Message and AcknowledgeMessage --- .../Abstractions/ConsumerBase.cs | 11 +++++----- .../Consumers/AcknowledgeMessageArgs.cs | 5 +++-- .../Consumers/MessageInternalReceivedArgs.cs | 5 +++-- .../Events/Consumers/MessageReceivedArgs.cs | 22 +++++++++---------- .../Consumers/ConsumerNodeProvider.cs | 3 ++- .../Producers/ProducerNodeProvider.cs | 13 ++++++++++- .../Andy.X.Client/XClient.cs | 2 ++ 7 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs index 518cb64..759f2e6 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs @@ -101,13 +101,12 @@ public Consumer Build() return this as Consumer; } - public async Task AcknowledgeMessage(long ledgerId, long entryId, MessageAcknowledgement messageAcknowledgement) + public async Task AcknowledgeMessage(string nodeId, long entryId, MessageAcknowledgement messageAcknowledgement) { await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() { - LedgerId = ledgerId, EntryId = entryId, - + NodeId = nodeId, Acknowledgement = (int)messageAcknowledgement }); } @@ -121,8 +120,8 @@ private async void ConsumerNodeService_MessageInternalReceived(MessageInternalRe obj.Product, obj.Component, obj.Topic, - obj.LedgerId, obj.EntryId, + obj.NodeId, obj.MessageId, obj.Headers, obj.Payload, @@ -136,8 +135,8 @@ private async void ConsumerNodeService_MessageInternalReceived(MessageInternalRe { await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() { - LedgerId = obj.LedgerId, EntryId = obj.EntryId, + NodeId = obj.NodeId, Acknowledgement = (int)isMessageAcknowledged.Value }); } @@ -150,8 +149,8 @@ await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() { - LedgerId = obj.LedgerId, EntryId = obj.EntryId, + NodeId = obj.NodeId, Acknowledgement = (int)MessageAcknowledgement.Unacknowledged }); diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs index ac96d83..3e73a9f 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs @@ -8,10 +8,11 @@ public abstract partial class ConsumerBase public class AcknowledgeMessageArgs { [Key(0)] - public long LedgerId { get; set; } - [Key(1)] public long EntryId { get; set; } + [Key(1)] + public string NodeId { get; set; } + [Key(2)] public int Acknowledgement { get; set; } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs index 22906c3..ec8b1fb 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs @@ -19,10 +19,11 @@ public class MessageInternalReceivedArgs public string Topic { get; set; } [Key(4)] - public long LedgerId { get; set; } - [Key(5)] public long EntryId { get; set; } + [Key(5)] + public string NodeId { get; set; } + [Key(6)] public Dictionary Headers { get; set; } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs index 707e705..03a95d7 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs @@ -10,12 +10,12 @@ public class MessageReceivedArgs public string Component { get; private set; } public string Topic { get; private set; } - public long LedgerId { get; set; } public long EntryId { get; set; } + public string NodeId { get; set; } public string MessageId { get; private set; } - public Dictionary Headers{ get; set; } + public Dictionary Headers { get; set; } // Summary: // Gets the message data as a byte[]. @@ -27,15 +27,15 @@ public class MessageReceivedArgs - public MessageReceivedArgs(string tenant, - string product, - string component, - string topic, - long ledgerId, + public MessageReceivedArgs(string tenant, + string product, + string component, + string topic, long entryId, - string messageId, - Dictionary headers, - byte[] payload, + string nodeId, + string messageId, + Dictionary headers, + byte[] payload, T genericPayload, DateTimeOffset sentDate) { @@ -44,8 +44,8 @@ public MessageReceivedArgs(string tenant, Component = component; Topic = topic; - LedgerId = ledgerId; EntryId = entryId; + NodeId = nodeId; MessageId = messageId; Headers = headers; diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs index 292210d..3385973 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System.Net.Http; namespace Andy.X.Client.Abstractions { @@ -47,7 +48,7 @@ public ConsumerNodeProvider(XClientConfiguration xClientConfig, ConsumerConfigur .AddMessagePackProtocol() .ConfigureLogging(factory => { - factory.AddConsole(); + factory.AddSystemdConsole(); factory.AddFilter("Console", level => level >= LogLevel.Trace); }) .Build(); diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs index f590e03..fdd16ac 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System.Net.Http; namespace Andy.X.Client.Abstractions { @@ -24,9 +25,19 @@ public ProducerNodeProvider(XClientConfiguration xClientConfig, ProducerConfigur _connection = new HubConnectionBuilder() .WithUrl($"{xClientConfig.ServiceUrl}/realtime/v3/producer", option => { + //option.HttpMessageHandlerFactory = (message) => + //{ + // return xClientConfig.HttpClientHandler; + //}; + + option.HttpMessageHandlerFactory = (message) => { - return xClientConfig.HttpClientHandler; + if (message is HttpClientHandler clientHandler) + // always verify the SSL certificate + clientHandler.ServerCertificateCustomValidationCallback += + (sender, certificate, chain, sslPolicyErrors) => { return true; }; + return message; }; option.Headers["x-andyx-tenant-authoriziation"] = xClientConfig.TenantToken; diff --git a/src/net/andy-x-client-lib/Andy.X.Client/XClient.cs b/src/net/andy-x-client-lib/Andy.X.Client/XClient.cs index f479894..71fc9a1 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/XClient.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/XClient.cs @@ -95,6 +95,8 @@ public IXClientTenantConnection ForService(string nodeHostName, int hostPort, No { _xClientConfiguration.HttpClientHandler.ServerCertificateCustomValidationCallback += (sender, certificate, chain, sslPolicyErrors) => { return true; }; + + } return this; From a47fe9bcd115438b1e8e05961e0c48513371bd6c Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Fri, 26 Aug 2022 11:02:28 +0200 Subject: [PATCH 03/13] v3/feature/27 Implement v3 structure of the .net library --- Andy.X.Client.sln | 31 +++++++ ...ient.Extensions.DependencyInjection.csproj | 7 ++ .../Assets/andyX.png | Bin 0 -> 58471 bytes .../Assets/license.md | 20 +++++ .../XClients/IXClientConfiguration.cs | 14 ++++ .../XClients/IXClientProductConnection.cs | 8 ++ .../XClients/IXClientServiceConnection.cs | 12 +++ .../XClients/IXClientTenantConnection.cs | 8 ++ src/Andy.X.Client/Andy.X.Client.csproj | 65 +++++++++++++++ src/Andy.X.Client/Assets/andyX.png | Bin 0 -> 58471 bytes src/Andy.X.Client/Assets/license.md | 20 +++++ .../Configurations/NodeConnectionType.cs | 14 ++++ src/Andy.X.Client/XClient.cs | 76 ++++++++++++++++++ .../Events/Consumers/ConsumerConnectedArgs.cs | 6 +- .../Producers/ProducerNodeProvider.cs | 2 - 15 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 Andy.X.Client.sln create mode 100644 src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj create mode 100644 src/Andy.X.Client.Extensions.DependencyInjection/Assets/andyX.png create mode 100644 src/Andy.X.Client.Extensions.DependencyInjection/Assets/license.md create mode 100644 src/Andy.X.Client/Abstractions/XClients/IXClientConfiguration.cs create mode 100644 src/Andy.X.Client/Abstractions/XClients/IXClientProductConnection.cs create mode 100644 src/Andy.X.Client/Abstractions/XClients/IXClientServiceConnection.cs create mode 100644 src/Andy.X.Client/Abstractions/XClients/IXClientTenantConnection.cs create mode 100644 src/Andy.X.Client/Andy.X.Client.csproj create mode 100644 src/Andy.X.Client/Assets/andyX.png create mode 100644 src/Andy.X.Client/Assets/license.md create mode 100644 src/Andy.X.Client/Configurations/NodeConnectionType.cs create mode 100644 src/Andy.X.Client/XClient.cs diff --git a/Andy.X.Client.sln b/Andy.X.Client.sln new file mode 100644 index 0000000..bdb1cec --- /dev/null +++ b/Andy.X.Client.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32804.467 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Andy.X.Client", "src\Andy.X.Client\Andy.X.Client.csproj", "{5D4C1C4B-1A71-4C4C-BBFE-D67E91E8AB10}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Andy.X.Client.Extensions.DependencyInjection", "src\Andy.X.Client.Extensions.DependencyInjection\Andy.X.Client.Extensions.DependencyInjection.csproj", "{A96A1988-9ABC-45BC-B643-A259807821D1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5D4C1C4B-1A71-4C4C-BBFE-D67E91E8AB10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D4C1C4B-1A71-4C4C-BBFE-D67E91E8AB10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D4C1C4B-1A71-4C4C-BBFE-D67E91E8AB10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D4C1C4B-1A71-4C4C-BBFE-D67E91E8AB10}.Release|Any CPU.Build.0 = Release|Any CPU + {A96A1988-9ABC-45BC-B643-A259807821D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A96A1988-9ABC-45BC-B643-A259807821D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A96A1988-9ABC-45BC-B643-A259807821D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A96A1988-9ABC-45BC-B643-A259807821D1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D1632860-7F16-417F-B5D8-67F265E250C4} + EndGlobalSection +EndGlobal diff --git a/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj b/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj new file mode 100644 index 0000000..8f839b3 --- /dev/null +++ b/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj @@ -0,0 +1,7 @@ + + + + net6.0;net5;netcoreapp3.1;netstandard2.1;netstandard2.0 + + + diff --git a/src/Andy.X.Client.Extensions.DependencyInjection/Assets/andyX.png b/src/Andy.X.Client.Extensions.DependencyInjection/Assets/andyX.png new file mode 100644 index 0000000000000000000000000000000000000000..0411c961ec3e156c621bea6b4e9e28bcac07c439 GIT binary patch literal 58471 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 I*+2RJ01TmIVgLXD literal 0 HcmV?d00001 diff --git a/src/Andy.X.Client.Extensions.DependencyInjection/Assets/license.md b/src/Andy.X.Client.Extensions.DependencyInjection/Assets/license.md new file mode 100644 index 0000000..6413433 --- /dev/null +++ b/src/Andy.X.Client.Extensions.DependencyInjection/Assets/license.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2022 Buildersoft + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/Andy.X.Client/Abstractions/XClients/IXClientConfiguration.cs b/src/Andy.X.Client/Abstractions/XClients/IXClientConfiguration.cs new file mode 100644 index 0000000..eb3edd0 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/XClients/IXClientConfiguration.cs @@ -0,0 +1,14 @@ +using System.Net.Http; +using System; +using Microsoft.Extensions.Logging; + +namespace Andy.X.Client.Abstractions.XClients +{ + internal interface IXClientConfiguration + { + IXClientConfiguration AddLoggingSupport(ILoggerFactory loggerFactory); + IXClientConfiguration WithHttpClientHandler(Action httpHandler); + + XClient Build(); + } +} diff --git a/src/Andy.X.Client/Abstractions/XClients/IXClientProductConnection.cs b/src/Andy.X.Client/Abstractions/XClients/IXClientProductConnection.cs new file mode 100644 index 0000000..dcdfb0c --- /dev/null +++ b/src/Andy.X.Client/Abstractions/XClients/IXClientProductConnection.cs @@ -0,0 +1,8 @@ +namespace Andy.X.Client.Abstractions.XClients +{ + internal interface IXClientProductConnection + { + IXClientConfiguration AndProduct(string product); + IXClientConfiguration AndProduct(string product, string key, string secret); + } +} diff --git a/src/Andy.X.Client/Abstractions/XClients/IXClientServiceConnection.cs b/src/Andy.X.Client/Abstractions/XClients/IXClientServiceConnection.cs new file mode 100644 index 0000000..d614f4f --- /dev/null +++ b/src/Andy.X.Client/Abstractions/XClients/IXClientServiceConnection.cs @@ -0,0 +1,12 @@ +using Andy.X.Client.Configurations; + +namespace Andy.X.Client.Abstractions.XClients +{ + internal interface IXClientServiceConnection + { + IXClientTenantConnection ForService(string nodeUrl); + IXClientTenantConnection ForService(string nodeHostName, int hostPort); + IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType); + IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType, bool isSSLCertsSkipped); + } +} diff --git a/src/Andy.X.Client/Abstractions/XClients/IXClientTenantConnection.cs b/src/Andy.X.Client/Abstractions/XClients/IXClientTenantConnection.cs new file mode 100644 index 0000000..9acf8bb --- /dev/null +++ b/src/Andy.X.Client/Abstractions/XClients/IXClientTenantConnection.cs @@ -0,0 +1,8 @@ +namespace Andy.X.Client.Abstractions.XClients +{ + internal interface IXClientTenantConnection + { + IXClientProductConnection AndTenant(string tenant); + IXClientProductConnection AndTenant(string tenant, string key, string secret); + } +} diff --git a/src/Andy.X.Client/Andy.X.Client.csproj b/src/Andy.X.Client/Andy.X.Client.csproj new file mode 100644 index 0000000..e9db310 --- /dev/null +++ b/src/Andy.X.Client/Andy.X.Client.csproj @@ -0,0 +1,65 @@ + + + + net6.0;net5;netcoreapp3.1;netstandard2.1;netstandard2.0 + + 3.0.0 + 3.0.0 + Buildersoft Andy X + Buildersoft + Buildersoft + Copyright © Buildersoft 2022 + + Andy X is an open-source distributed streaming platform designed to deliver the best performance possible for high-performance data pipelines, streaming analytics, streaming between microservices and data integrations. + This library offers flexible messaging models with high-level APIs for NET. + + + https://github.com/buildersoftdev/andyxclient + andyx eda streaming distributed + 3.0.0-alpha1 + license.md + andyX.png + Buildersoft.Andy.X.Client + README.md + True + True + True + + In this version we added support for Bulk Producing, we re-designed the entire apis for Andy X Client. + https://buildersoftdev.azurewebsites.net/andyx + + + + + + + + + + + + + + + + + + True + + + + True + + + + True + + + + + + + + + + diff --git a/src/Andy.X.Client/Assets/andyX.png b/src/Andy.X.Client/Assets/andyX.png new file mode 100644 index 0000000000000000000000000000000000000000..0411c961ec3e156c621bea6b4e9e28bcac07c439 GIT binary patch literal 58471 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 I*+2RJ01TmIVgLXD literal 0 HcmV?d00001 diff --git a/src/Andy.X.Client/Assets/license.md b/src/Andy.X.Client/Assets/license.md new file mode 100644 index 0000000..6413433 --- /dev/null +++ b/src/Andy.X.Client/Assets/license.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2022 Buildersoft + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/Andy.X.Client/Configurations/NodeConnectionType.cs b/src/Andy.X.Client/Configurations/NodeConnectionType.cs new file mode 100644 index 0000000..761be47 --- /dev/null +++ b/src/Andy.X.Client/Configurations/NodeConnectionType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Andy.X.Client.Configurations +{ + public enum NodeConnectionType + { + SSL, + NON_SSL + } +} diff --git a/src/Andy.X.Client/XClient.cs b/src/Andy.X.Client/XClient.cs new file mode 100644 index 0000000..1273307 --- /dev/null +++ b/src/Andy.X.Client/XClient.cs @@ -0,0 +1,76 @@ +using Andy.X.Client.Abstractions.XClients; +using Andy.X.Client.Configurations; +using Microsoft.Extensions.Logging; +using System; +using System.Net.Http; + +namespace Andy.X.Client +{ + public class XClient : IXClientServiceConnection, IXClientTenantConnection, IXClientProductConnection, IXClientConfiguration + { + private XClient() + { + + } + + public static IXClientTenantConnection CreateClient() + { + return new XClient(); + } + + public XClient Build() + { + throw new NotImplementedException(); + } + + IXClientConfiguration IXClientConfiguration.AddLoggingSupport(ILoggerFactory loggerFactory) + { + throw new NotImplementedException(); + } + + IXClientConfiguration IXClientProductConnection.AndProduct(string product) + { + throw new NotImplementedException(); + } + + IXClientConfiguration IXClientProductConnection.AndProduct(string product, string key, string secret) + { + throw new NotImplementedException(); + } + + IXClientProductConnection IXClientTenantConnection.AndTenant(string tenant) + { + throw new NotImplementedException(); + } + + IXClientProductConnection IXClientTenantConnection.AndTenant(string tenant, string key, string secret) + { + throw new NotImplementedException(); + } + + IXClientTenantConnection IXClientServiceConnection.ForService(string nodeUrl) + { + throw new NotImplementedException(); + } + + IXClientTenantConnection IXClientServiceConnection.ForService(string nodeHostName, int hostPort) + { + throw new NotImplementedException(); + } + + IXClientTenantConnection IXClientServiceConnection.ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType) + { + throw new NotImplementedException(); + } + + IXClientTenantConnection IXClientServiceConnection.ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType, bool isSSLCertsSkipped) + { + throw new NotImplementedException(); + } + + IXClientConfiguration IXClientConfiguration.WithHttpClientHandler(Action httpHandler) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs index 07721d1..34c33b6 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs @@ -1,7 +1,6 @@ using MessagePack; using System; - namespace Andy.X.Client.Abstractions { public abstract partial class ConsumerBase @@ -19,8 +18,11 @@ public class ConsumerConnectedArgs public string Topic { get; set; } [Key(4)] - public Guid Id { get; set; } + public string Subscription { get; set; } + [Key(5)] + public Guid Id { get; set; } + [Key(6)] public string ConsumerName { get; set; } } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs index fdd16ac..bb70065 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs @@ -1,6 +1,4 @@ using Andy.X.Client.Configurations; -using MessagePack; -using MessagePack.Resolvers; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; From 0d083bbd3d4161d1f3322f1e7c1b57ab7104bbba Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Mon, 19 Sep 2022 02:05:03 +0200 Subject: [PATCH 04/13] Add new components --- .../Abstractions/ConsumerBase.cs | 6 +++++ .../Abstractions/ProducerBase.cs | 6 +++++ .../Consumers/ConsumerNodeProvider.cs | 3 ++- .../Consumers/ConsumerNodeService.cs | 3 +++ .../Producers/ProducerNodeProvider.cs | 26 +++++++++---------- .../Producers/ProducerNodeService.cs | 2 ++ 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs index 759f2e6..b8a9851 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs @@ -95,12 +95,18 @@ public Consumer Build() consumerNodeService.ConsumerConnected += ConsumerNodeService_ConsumerConnected; consumerNodeService.ConsumerDisconnected += ConsumerNodeService_ConsumerDisconnected; consumerNodeService.MessageInternalReceived += ConsumerNodeService_MessageInternalReceived; + consumerNodeService.AndyOrderedDisconnect += ConsumerNodeService_AndyOrderedDisconnect; isBuilt = true; return this as Consumer; } + private async void ConsumerNodeService_AndyOrderedDisconnect(string obj) + { + await consumerNodeService.DisconnectAsync(); + } + public async Task AcknowledgeMessage(string nodeId, long entryId, MessageAcknowledgement messageAcknowledgement) { await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs index 4a80f87..c4ad88c 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs @@ -420,11 +420,17 @@ public Producer Build() producerNodeService.ProducerConnected += ProducerNodeService_ProducerConnected; producerNodeService.ProducerDisconnected += ProducerNodeService_ProducerDisconnected; producerNodeService.MessageStored += ProducerNodeService_MessageStored; + producerNodeService.AndyOrderedDisconnect += ProducerNodeService_AndyOrderedDisconnect; isConnected = false; isBuilt = true; return this as Producer; } + + private async void ProducerNodeService_AndyOrderedDisconnect(string obj) + { + await producerNodeService.DisconnectAsync(); + } } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs index 3385973..05ca9f7 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs @@ -29,6 +29,7 @@ public ConsumerNodeProvider(XClientConfiguration xClientConfig, ConsumerConfigur return xClientConfig.HttpClientHandler; }; + option.Headers["x-andyx-tenant-authoriziation"] = xClientConfig.TenantToken; option.Headers["x-andyx-component-authoriziation"] = consumerConfig.ComponentToken; @@ -49,7 +50,7 @@ public ConsumerNodeProvider(XClientConfiguration xClientConfig, ConsumerConfigur .ConfigureLogging(factory => { factory.AddSystemdConsole(); - factory.AddFilter("Console", level => level >= LogLevel.Trace); + factory.AddFilter("Console", level => level >= LogLevel.Information); }) .Build(); } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeService.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeService.cs index 2d0e598..dba38a7 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeService.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeService.cs @@ -19,6 +19,7 @@ private class ConsumerNodeService public event Action ConsumerConnected; public event Action ConsumerDisconnected; public event Action MessageInternalReceived; + public event Action AndyOrderedDisconnect; public ConsumerNodeService(ConsumerNodeProvider consumerNodeProvider, XClientConfiguration xClientConfiguration) @@ -41,6 +42,8 @@ public ConsumerNodeService(ConsumerNodeProvider consumerNodeProvider, _connection.On("ConsumerConnected", connectedArgs => ConsumerConnected?.Invoke(connectedArgs)); _connection.On("ConsumerDisconnected", disconnected => ConsumerDisconnected?.Invoke(disconnected)); _connection.On("MessageSent", received => MessageInternalReceived?.Invoke(received)); + + _connection.On("AndyOrderedDisconnect", message => AndyOrderedDisconnect?.Invoke(message)); } private Task Connection_Reconnecting(Exception arg) diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs index bb70065..6b88765 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs @@ -23,21 +23,21 @@ public ProducerNodeProvider(XClientConfiguration xClientConfig, ProducerConfigur _connection = new HubConnectionBuilder() .WithUrl($"{xClientConfig.ServiceUrl}/realtime/v3/producer", option => { - //option.HttpMessageHandlerFactory = (message) => - //{ - // return xClientConfig.HttpClientHandler; - //}; - - option.HttpMessageHandlerFactory = (message) => { - if (message is HttpClientHandler clientHandler) - // always verify the SSL certificate - clientHandler.ServerCertificateCustomValidationCallback += - (sender, certificate, chain, sslPolicyErrors) => { return true; }; - return message; + return xClientConfig.HttpClientHandler; }; + + //option.HttpMessageHandlerFactory = (message) => + //{ + // if (message is HttpClientHandler clientHandler) + // // always verify the SSL certificate + // clientHandler.ServerCertificateCustomValidationCallback += + // (sender, certificate, chain, sslPolicyErrors) => { return true; }; + // return message; + //}; + option.Headers["x-andyx-tenant-authoriziation"] = xClientConfig.TenantToken; option.Headers["x-andyx-component-authoriziation"] = producerConfig.ComponentToken; @@ -53,8 +53,8 @@ public ProducerNodeProvider(XClientConfiguration xClientConfig, ProducerConfigur .AddMessagePackProtocol() .ConfigureLogging(factory => { - factory.AddConsole(); - factory.AddFilter("Console", level => level >= LogLevel.Trace); + factory.AddSystemdConsole(); + factory.AddFilter("Console", level => level >= LogLevel.Information); }) .Build(); } diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeService.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeService.cs index fe2172a..e2626dd 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeService.cs +++ b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeService.cs @@ -21,6 +21,7 @@ private class ProducerNodeService public event Action ProducerConnected; public event Action ProducerDisconnected; public event Action MessageStored; + public event Action AndyOrderedDisconnect; public ProducerNodeService(ProducerNodeProvider producerNodeProvider, XClientConfiguration xClientConfiguration) @@ -42,6 +43,7 @@ public ProducerNodeService(ProducerNodeProvider producerNodeProvider, _connection.On("ProducerConnected", connectedArgs => ProducerConnected?.Invoke(connectedArgs)); _connection.On("ProducerDisconnected", disconnected => ProducerDisconnected?.Invoke(disconnected)); _connection.On("MessageStored", received => MessageStored?.Invoke(received)); + _connection.On("AndyOrderedDisconnect", message => AndyOrderedDisconnect?.Invoke(message)); } private Task Connection_Reconnecting(Exception arg) From a09280afa382ce6d56ccb5ee42bb46e5cdd5b666 Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Thu, 20 Oct 2022 14:44:15 +0200 Subject: [PATCH 05/13] v3/feature/27 Implement v3 structure of the .net library Implement new Client for v3 of Andy X --- docs/General/1 Welcome.md | 36 -- docs/Tenants/1 Tenants.md | 17 - ...ient.Extensions.DependencyInjection.csproj | 48 +- ...ndyXClientDependencyInjectionExtensions.cs | 0 .../Assets/andyX.png | Bin 58471 -> 19321 bytes .../ConsumerDependencyInjectionExtensions.cs | 20 + .../ProducerDependencyInjectionExtensions.cs | 21 + .../Abstractions/Base/ConsumerBase.cs | 202 ++++++++ .../Abstractions/Base/ProducerBase.cs | 297 ++++++++++++ .../Abstractions/Consumers/IConsumer.cs | 21 + .../Consumers/IConsumerComponentConnection.cs | 8 + .../Consumers/IConsumerConfiguration.cs | 10 + .../Consumers/IConsumerConnection.cs | 7 + .../Consumers/IConsumerMessageHandler.cs | 9 + .../IConsumerSubscriptionConfiguration.cs | 10 + .../Consumers/IConsumerTopicConnection.cs | 7 + .../Abstractions/IXClientFactory.cs | 0 .../Abstractions/Producers/IProducer.cs | 15 + .../Producers/IProducerComponentConnection.cs | 8 + .../Producers/IProducerConfiguration.cs | 16 + .../Producers/IProducerConnection.cs | 7 + .../Producers/IProducerTopicConnection.cs | 7 + .../Abstractions/XClients/IXClient.cs | 9 + .../XClients/IXClientConfiguration.cs | 11 +- .../XClients/IXClientProductConnection.cs | 2 +- .../XClients/IXClientServiceConnection.cs | 6 +- .../XClients/IXClientTenantConnection.cs | 2 +- src/Andy.X.Client/Andy.X.Client.csproj | 25 +- src/Andy.X.Client/Assets/andyX.png | Bin 58471 -> 19321 bytes src/Andy.X.Client/Builders/ConsumerBuilder.cs | 21 + src/Andy.X.Client/Builders/ProducerBuilder.cs | 21 + .../Commands/AcknowledgeMessageArgs.cs | 24 + .../Commands/TransmitMessageArgs.cs | 40 ++ .../Configurations/ComponentConfiguration.cs | 14 + .../Configurations/ConsumerConfiguration.cs | 38 ++ .../Configurations/NodeConnectionType.cs | 8 +- .../Configurations/ProducerConfiguration.cs | 60 +++ .../Configurations/ProductConfiguration.cs | 13 + .../SubscriptionConfiguration.cs | 72 +++ .../Configurations/TenantConfiguration.cs | 15 + .../Configurations/TopicConfiguration.cs | 14 + .../Configurations/XClientConfiguration.cs | 76 +++ src/Andy.X.Client/Consumer.cs | 52 +++ .../Events/Consumers/ConsumerConnectedArgs.cs | 26 ++ .../Consumers/ConsumerDisconnectedArgs.cs | 23 + .../Consumers/MessageInternalReceivedArgs.cs | 28 ++ .../Events/Producers/MessageAcceptedArgs.cs | 18 + .../Events/Producers/ProducerConnectedArgs.cs | 22 + .../Producers/ProducerDisconnectedArgs.cs | 23 + .../Andy.X.Client/Factories/XClientFactory.cs | 2 +- src/Andy.X.Client/Models/Message.cs | 28 ++ src/Andy.X.Client/Models/MessageId.cs | 18 + src/Andy.X.Client/Producer.cs | 53 +++ .../Providers/ConsumerNodeProvider.cs | 84 ++++ .../Providers/ProducerNodeProvider.cs | 79 ++++ .../Services/ConsumerNodeService.cs | 106 +++++ .../Services/ProducerNodeService.cs | 116 +++++ .../Utilities/Helpers/AuthorizationHelpers.cs | 16 + src/Andy.X.Client/XClient.cs | 89 ++-- ...ient.Extensions.DependencyInjection.csproj | 103 ----- .../Assets/andyX.png | Bin 58471 -> 0 bytes .../Assets/license.md | 20 - .../ConsumerDependencyInjectionExtensions.cs | 20 - .../ProducerDependencyInjectionExtensions.cs | 20 - .../Client/IXClientConfiguration.cs | 14 - .../Client/IXClientProductConnection.cs | 7 - .../Client/IXClientServiceConnection.cs | 12 - .../Client/IXClientTenantConnection.cs | 8 - .../Abstractions/ConsumerBase.cs | 300 ------------ .../Consumers/IConsumerComponentConnection.cs | 8 - .../IConsumerInitialPositionConnection.cs | 9 - .../Consumers/IConsumerNameConnection.cs | 7 - .../Consumers/IConsumerOtherConfiguration.cs | 12 - .../IConsumerSubscriptionTypeConnection.cs | 9 - .../Consumers/IConsumerTopicConnection.cs | 8 - .../Andy.X.Client/Abstractions/IConsumer.cs | 6 - .../Andy.X.Client/Abstractions/IProducer.cs | 6 - .../Abstractions/ProducerBase.cs | 436 ------------------ .../Producers/IProducerComponentConnection.cs | 8 - .../Producers/IProducerNameConnection.cs | 7 - .../Producers/IProducerOtherConfiguration.cs | 18 - .../Producers/IProducerTopicConnection.cs | 8 - .../Andy.X.Client/Andy.X.Client.csproj | 107 ----- .../Andy.X.Client/Assets/andyX.png | Bin 58471 -> 0 bytes .../Andy.X.Client/Assets/license.md | 20 - .../Andy.X.Client/Builders/ConsumerBuilder.cs | 21 - .../Andy.X.Client/Builders/ProducerBuilder.cs | 21 - .../Configurations/ConsumerConfiguration.cs | 98 ---- .../Configurations/InitialPosition.cs | 8 - .../Configurations/NodeConnectionType.cs | 8 - .../Configurations/ProducerConfiguration.cs | 49 -- .../Configurations/XClientConfiguration.cs | 58 --- .../Andy.X.Client/Consumer.cs | 42 -- .../Consumers/AcknowledgeMessageArgs.cs | 27 -- .../Events/Consumers/ConsumerConnectedArgs.cs | 29 -- .../Consumers/ConsumerDisconnectedArgs.cs | 26 -- .../Consumers/MessageInternalReceivedArgs.cs | 40 -- .../Events/Consumers/MessageReceivedArgs.cs | 59 --- .../Events/Producers/MessageStoredArgs.cs | 14 - .../Events/Producers/ProducerConnectedArgs.cs | 26 -- .../Producers/ProducerDisconnectedArgs.cs | 26 -- .../Events/Producers/TransmitMessageArgs.cs | 44 -- .../Andy.X.Client/Extensions/Json.cs | 77 ---- .../Andy.X.Client/Producer.cs | 40 -- .../Consumers/ConsumerNodeProvider.cs | 63 --- .../Consumers/ConsumerNodeService.cs | 101 ---- .../Producers/ProducerNodeProvider.cs | 69 --- .../Producers/ProducerNodeService.cs | 112 ----- .../Andy.X.Client/XClient.cs | 142 ------ .../andy-x-client-lib/andy-x-client-lib.sln | 31 -- 110 files changed, 1906 insertions(+), 2518 deletions(-) delete mode 100644 docs/General/1 Welcome.md delete mode 100644 docs/Tenants/1 Tenants.md rename src/{net/andy-x-client-lib => }/Andy.X.Client.Extensions.DependencyInjection/AndyXClientDependencyInjectionExtensions.cs (100%) create mode 100644 src/Andy.X.Client.Extensions.DependencyInjection/ConsumerDependencyInjectionExtensions.cs create mode 100644 src/Andy.X.Client.Extensions.DependencyInjection/ProducerDependencyInjectionExtensions.cs create mode 100644 src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs create mode 100644 src/Andy.X.Client/Abstractions/Base/ProducerBase.cs create mode 100644 src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs create mode 100644 src/Andy.X.Client/Abstractions/Consumers/IConsumerComponentConnection.cs create mode 100644 src/Andy.X.Client/Abstractions/Consumers/IConsumerConfiguration.cs create mode 100644 src/Andy.X.Client/Abstractions/Consumers/IConsumerConnection.cs create mode 100644 src/Andy.X.Client/Abstractions/Consumers/IConsumerMessageHandler.cs create mode 100644 src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs create mode 100644 src/Andy.X.Client/Abstractions/Consumers/IConsumerTopicConnection.cs rename src/{net/andy-x-client-lib => }/Andy.X.Client/Abstractions/IXClientFactory.cs (100%) create mode 100644 src/Andy.X.Client/Abstractions/Producers/IProducer.cs create mode 100644 src/Andy.X.Client/Abstractions/Producers/IProducerComponentConnection.cs create mode 100644 src/Andy.X.Client/Abstractions/Producers/IProducerConfiguration.cs create mode 100644 src/Andy.X.Client/Abstractions/Producers/IProducerConnection.cs create mode 100644 src/Andy.X.Client/Abstractions/Producers/IProducerTopicConnection.cs create mode 100644 src/Andy.X.Client/Abstractions/XClients/IXClient.cs create mode 100644 src/Andy.X.Client/Builders/ConsumerBuilder.cs create mode 100644 src/Andy.X.Client/Builders/ProducerBuilder.cs create mode 100644 src/Andy.X.Client/Commands/AcknowledgeMessageArgs.cs create mode 100644 src/Andy.X.Client/Commands/TransmitMessageArgs.cs create mode 100644 src/Andy.X.Client/Configurations/ComponentConfiguration.cs create mode 100644 src/Andy.X.Client/Configurations/ConsumerConfiguration.cs create mode 100644 src/Andy.X.Client/Configurations/ProducerConfiguration.cs create mode 100644 src/Andy.X.Client/Configurations/ProductConfiguration.cs create mode 100644 src/Andy.X.Client/Configurations/SubscriptionConfiguration.cs create mode 100644 src/Andy.X.Client/Configurations/TenantConfiguration.cs create mode 100644 src/Andy.X.Client/Configurations/TopicConfiguration.cs create mode 100644 src/Andy.X.Client/Configurations/XClientConfiguration.cs create mode 100644 src/Andy.X.Client/Consumer.cs create mode 100644 src/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs create mode 100644 src/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs create mode 100644 src/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs create mode 100644 src/Andy.X.Client/Events/Producers/MessageAcceptedArgs.cs create mode 100644 src/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs create mode 100644 src/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs rename src/{net/andy-x-client-lib => }/Andy.X.Client/Factories/XClientFactory.cs (87%) create mode 100644 src/Andy.X.Client/Models/Message.cs create mode 100644 src/Andy.X.Client/Models/MessageId.cs create mode 100644 src/Andy.X.Client/Producer.cs create mode 100644 src/Andy.X.Client/Providers/ConsumerNodeProvider.cs create mode 100644 src/Andy.X.Client/Providers/ProducerNodeProvider.cs create mode 100644 src/Andy.X.Client/Services/ConsumerNodeService.cs create mode 100644 src/Andy.X.Client/Services/ProducerNodeService.cs create mode 100644 src/Andy.X.Client/Utilities/Helpers/AuthorizationHelpers.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Assets/andyX.png delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Assets/license.md delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/ConsumerDependencyInjectionExtensions.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/ProducerDependencyInjectionExtensions.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientConfiguration.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientProductConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientServiceConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientTenantConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerComponentConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerInitialPositionConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerNameConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerOtherConfiguration.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionTypeConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerTopicConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/IConsumer.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/IProducer.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerComponentConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerNameConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerOtherConfiguration.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerTopicConnection.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Andy.X.Client.csproj delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Assets/andyX.png delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Assets/license.md delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Builders/ConsumerBuilder.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Builders/ProducerBuilder.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Configurations/ConsumerConfiguration.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Configurations/InitialPosition.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Configurations/NodeConnectionType.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Configurations/ProducerConfiguration.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Configurations/XClientConfiguration.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Consumer.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/MessageStoredArgs.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/TransmitMessageArgs.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Extensions/Json.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Producer.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeService.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeService.cs delete mode 100644 src/net/andy-x-client-lib/Andy.X.Client/XClient.cs delete mode 100644 src/net/andy-x-client-lib/andy-x-client-lib.sln diff --git a/docs/General/1 Welcome.md b/docs/General/1 Welcome.md deleted file mode 100644 index e025825..0000000 --- a/docs/General/1 Welcome.md +++ /dev/null @@ -1,36 +0,0 @@ -Welcome to Buildersoft Andy X Client library -============ -Andy X is an open-source distributed streaming platform designed to deliver the best performance possible for high-performance data pipelines, streaming analytics, streaming between microservices and data integrations. -This library offers flexible messaging models with high-level APIs for NET. - -## Get Started - -Follow the [Getting Started](https://andyx.azurewebsites.net/) instructions how to run Andy X. - -For local development and testing, you can run Andy X within a Docker container, for more info click [here](https://hub.docker.com/u/buildersoftdev) - -## How to Engage, Contribute, and Give Feedback - -Some of the best ways to contribute are to try things out, file issues, join in design conversations, -and make pull-requests. - -## Reporting security issues and bugs - -Security issues and bugs should be reported privately, via email, en.buildersoft@gmail.com. You should receive a response within 24 hours. - -## Related projects - -These are some other repos for related projects: - -* [Andy X Dashboard](https://github.com/buildersoftdev/andyxdashboard) - Dashboard for Andy X Node -* [Andy X Terminal](https://github.com/buildersoftdev/andyxterminal) - Manage all resources of Andy X - -## Deploying Andy X with docker-compose - -Andy X can be easily deployed on a docker container using docker-compose, for more info click [here](https://hub.docker.com/r/buildersoftdev/andyx) - -## Code of conduct - -This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. - -For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). \ No newline at end of file diff --git a/docs/Tenants/1 Tenants.md b/docs/Tenants/1 Tenants.md deleted file mode 100644 index fa02d7b..0000000 --- a/docs/Tenants/1 Tenants.md +++ /dev/null @@ -1,17 +0,0 @@ -Tenants -============ - -## What's is a Tenant? - - -## How to configure a Tenant - - -## How to Isolate a Tenant - - -## How to deploy a Tenant - - -## What's is multitenancy? - diff --git a/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj b/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj index 8f839b3..6747091 100644 --- a/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj +++ b/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj @@ -1,7 +1,53 @@  - net6.0;net5;netcoreapp3.1;netstandard2.1;netstandard2.0 + net6.0;net5;netcoreapp3.1;netstandard2.1 + 3.0.0 + 3.0.0 + Buildersoft Andy X + Buildersoft + Buildersoft + Copyright © Buildersoft 2022 + + + Andy X Client extensions for ASP.NET Core + + + https://github.com/buildersoftdev/andyxclient + andyx eda streaming distributed + + 3.0.0 + license.md + andyX.png + Andy.X.Client.Extensions.Microsoft.DependencyInjection + True + True + True + + https://buildersoftdev.azurewebsites.net/andyx + + + + + + + + + + + True + + + + True + + + + + + + + diff --git a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/AndyXClientDependencyInjectionExtensions.cs b/src/Andy.X.Client.Extensions.DependencyInjection/AndyXClientDependencyInjectionExtensions.cs similarity index 100% rename from src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/AndyXClientDependencyInjectionExtensions.cs rename to src/Andy.X.Client.Extensions.DependencyInjection/AndyXClientDependencyInjectionExtensions.cs diff --git a/src/Andy.X.Client.Extensions.DependencyInjection/Assets/andyX.png b/src/Andy.X.Client.Extensions.DependencyInjection/Assets/andyX.png index 0411c961ec3e156c621bea6b4e9e28bcac07c439..0f885bab162fce07d59dbc115eb4c3bbce774463 100644 GIT binary patch literal 19321 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 literal 58471 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 I*+2RJ01TmIVgLXD diff --git a/src/Andy.X.Client.Extensions.DependencyInjection/ConsumerDependencyInjectionExtensions.cs b/src/Andy.X.Client.Extensions.DependencyInjection/ConsumerDependencyInjectionExtensions.cs new file mode 100644 index 0000000..0cf639f --- /dev/null +++ b/src/Andy.X.Client.Extensions.DependencyInjection/ConsumerDependencyInjectionExtensions.cs @@ -0,0 +1,20 @@ +using Andy.X.Client.Builders; +using Andy.X.Client.Configurations; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Andy.X.Client.Extensions.DependencyInjection +{ + public static class ConsumerDependencyInjectionExtensions + { + public static IServiceCollection AddConsumerBuilder(this IServiceCollection services, Action configuration) + { + ConsumerConfiguration ConsumerConfiguration = new ConsumerConfiguration(); + configuration.Invoke(ConsumerConfiguration); + + var builder = new ConsumerBuilder(ConsumerConfiguration); + + return services.AddSingleton>(builder); + } + } +} diff --git a/src/Andy.X.Client.Extensions.DependencyInjection/ProducerDependencyInjectionExtensions.cs b/src/Andy.X.Client.Extensions.DependencyInjection/ProducerDependencyInjectionExtensions.cs new file mode 100644 index 0000000..01860a8 --- /dev/null +++ b/src/Andy.X.Client.Extensions.DependencyInjection/ProducerDependencyInjectionExtensions.cs @@ -0,0 +1,21 @@ +using Andy.X.Client.Builders; +using Andy.X.Client.Configurations; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Andy.X.Client.Extensions.DependencyInjection +{ + public static class ProducerDependencyInjectionExtensions + { + public static IServiceCollection AddProducerBuilder(this IServiceCollection services, Action configuration) + { + ProducerConfiguration producerConfiguration = new ProducerConfiguration(); + configuration.Invoke(producerConfiguration); + + var builder = new ProducerBuilder(producerConfiguration); + + + return services.AddSingleton>(builder); + } + } +} diff --git a/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs b/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs new file mode 100644 index 0000000..eaa7f26 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs @@ -0,0 +1,202 @@ +using Andy.X.Client.Abstractions.Consumers; +using Andy.X.Client.Abstractions.XClients; +using Andy.X.Client.Builders; +using Andy.X.Client.Commands; +using Andy.X.Client.Configurations; +using Andy.X.Client.Models; +using Andy.X.Client.Providers; +using Andy.X.Client.Services; +using MessagePack; +using Microsoft.Extensions.Logging; +using System; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace Andy.X.Client.Abstractions.Base +{ + public abstract class ConsumerBase : IConsumer + { + + private Action> _clientAction; + + private readonly ILogger _logger; + private readonly IXClient _xClient; + private readonly ConsumerConfiguration _consumerConfiguration; + + private ConsumerNodeProvider consumerNodeProvider; + private ConsumerNodeService consumerNodeService; + private bool isBuilt = false; + + public ConsumerBase(IXClient xClient) : this(xClient, new ConsumerConfiguration()) + { + // calls last constructor + } + + public ConsumerBase(IXClientFactory xClientFactory) : this(xClientFactory.CreateClient(), new ConsumerConfiguration()) + { + // calls last constructor + } + + public ConsumerBase(IXClientFactory xClientFactory, ConsumerConfiguration consumerConfiguration) : this(xClientFactory.CreateClient(), consumerConfiguration) + { + // calls last constructor + } + + public ConsumerBase(IXClientFactory xClientFactory, ConsumerBuilder consumerBuilder) : this(xClientFactory.CreateClient(), consumerBuilder.ConsumerConfiguration) + { + // calls last constructor + } + + public ConsumerBase(IXClient xClient, ConsumerConfiguration consumerConfiguration) + { + _xClient = xClient; + + _consumerConfiguration = consumerConfiguration; + _logger = xClient + .GetClientConfiguration().Settings.Logging + .GetLoggerFactory() + .CreateLogger(typeof(Consumer)); + } + + + public IConsumerConfiguration AndSubscription(Action config) + { + config.Invoke(_consumerConfiguration.Subscription); + + return this; + } + + public IConsumerConnection AndTopic(string topic, string description = "") + { + _consumerConfiguration.Topic.Name = topic; + _consumerConfiguration.Topic.Description = description; + + return this; + } + + public IConsumerTopicConnection ForComponent(string component) + { + _consumerConfiguration.Component.Name = component; + + return this; + } + + public IConsumerTopicConnection ForComponent(string component, string key, string secret) + { + _consumerConfiguration.Component.Name = component; + _consumerConfiguration.Component.Key = key; + _consumerConfiguration.Component.Secret = secret; + + return this; + } + + public IConsumerSubscriptionConfiguration WithName(string name) + { + _consumerConfiguration.Name = name; + + return this; + } + + public IConsumerConfiguration WithSettings(Action settings) + { + settings.Invoke(_consumerConfiguration.Settings); + + return this; + } + + + public async Task SubscribeAsync() + { + if (isBuilt != true) + throw new Exception("Consumer should be built before subscribing the connection"); + + await consumerNodeService.ConnectAsync(); + } + + public async Task CloseAsync() + { + if (isBuilt != true) + throw new Exception("Consumer should be built before closing the connection"); + + await consumerNodeService.DisconnectAsync(); + } + + public Consumer Build() + { + consumerNodeProvider = new ConsumerNodeProvider(_xClient.GetClientConfiguration(), _consumerConfiguration); + consumerNodeService = new ConsumerNodeService(consumerNodeProvider, _xClient.GetClientConfiguration()); + + consumerNodeService.ConsumerConnected += ConsumerNodeService_ConsumerConnected; + consumerNodeService.ConsumerDisconnected += ConsumerNodeService_ConsumerDisconnected; + consumerNodeService.AndyOrderedDisconnect += ConsumerNodeService_AndyOrderedDisconnect; + + consumerNodeService.MessageInternalReceived += ConsumerNodeService_MessageInternalReceived; + + isBuilt = true; + + return this as Consumer; + } + + public IConsumerMessageHandler MessageReceivedHandler(Action> messageReceived) + { + _clientAction = messageReceived; + + return this; + } + + public async Task MessageAcknowledgement(MessageAcknowledgement messageAcknowledgement, Message message) + { + await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() + { + EntryId = message.EntryId, + NodeId = message.NodeId, + Acknowledgement = (int)messageAcknowledgement + }); + } + + public Task AcknowledgeMessage(Message message) + { + return MessageAcknowledgement(Commands.MessageAcknowledgement.Acknowledged, message); + } + public Task SkipMessage(Message message) + { + return MessageAcknowledgement(Commands.MessageAcknowledgement.Skipped, message); + } + public Task UnacknowledgeMessage(Message message) + { + return MessageAcknowledgement(Commands.MessageAcknowledgement.Unacknowledged, message); + } + + private void ConsumerNodeService_MessageInternalReceived(Events.Consumers.MessageInternalReceivedArgs obj) + { + K keyParsed = MessagePackSerializer.Deserialize(obj.MessageId, MessagePack.Resolvers.ContractlessStandardResolver.Options); + V valueParsed = MessagePackSerializer.Deserialize(obj.Payload, MessagePack.Resolvers.ContractlessStandardResolver.Options); + + _clientAction?.Invoke(keyParsed, + new Message( + entryId: obj.EntryId, + nodeId: obj.NodeId, + headers: obj.Headers, + payload: valueParsed, + sentDate: obj.SentDate, + receivedDate: DateTimeOffset.UtcNow)); + } + + private async void ConsumerNodeService_AndyOrderedDisconnect(string obj) + { + _logger.LogError($"Consumer is disconnected with request from the server, error details: {obj}"); + + await CloseAsync(); + } + + private void ConsumerNodeService_ConsumerDisconnected(Events.Consumers.ConsumerDisconnectedArgs obj) + { + _logger.LogWarning($"andyx-client | Consumer '{obj.ConsumerName}|{obj.Id}' is disconnected"); + } + + private void ConsumerNodeService_ConsumerConnected(Events.Consumers.ConsumerConnectedArgs obj) + { + _logger.LogWarning($"andyx-client | Consumer '{obj.ConsumerName}|{obj.Id}' is connected"); + } + } +} diff --git a/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs b/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs new file mode 100644 index 0000000..107bfe4 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs @@ -0,0 +1,297 @@ +using Andy.X.Client.Abstractions.Producers; +using Andy.X.Client.Abstractions.XClients; +using Andy.X.Client.Builders; +using Andy.X.Client.Commands; +using Andy.X.Client.Configurations; +using Andy.X.Client.Models; +using Andy.X.Client.Providers; +using Andy.X.Client.Services; +using MessagePack; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Andy.X.Client.Abstractions.Base +{ + public abstract class ProducerBase : IProducer + { + private readonly IXClient _xClient; + private readonly ProducerConfiguration _producerConfiguration; + private readonly ILogger _logger; + + private readonly IDictionary _headers; + + // Connection with Andy X Server + private ProducerNodeService producerNodeService; + private ProducerNodeProvider producerNodeProvider; + private ConcurrentDictionary callBackResponses; + + private bool isBuilt = false; + + #region Constuctors + public ProducerBase(IXClient xClient) : this(xClient, new ProducerConfiguration()) + { + } + + public ProducerBase(IXClientFactory xClientFactory) : this(xClientFactory.CreateClient(), new ProducerConfiguration()) + { + } + + public ProducerBase(IXClientFactory xClientFactory, ProducerConfiguration producerConfiguration) : this(xClientFactory.CreateClient(), producerConfiguration) + { + } + + public ProducerBase(IXClientFactory xClientFactory, ProducerBuilder producerBuilder) : this(xClientFactory.CreateClient(), producerBuilder.ProducerConfiguration) + { + } + + public ProducerBase(IXClient xClient, ProducerConfiguration producerConfiguration) + { + _xClient = xClient; + _producerConfiguration = producerConfiguration; + + _logger = _xClient.GetClientConfiguration() + .Settings.Logging + .GetLoggerFactory() + .CreateLogger(typeof(Producer)); + + _headers = new Dictionary(); + } + #endregion + public IProducerTopicConnection ForComponent(string component) + { + _producerConfiguration.Component.Name = component; + + return this; + } + + public IProducerTopicConnection ForComponent(string component, string key, string secret) + { + _producerConfiguration.Component.Name = component; + _producerConfiguration.Component.Key = key; + _producerConfiguration.Component.Secret = secret; + + return this; + } + + public IProducerConfiguration AddDefaultHeader(string key, string value) + { + _headers.TryAdd(key, value); + + return this; + } + + public IProducerConfiguration AddDefaultHeaders(IDictionary headers) + { + foreach (var header in headers) + { + _headers.TryAdd(header.Key, header.Value); + } + + return this; + } + + public IProducerConnection AndTopic(string topic, string description = "") + { + _producerConfiguration.Topic.Name = topic; + _producerConfiguration.Topic.Description = description; + + return this; + } + + public Producer Build() + { + + if (_producerConfiguration.Settings.RequireCallback == true) + callBackResponses = new ConcurrentDictionary(); + + _headers.Add("andyx-client", "Andy X Client for .NET"); + _headers.Add("andyx-client-version", "v3.0.0-alpha10"); + _headers.Add("andyx-producer-name", _producerConfiguration.Name); + _headers.Add("andyx-content-type", "application/andyxbinary+json"); + + + producerNodeProvider = new ProducerNodeProvider(_xClient.GetClientConfiguration(), _producerConfiguration); + producerNodeService = new ProducerNodeService(producerNodeProvider, _xClient.GetClientConfiguration()); + + producerNodeService.ProducerConnected += ProducerNodeService_ProducerConnected; + producerNodeService.ProducerDisconnected += ProducerNodeService_ProducerDisconnected; + producerNodeService.MessageAccepted += ProducerNodeService_MessageAccepted; + producerNodeService.AndyOrderedDisconnect += ProducerNodeService_AndyOrderedDisconnect; + + isBuilt = true; + + return this as Producer; + } + + private async void ProducerNodeService_AndyOrderedDisconnect(string obj) + { + _logger.LogError($"Producer is disconnected with request from the server, error details: {obj}"); await CloseAsync(); + } + + private void ProducerNodeService_MessageAccepted(Events.Producers.MessageAcceptedArgs obj) + { + callBackResponses + .TryAdd(obj.IdentityId, new MessageId(obj.IdentityId, obj.MessageCount, obj.AcceptedDate)); + } + + private void ProducerNodeService_ProducerDisconnected(Events.Producers.ProducerDisconnectedArgs obj) + { + _logger.LogWarning($"andyx-client | Producer '{obj.ProducerName}|{obj.Id}' is disconnected"); + } + + private void ProducerNodeService_ProducerConnected(Events.Producers.ProducerConnectedArgs obj) + { + _logger.LogWarning($"andyx-client | Producer '{obj.ProducerName}|{obj.Id}' is connected"); + } + + public async Task CloseAsync() + { + if (isBuilt != true) + throw new Exception("Producer should be built before closing the connection"); + + await producerNodeService.DisconnectAsync(); + } + + + public async Task OpenAsync() + { + if (isBuilt != true) + throw new Exception("Producer should be built before connecting the connection"); + + await producerNodeService.ConnectAsync(); + } + + public async Task SendAsync(K id, V message, IDictionary headers = null, string sendNodeId = "") + { + headers = TryAddHeaders(headers); + + var identityId = Guid.NewGuid(); + var result = new MessageId(identityId, -1, DateTimeOffset.UtcNow); + + if (producerNodeService.GetConnectionState() == HubConnectionState.Connected) + { + await producerNodeService.TransmitMessage(new TransmitMessageArgs() + { + Tenant = _xClient.GetClientConfiguration().Tenant.Name, + Product = _xClient.GetClientConfiguration().Product.Name, + Component = _producerConfiguration.Component.Name, + Topic = _producerConfiguration.Topic.Name, + + IdentityId = identityId, + Headers = headers as Dictionary, + + Id = MessagePackSerializer.Serialize(id, MessagePack.Resolvers.ContractlessStandardResolver.Options), + Payload = MessagePackSerializer.Serialize(message, MessagePack.Resolvers.ContractlessStandardResolver.Options), + SentDate = DateTimeOffset.UtcNow, + + NodeId = sendNodeId, + RequiresCallback = _producerConfiguration.Settings.RequireCallback + }); + } + else + { + if (_producerConfiguration.Settings.BreakIfTryToSendMessageInClosedConneciton == true) + throw new Exception($"Connection with Andy X is not establised, message is not produced, connectionState: {producerNodeService.GetConnectionState()}. To ignore and not to throw an exception, update Producer Settings 'BreakIfTryToSendMessageInClosedConneciton' to false"); + } + + return CheckForCallbackResponse(identityId, result); + } + + public async Task SendAsync(IDictionary messages, IDictionary headers = null, string sendNodeId = "") + { + headers = TryAddHeaders(headers); + var messagesArgs = new List(); + + var identityId = Guid.NewGuid(); + var result = new MessageId(identityId, -1, DateTimeOffset.UtcNow); + + + if (producerNodeService.GetConnectionState() == HubConnectionState.Connected) + { + foreach (var message in messages) + { + messagesArgs.Add(new TransmitMessageArgs() + { + Tenant = _xClient.GetClientConfiguration().Tenant.Name, + Product = _xClient.GetClientConfiguration().Product.Name, + Component = _producerConfiguration.Component.Name, + Topic = _producerConfiguration.Topic.Name, + + IdentityId = identityId, + Headers = headers as Dictionary, + + Id = MessagePackSerializer.Serialize(message.Key, MessagePack.Resolvers.ContractlessStandardResolver.Options), + Payload = MessagePackSerializer.Serialize(message.Value, MessagePack.Resolvers.ContractlessStandardResolver.Options), + SentDate = DateTimeOffset.UtcNow, + + NodeId = sendNodeId, + RequiresCallback = _producerConfiguration.Settings.RequireCallback + }); + } + + await producerNodeService.TransmitMessages(messagesArgs); + } + else + { + if (_producerConfiguration.Settings.BreakIfTryToSendMessageInClosedConneciton == true) + throw new Exception($"Connection with Andy X is not establised, messages are not produced, connectionState: {producerNodeService.GetConnectionState()}. To ignore and not to throw an exception, update Producer Settings 'BreakIfTryToSendMessageInClosedConneciton' to false"); + } + + return CheckForCallbackResponse(identityId, result); + } + + public IProducerConfiguration WithName(string name) + { + _producerConfiguration.Name = name; + + return this; + } + + public IProducerConfiguration WithSettings(Action settings) + { + settings.Invoke(_producerConfiguration.Settings); + + return this; + } + + private IDictionary TryAddHeaders(IDictionary headers) + { + if (headers == null) + { + return _headers; + } + + foreach (var defaultHeader in _headers) + { + headers.Add(defaultHeader.Key, defaultHeader.Value); + } + + return headers; + } + + private MessageId CheckForCallbackResponse(Guid identityId, MessageId result) + { + if (_producerConfiguration.Settings.RequireCallback == true) + { + int milisecondsWait = 0; + int sleepTime = 10; + while (callBackResponses.ContainsKey(identityId) != true) + { + Thread.Sleep(sleepTime); + milisecondsWait += sleepTime; + if (milisecondsWait >= _producerConfiguration.Settings.TimeoutInSyncResponseMs) + throw new Exception($"Run in timeout, couldnot get response from Andy X. TimeoutInSyncResponseMs: {_producerConfiguration.Settings.TimeoutInSyncResponseMs}. You can increase TimeoutInSyncResponseMs at Producer Settings."); + + } + callBackResponses.TryRemove(identityId, out result); + } + + return result; + } + } +} diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs new file mode 100644 index 0000000..52b5aa5 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs @@ -0,0 +1,21 @@ +using Andy.X.Client.Commands; +using Andy.X.Client.Models; +using System; +using System.Threading.Tasks; + +namespace Andy.X.Client.Abstractions.Consumers +{ + public interface IConsumer : IConsumerComponentConnection, IConsumerConfiguration, IConsumerConnection, IConsumerSubscriptionConfiguration, IConsumerTopicConnection, IConsumerMessageHandler + { + IConsumerMessageHandler MessageReceivedHandler(Action> messageReceived); + + Task AcknowledgeMessage(Message message); + Task UnacknowledgeMessage(Message message); + Task SkipMessage(Message message); + + Task MessageAcknowledgement(MessageAcknowledgement messageAcknowledgement, Message message); + + Task SubscribeAsync(); + Task CloseAsync(); + } +} diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerComponentConnection.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerComponentConnection.cs new file mode 100644 index 0000000..e4ba9b9 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumerComponentConnection.cs @@ -0,0 +1,8 @@ +namespace Andy.X.Client.Abstractions.Consumers +{ + public interface IConsumerComponentConnection + { + IConsumerTopicConnection ForComponent(string component); + IConsumerTopicConnection ForComponent(string component, string key, string secret); + } +} diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerConfiguration.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerConfiguration.cs new file mode 100644 index 0000000..92df971 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumerConfiguration.cs @@ -0,0 +1,10 @@ +namespace Andy.X.Client.Abstractions.Consumers +{ + public interface IConsumerConfiguration + { + // we do not have settings for consumers for now. + //IConsumerConfiguration WithSettings(Action settings); + + Consumer Build(); + } +} \ No newline at end of file diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerConnection.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerConnection.cs new file mode 100644 index 0000000..82f69b3 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumerConnection.cs @@ -0,0 +1,7 @@ +namespace Andy.X.Client.Abstractions.Consumers +{ + public interface IConsumerConnection + { + IConsumerSubscriptionConfiguration WithName(string name); + } +} \ No newline at end of file diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerMessageHandler.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerMessageHandler.cs new file mode 100644 index 0000000..5c39057 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumerMessageHandler.cs @@ -0,0 +1,9 @@ +using Andy.X.Client.Models; + +namespace Andy.X.Client.Abstractions.Consumers +{ + public interface IConsumerMessageHandler + { + Consumer Build(); + } +} diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs new file mode 100644 index 0000000..eb7a534 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs @@ -0,0 +1,10 @@ +using Andy.X.Client.Configurations; +using System; + +namespace Andy.X.Client.Abstractions.Consumers +{ + public interface IConsumerSubscriptionConfiguration + { + IConsumerConfiguration AndSubscription(Action config); + } +} diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerTopicConnection.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerTopicConnection.cs new file mode 100644 index 0000000..0b294dc --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumerTopicConnection.cs @@ -0,0 +1,7 @@ +namespace Andy.X.Client.Abstractions.Consumers +{ + public interface IConsumerTopicConnection + { + IConsumerConnection AndTopic(string topic, string description = ""); + } +} \ No newline at end of file diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/IXClientFactory.cs b/src/Andy.X.Client/Abstractions/IXClientFactory.cs similarity index 100% rename from src/net/andy-x-client-lib/Andy.X.Client/Abstractions/IXClientFactory.cs rename to src/Andy.X.Client/Abstractions/IXClientFactory.cs diff --git a/src/Andy.X.Client/Abstractions/Producers/IProducer.cs b/src/Andy.X.Client/Abstractions/Producers/IProducer.cs new file mode 100644 index 0000000..adf70a9 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Producers/IProducer.cs @@ -0,0 +1,15 @@ +using Andy.X.Client.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Andy.X.Client.Abstractions.Producers +{ + public interface IProducer : IProducerConfiguration, IProducerComponentConnection, IProducerTopicConnection, IProducerConnection + { + Task SendAsync(K id, V message, IDictionary headers = null, string sendNodeId = ""); + Task SendAsync(IDictionary messages, IDictionary headers = null, string sendNodeId = ""); + + Task OpenAsync(); + Task CloseAsync(); + } +} diff --git a/src/Andy.X.Client/Abstractions/Producers/IProducerComponentConnection.cs b/src/Andy.X.Client/Abstractions/Producers/IProducerComponentConnection.cs new file mode 100644 index 0000000..befcf1e --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Producers/IProducerComponentConnection.cs @@ -0,0 +1,8 @@ +namespace Andy.X.Client.Abstractions.Producers +{ + public interface IProducerComponentConnection + { + IProducerTopicConnection ForComponent(string component); + IProducerTopicConnection ForComponent(string component, string key, string secret); + } +} diff --git a/src/Andy.X.Client/Abstractions/Producers/IProducerConfiguration.cs b/src/Andy.X.Client/Abstractions/Producers/IProducerConfiguration.cs new file mode 100644 index 0000000..178274c --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Producers/IProducerConfiguration.cs @@ -0,0 +1,16 @@ +using Andy.X.Client.Configurations; +using System; +using System.Collections.Generic; + +namespace Andy.X.Client.Abstractions.Producers +{ + public interface IProducerConfiguration + { + IProducerConfiguration AddDefaultHeader(string key, string value); + IProducerConfiguration AddDefaultHeaders(IDictionary headers); + + IProducerConfiguration WithSettings(Action settings); + + Producer Build(); + } +} \ No newline at end of file diff --git a/src/Andy.X.Client/Abstractions/Producers/IProducerConnection.cs b/src/Andy.X.Client/Abstractions/Producers/IProducerConnection.cs new file mode 100644 index 0000000..f0840ce --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Producers/IProducerConnection.cs @@ -0,0 +1,7 @@ +namespace Andy.X.Client.Abstractions.Producers +{ + public interface IProducerConnection + { + IProducerConfiguration WithName(string name); + } +} \ No newline at end of file diff --git a/src/Andy.X.Client/Abstractions/Producers/IProducerTopicConnection.cs b/src/Andy.X.Client/Abstractions/Producers/IProducerTopicConnection.cs new file mode 100644 index 0000000..7169b37 --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Producers/IProducerTopicConnection.cs @@ -0,0 +1,7 @@ +namespace Andy.X.Client.Abstractions.Producers +{ + public interface IProducerTopicConnection + { + IProducerConnection AndTopic(string topic, string description = ""); + } +} \ No newline at end of file diff --git a/src/Andy.X.Client/Abstractions/XClients/IXClient.cs b/src/Andy.X.Client/Abstractions/XClients/IXClient.cs new file mode 100644 index 0000000..3dce80f --- /dev/null +++ b/src/Andy.X.Client/Abstractions/XClients/IXClient.cs @@ -0,0 +1,9 @@ +using Andy.X.Client.Configurations; + +namespace Andy.X.Client.Abstractions.XClients +{ + public interface IXClient : IXClientServiceConnection, IXClientTenantConnection, IXClientProductConnection, IXClientConfiguration + { + XClientConfiguration GetClientConfiguration(); + } +} diff --git a/src/Andy.X.Client/Abstractions/XClients/IXClientConfiguration.cs b/src/Andy.X.Client/Abstractions/XClients/IXClientConfiguration.cs index eb3edd0..b237646 100644 --- a/src/Andy.X.Client/Abstractions/XClients/IXClientConfiguration.cs +++ b/src/Andy.X.Client/Abstractions/XClients/IXClientConfiguration.cs @@ -1,14 +1,11 @@ -using System.Net.Http; -using System; -using Microsoft.Extensions.Logging; +using System; +using Andy.X.Client.Configurations; namespace Andy.X.Client.Abstractions.XClients { - internal interface IXClientConfiguration + public interface IXClientConfiguration { - IXClientConfiguration AddLoggingSupport(ILoggerFactory loggerFactory); - IXClientConfiguration WithHttpClientHandler(Action httpHandler); - + IXClientConfiguration WithSettings(Action settings); XClient Build(); } } diff --git a/src/Andy.X.Client/Abstractions/XClients/IXClientProductConnection.cs b/src/Andy.X.Client/Abstractions/XClients/IXClientProductConnection.cs index dcdfb0c..5c997e4 100644 --- a/src/Andy.X.Client/Abstractions/XClients/IXClientProductConnection.cs +++ b/src/Andy.X.Client/Abstractions/XClients/IXClientProductConnection.cs @@ -1,6 +1,6 @@ namespace Andy.X.Client.Abstractions.XClients { - internal interface IXClientProductConnection + public interface IXClientProductConnection { IXClientConfiguration AndProduct(string product); IXClientConfiguration AndProduct(string product, string key, string secret); diff --git a/src/Andy.X.Client/Abstractions/XClients/IXClientServiceConnection.cs b/src/Andy.X.Client/Abstractions/XClients/IXClientServiceConnection.cs index d614f4f..6fce758 100644 --- a/src/Andy.X.Client/Abstractions/XClients/IXClientServiceConnection.cs +++ b/src/Andy.X.Client/Abstractions/XClients/IXClientServiceConnection.cs @@ -1,11 +1,11 @@ using Andy.X.Client.Configurations; +using System; namespace Andy.X.Client.Abstractions.XClients { - internal interface IXClientServiceConnection + public interface IXClientServiceConnection { - IXClientTenantConnection ForService(string nodeUrl); - IXClientTenantConnection ForService(string nodeHostName, int hostPort); + IXClientTenantConnection ForService(Uri nodeUrl); IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType); IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType, bool isSSLCertsSkipped); } diff --git a/src/Andy.X.Client/Abstractions/XClients/IXClientTenantConnection.cs b/src/Andy.X.Client/Abstractions/XClients/IXClientTenantConnection.cs index 9acf8bb..2cc5909 100644 --- a/src/Andy.X.Client/Abstractions/XClients/IXClientTenantConnection.cs +++ b/src/Andy.X.Client/Abstractions/XClients/IXClientTenantConnection.cs @@ -1,6 +1,6 @@ namespace Andy.X.Client.Abstractions.XClients { - internal interface IXClientTenantConnection + public interface IXClientTenantConnection { IXClientProductConnection AndTenant(string tenant); IXClientProductConnection AndTenant(string tenant, string key, string secret); diff --git a/src/Andy.X.Client/Andy.X.Client.csproj b/src/Andy.X.Client/Andy.X.Client.csproj index e9db310..ee534da 100644 --- a/src/Andy.X.Client/Andy.X.Client.csproj +++ b/src/Andy.X.Client/Andy.X.Client.csproj @@ -1,7 +1,7 @@  - net6.0;net5;netcoreapp3.1;netstandard2.1;netstandard2.0 + net6.0;net5;netcoreapp3.1;netstandard2.1 3.0.0 3.0.0 @@ -9,6 +9,7 @@ Buildersoft Buildersoft Copyright © Buildersoft 2022 + Andy X is an open-source distributed streaming platform designed to deliver the best performance possible for high-performance data pipelines, streaming analytics, streaming between microservices and data integrations. This library offers flexible messaging models with high-level APIs for NET. @@ -16,11 +17,11 @@ https://github.com/buildersoftdev/andyxclient andyx eda streaming distributed - 3.0.0-alpha1 + + 3.0.0 license.md andyX.png Buildersoft.Andy.X.Client - README.md True True True @@ -31,22 +32,18 @@ - - + + - - - + + + - - True - - True @@ -60,6 +57,6 @@ - - + + diff --git a/src/Andy.X.Client/Assets/andyX.png b/src/Andy.X.Client/Assets/andyX.png index 0411c961ec3e156c621bea6b4e9e28bcac07c439..0f885bab162fce07d59dbc115eb4c3bbce774463 100644 GIT binary patch literal 19321 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 literal 58471 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 I*+2RJ01TmIVgLXD diff --git a/src/Andy.X.Client/Builders/ConsumerBuilder.cs b/src/Andy.X.Client/Builders/ConsumerBuilder.cs new file mode 100644 index 0000000..5473508 --- /dev/null +++ b/src/Andy.X.Client/Builders/ConsumerBuilder.cs @@ -0,0 +1,21 @@ +using Andy.X.Client.Configurations; +using System; + +namespace Andy.X.Client.Builders +{ + public class ConsumerBuilder + { + public ConsumerConfiguration ConsumerConfiguration { get; private set; } + + public ConsumerBuilder(ConsumerConfiguration consumerConfiguration) + { + ConsumerConfiguration = consumerConfiguration; + } + + public ConsumerBuilder(Action config) + { + ConsumerConfiguration = new ConsumerConfiguration(); + config.Invoke(ConsumerConfiguration); + } + } +} diff --git a/src/Andy.X.Client/Builders/ProducerBuilder.cs b/src/Andy.X.Client/Builders/ProducerBuilder.cs new file mode 100644 index 0000000..117b59e --- /dev/null +++ b/src/Andy.X.Client/Builders/ProducerBuilder.cs @@ -0,0 +1,21 @@ +using Andy.X.Client.Configurations; +using System; + +namespace Andy.X.Client.Builders +{ + public class ProducerBuilder + { + public ProducerConfiguration ProducerConfiguration { get; private set; } + + public ProducerBuilder(ProducerConfiguration producerConfiguration) + { + ProducerConfiguration = producerConfiguration; + } + + public ProducerBuilder(Action config) + { + ProducerConfiguration = new ProducerConfiguration(); + config.Invoke(ProducerConfiguration); + } + } +} diff --git a/src/Andy.X.Client/Commands/AcknowledgeMessageArgs.cs b/src/Andy.X.Client/Commands/AcknowledgeMessageArgs.cs new file mode 100644 index 0000000..8d8a658 --- /dev/null +++ b/src/Andy.X.Client/Commands/AcknowledgeMessageArgs.cs @@ -0,0 +1,24 @@ +using MessagePack; + +namespace Andy.X.Client.Commands +{ + [MessagePackObject] + public sealed class AcknowledgeMessageArgs + { + [Key(0)] + public long EntryId { get; set; } + + [Key(1)] + public string NodeId { get; set; } + + [Key(2)] + public int Acknowledgement { get; set; } + } + + public enum MessageAcknowledgement + { + Acknowledged, + Unacknowledged, + Skipped + } +} diff --git a/src/Andy.X.Client/Commands/TransmitMessageArgs.cs b/src/Andy.X.Client/Commands/TransmitMessageArgs.cs new file mode 100644 index 0000000..bfcbe2f --- /dev/null +++ b/src/Andy.X.Client/Commands/TransmitMessageArgs.cs @@ -0,0 +1,40 @@ +using MessagePack; +using System.Collections.Generic; +using System; + +namespace Andy.X.Client.Commands +{ + [MessagePackObject] + public sealed class TransmitMessageArgs + { + [Key(0)] + public string Tenant { get; set; } + [Key(1)] + public string Product { get; set; } + [Key(2)] + public string Component { get; set; } + [Key(3)] + public string Topic { get; set; } + + [Key(4)] + public Dictionary Headers { get; set; } + + [Key(5)] + public byte[] Id { get; set; } + + [Key(6)] + public byte[] Payload { get; set; } + + [Key(7)] + public DateTimeOffset SentDate { get; set; } + + [Key(8)] + public Guid IdentityId { get; set; } + + [Key(9)] + public string NodeId { get; set; } + + [Key(10)] + public bool RequiresCallback { get; set; } + } +} diff --git a/src/Andy.X.Client/Configurations/ComponentConfiguration.cs b/src/Andy.X.Client/Configurations/ComponentConfiguration.cs new file mode 100644 index 0000000..47f9c18 --- /dev/null +++ b/src/Andy.X.Client/Configurations/ComponentConfiguration.cs @@ -0,0 +1,14 @@ +namespace Andy.X.Client.Configurations +{ + public class ComponentConfiguration + { + public string Name { get; set; } + public string Key { get; set; } + public string Secret { get; set; } + + public ComponentConfiguration() + { + Name = "default"; + } + } +} diff --git a/src/Andy.X.Client/Configurations/ConsumerConfiguration.cs b/src/Andy.X.Client/Configurations/ConsumerConfiguration.cs new file mode 100644 index 0000000..61ecfc5 --- /dev/null +++ b/src/Andy.X.Client/Configurations/ConsumerConfiguration.cs @@ -0,0 +1,38 @@ +namespace Andy.X.Client.Configurations +{ + public class ConsumerConfiguration + { + public string Name { get; set; } + public SubscriptionConfiguration Subscription { get; set; } + public ComponentConfiguration Component { get; set; } + public TopicConfiguration Topic { get; set; } + + public ConsumerSettings Settings { get; set; } + + public ConsumerConfiguration() : this("hello-world-consumer") + { + // it calls the other constructor + } + + public ConsumerConfiguration(string name) : this(name, new SubscriptionConfiguration()) + { + // it calls the other constructor + } + + public ConsumerConfiguration(string name, SubscriptionConfiguration subscription) + { + Name = name; + Subscription = subscription; + + Component = new ComponentConfiguration(); + Topic = new TopicConfiguration(); + + Settings = new ConsumerSettings(); + } + } + + public class ConsumerSettings + { + + } +} diff --git a/src/Andy.X.Client/Configurations/NodeConnectionType.cs b/src/Andy.X.Client/Configurations/NodeConnectionType.cs index 761be47..4a4d0af 100644 --- a/src/Andy.X.Client/Configurations/NodeConnectionType.cs +++ b/src/Andy.X.Client/Configurations/NodeConnectionType.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Andy.X.Client.Configurations +namespace Andy.X.Client.Configurations { public enum NodeConnectionType { diff --git a/src/Andy.X.Client/Configurations/ProducerConfiguration.cs b/src/Andy.X.Client/Configurations/ProducerConfiguration.cs new file mode 100644 index 0000000..490c996 --- /dev/null +++ b/src/Andy.X.Client/Configurations/ProducerConfiguration.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; + +namespace Andy.X.Client.Configurations +{ + public class ProducerConfiguration + { + public string Name { get; set; } + + public ProducerSettings Settings { get; set; } + public ComponentConfiguration Component { get; set; } + public TopicConfiguration Topic { get; set; } + + // These headers will be sent with each message. + public Dictionary Headers { get; set; } + + public ProducerConfiguration() : this("hello-world-producer", new ProducerSettings()) + { + // it calls the last constructor + } + + public ProducerConfiguration(string name) : this(name, new ProducerSettings()) + { + // it calls the other constructor + } + + public ProducerConfiguration(string name, ProducerSettings producerSettings) + { + Name = name; + Settings = producerSettings; + + Component = new ComponentConfiguration(); + Topic = new TopicConfiguration(); + + Headers = new Dictionary(); + } + } + + public class ProducerSettings + { + public bool EnableRetryProducing { get; set; } + public bool BreakIfTryToSendMessageInClosedConneciton { get; set; } + public int BatchSize { get; set; } + public long LingerMs { get; set; } + public int RequestTimeoutMs { get; set; } + public int TimeoutInSyncResponseMs { get; set; } + + public bool RequireCallback { get; set; } + + public ProducerSettings() + { + EnableRetryProducing = false; + BatchSize = 100; + LingerMs = 1; + RequestTimeoutMs = 60000; + TimeoutInSyncResponseMs = 3000; + BreakIfTryToSendMessageInClosedConneciton = false; + RequireCallback = true; + } + } +} diff --git a/src/Andy.X.Client/Configurations/ProductConfiguration.cs b/src/Andy.X.Client/Configurations/ProductConfiguration.cs new file mode 100644 index 0000000..eb1e06f --- /dev/null +++ b/src/Andy.X.Client/Configurations/ProductConfiguration.cs @@ -0,0 +1,13 @@ +namespace Andy.X.Client.Configurations +{ + public class ProductConfiguration + { + public string Name { get; set; } + public string Key { get; set; } + public string Secret { get; set; } + public ProductConfiguration() + { + Name = "public"; + } + } +} diff --git a/src/Andy.X.Client/Configurations/SubscriptionConfiguration.cs b/src/Andy.X.Client/Configurations/SubscriptionConfiguration.cs new file mode 100644 index 0000000..61f2c99 --- /dev/null +++ b/src/Andy.X.Client/Configurations/SubscriptionConfiguration.cs @@ -0,0 +1,72 @@ +namespace Andy.X.Client.Configurations +{ + public class SubscriptionConfiguration + { + public string Name { get; set; } + + public SubscriptionType Type { get; set; } + public SubscriptionMode Mode { get; set; } + public InitialPosition InitialPosition { get; set; } + + public SubscriptionConfiguration(): this("hello-world-subscription") + { + + } + + public SubscriptionConfiguration(string name) + { + Name = name; + + Type = SubscriptionType.Unique; + Mode = SubscriptionMode.Resilient; + InitialPosition = InitialPosition.Latest; + } + } + + public enum SubscriptionType + { + ///

+ /// Only one consumer instance + /// + Unique, + /// + /// One consumer instance with other backup + /// + Failover, + /// + /// Shared to more than one consumer instance. + /// + Shared + } + + public enum SubscriptionMode + { + /// + /// Durable + /// + Resilient, + + /// + /// Non Durable + /// + NonResilient + } + + public enum InitialPosition + { + /// + /// Register pointer of entry to the earliest message entry position in topic + /// + Earliest, + + /// + /// Register pointer of entry to the lastest message entry position + /// + Latest, + + /// + /// Register pointer of entry to some time ago message entry position. Version 3.0 will not support InTime + /// + //InTime + } +} diff --git a/src/Andy.X.Client/Configurations/TenantConfiguration.cs b/src/Andy.X.Client/Configurations/TenantConfiguration.cs new file mode 100644 index 0000000..bbacaf0 --- /dev/null +++ b/src/Andy.X.Client/Configurations/TenantConfiguration.cs @@ -0,0 +1,15 @@ +namespace Andy.X.Client.Configurations +{ + public class TenantConfiguration + { + public string Name { get; set; } + public string Key { get; set; } + public string Secret { get; set; } + + public TenantConfiguration() + { + Name = "default"; + } + } + +} diff --git a/src/Andy.X.Client/Configurations/TopicConfiguration.cs b/src/Andy.X.Client/Configurations/TopicConfiguration.cs new file mode 100644 index 0000000..6192053 --- /dev/null +++ b/src/Andy.X.Client/Configurations/TopicConfiguration.cs @@ -0,0 +1,14 @@ +namespace Andy.X.Client.Configurations +{ + public class TopicConfiguration + { + public string Name { get; set; } + public string Description { get; set; } + + public TopicConfiguration() + { + Name = "hello-world"; + Description = "hello-world topic created from Andy X Client for .NET"; + } + } +} diff --git a/src/Andy.X.Client/Configurations/XClientConfiguration.cs b/src/Andy.X.Client/Configurations/XClientConfiguration.cs new file mode 100644 index 0000000..c0b210a --- /dev/null +++ b/src/Andy.X.Client/Configurations/XClientConfiguration.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Net.Http; + +namespace Andy.X.Client.Configurations +{ + public class XClientConfiguration + { + public Uri NodeUrl { get; set; } + public TenantConfiguration Tenant { get; set; } + public ProductConfiguration Product { get; set; } + + public XClientSettings Settings { get; set; } + + public XClientConfiguration() : this(new Uri("http://localhost6540")) + { + // it calls the other constructor + } + + /// + /// Initialize XClientConfiguration + /// + /// nodeUrl, default value is http://localhost:6540 + public XClientConfiguration(Uri nodeUrl) + { + NodeUrl = nodeUrl; + Tenant = new TenantConfiguration(); + Product = new ProductConfiguration(); + + Settings = new XClientSettings(); + } + } + + public class XClientSettings + { + public LoggingConfiguration Logging { get; set; } + public HttpClientHandler HttpClientHandler { get; set; } + + public bool EnableAutoReconnect { get; set; } + + public int ConnectionTimeoutMs { get; set; } + public int ReconnectionTimeoutMs { get; set; } + + + public XClientSettings() + { + Logging = new LoggingConfiguration(); + HttpClientHandler = new HttpClientHandler(); + + EnableAutoReconnect = true; + ConnectionTimeoutMs = 10000; + ReconnectionTimeoutMs = 1000; + } + } + + public class LoggingConfiguration + { + private readonly ILoggerFactory _loggerFactory = null; + + public LoggingConfiguration() : this(new LoggerFactory()) + { + + } + + public LoggingConfiguration(ILoggerFactory loggerFactory) + { + _loggerFactory = loggerFactory; + } + + public ILoggerFactory GetLoggerFactory() + { + return _loggerFactory; + } + } + +} diff --git a/src/Andy.X.Client/Consumer.cs b/src/Andy.X.Client/Consumer.cs new file mode 100644 index 0000000..b90e571 --- /dev/null +++ b/src/Andy.X.Client/Consumer.cs @@ -0,0 +1,52 @@ +using Andy.X.Client.Abstractions; +using Andy.X.Client.Abstractions.Base; +using Andy.X.Client.Abstractions.Consumers; +using Andy.X.Client.Abstractions.XClients; +using Andy.X.Client.Builders; +using Andy.X.Client.Configurations; + +namespace Andy.X.Client +{ + public class Consumer : ConsumerBase + { + private Consumer(IXClient xClient) : base(xClient) + { + } + public static IConsumerComponentConnection CreateNewConsumer(IXClient xClient) + { + return new Consumer(xClient); + } + + private Consumer(IXClientFactory xClientFactory) : base(xClientFactory) + { + } + public static IConsumerComponentConnection CreateNewConsumer(IXClientFactory xClientFactory) + { + return new Consumer(xClientFactory); + } + + private Consumer(IXClientFactory xClientFactory, ConsumerConfiguration consumerConfiguration) : base(xClientFactory, consumerConfiguration) + { + } + public static IConsumerComponentConnection CreateNewConsumer(IXClientFactory xClientFactory, ConsumerConfiguration consumerConfiguration) + { + return new Consumer(xClientFactory, consumerConfiguration); + } + + private Consumer(IXClientFactory xClientFactory, ConsumerBuilder consumerBuilder) : base(xClientFactory, consumerBuilder) + { + } + public static IConsumerComponentConnection CreateNewConsumer(IXClientFactory xClientFactory, ConsumerBuilder consumerBuilder) + { + return new Consumer(xClientFactory, consumerBuilder); + } + + private Consumer(XClient xClient, ConsumerConfiguration consumerConfiguration) : base(xClient, consumerConfiguration) + { + } + public static IConsumerComponentConnection CreateNewConsumer(XClient xClient, ConsumerConfiguration consumerConfiguration) + { + return new Consumer(xClient, consumerConfiguration); + } + } +} diff --git a/src/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs b/src/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs new file mode 100644 index 0000000..9d848b2 --- /dev/null +++ b/src/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs @@ -0,0 +1,26 @@ +using MessagePack; +using System; + +namespace Andy.X.Client.Events.Consumers +{ + [MessagePackObject] + public sealed class ConsumerConnectedArgs + { + [Key(0)] + public string Tenant { get; set; } + [Key(1)] + public string Product { get; set; } + [Key(2)] + public string Component { get; set; } + [Key(3)] + public string Topic { get; set; } + + [Key(4)] + public string Subscription { get; set; } + + [Key(5)] + public Guid Id { get; set; } + [Key(6)] + public string ConsumerName { get; set; } + } +} diff --git a/src/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs b/src/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs new file mode 100644 index 0000000..5aa4ceb --- /dev/null +++ b/src/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs @@ -0,0 +1,23 @@ +using MessagePack; +using System; + +namespace Andy.X.Client.Events.Consumers +{ + [MessagePackObject] + public sealed class ConsumerDisconnectedArgs + { + [Key(0)] + public string Tenant { get; set; } + [Key(1)] + public string Product { get; set; } + [Key(2)] + public string Component { get; set; } + [Key(3)] + public string Topic { get; set; } + + [Key(4)] + public Guid Id { get; set; } + [Key(5)] + public string ConsumerName { get; set; } + } +} diff --git a/src/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs b/src/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs new file mode 100644 index 0000000..eaca87b --- /dev/null +++ b/src/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs @@ -0,0 +1,28 @@ +using MessagePack; +using System; +using System.Collections.Generic; + +namespace Andy.X.Client.Events.Consumers +{ + [MessagePackObject] + public sealed class MessageInternalReceivedArgs + { + [Key(0)] + public long EntryId { get; set; } + + [Key(1)] + public string NodeId { get; set; } + + [Key(2)] + public Dictionary Headers { get; set; } + + [Key(3)] + public byte[] MessageId { get; set; } + + [Key(4)] + public byte[] Payload { get; set; } + + [Key(5)] + public DateTimeOffset SentDate { get; set; } + } +} diff --git a/src/Andy.X.Client/Events/Producers/MessageAcceptedArgs.cs b/src/Andy.X.Client/Events/Producers/MessageAcceptedArgs.cs new file mode 100644 index 0000000..a22300c --- /dev/null +++ b/src/Andy.X.Client/Events/Producers/MessageAcceptedArgs.cs @@ -0,0 +1,18 @@ +using MessagePack; +using System; + +namespace Andy.X.Client.Events.Producers +{ + [MessagePackObject] + public sealed class MessageAcceptedArgs + { + [Key(0)] + public Guid IdentityId { get; set; } + + [Key(1)] + public int MessageCount { get; set; } + + [Key(2)] + public DateTimeOffset AcceptedDate { get; set; } + } +} diff --git a/src/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs b/src/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs new file mode 100644 index 0000000..d6969c2 --- /dev/null +++ b/src/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs @@ -0,0 +1,22 @@ +using MessagePack; + +namespace Andy.X.Client.Events.Producers +{ + [MessagePackObject] + public sealed class ProducerConnectedArgs + { + [Key(0)] + public string Tenant { get; set; } + [Key(1)] + public string Product { get; set; } + [Key(2)] + public string Component { get; set; } + [Key(3)] + public string Topic { get; set; } + + [Key(4)] + public string Id { get; set; } + [Key(5)] + public string ProducerName { get; set; } + } +} diff --git a/src/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs b/src/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs new file mode 100644 index 0000000..3584628 --- /dev/null +++ b/src/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs @@ -0,0 +1,23 @@ +using MessagePack; +using System; + +namespace Andy.X.Client.Events.Producers +{ + [MessagePackObject] + public sealed class ProducerDisconnectedArgs + { + [Key(0)] + public string Tenant { get; set; } + [Key(1)] + public string Product { get; set; } + [Key(2)] + public string Component { get; set; } + [Key(3)] + public string Topic { get; set; } + + [Key(4)] + public Guid Id { get; set; } + [Key(5)] + public string ProducerName { get; set; } + } +} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Factories/XClientFactory.cs b/src/Andy.X.Client/Factories/XClientFactory.cs similarity index 87% rename from src/net/andy-x-client-lib/Andy.X.Client/Factories/XClientFactory.cs rename to src/Andy.X.Client/Factories/XClientFactory.cs index 730bab1..81061cd 100644 --- a/src/net/andy-x-client-lib/Andy.X.Client/Factories/XClientFactory.cs +++ b/src/Andy.X.Client/Factories/XClientFactory.cs @@ -13,7 +13,7 @@ public XClientFactory(XClientConfiguration configuration) public XClient CreateClient() { - return XClient.CreateConnection(_configuration); + return XClient.CreateClient(_configuration); } } } diff --git a/src/Andy.X.Client/Models/Message.cs b/src/Andy.X.Client/Models/Message.cs new file mode 100644 index 0000000..885ce16 --- /dev/null +++ b/src/Andy.X.Client/Models/Message.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace Andy.X.Client.Models +{ + public sealed class Message + { + public long EntryId { get; private set; } + public string NodeId { get; private set; } + + public IDictionary Headers { get; private set; } + + public V Payload { get; private set; } + + public DateTimeOffset SentDate { get; private set; } + public DateTimeOffset ReceivedDate { get; private set; } + + public Message(long entryId, string nodeId, IDictionary headers, V payload, DateTimeOffset sentDate, DateTimeOffset receivedDate) + { + EntryId = entryId; + NodeId = nodeId; + Headers = headers; + Payload = payload; + SentDate = sentDate; + ReceivedDate = receivedDate; + } + } +} diff --git a/src/Andy.X.Client/Models/MessageId.cs b/src/Andy.X.Client/Models/MessageId.cs new file mode 100644 index 0000000..32a8528 --- /dev/null +++ b/src/Andy.X.Client/Models/MessageId.cs @@ -0,0 +1,18 @@ +using System; + +namespace Andy.X.Client.Models +{ + public class MessageId + { + public Guid IdentityId { get; private set; } + public int MessageCountAccepted { get; private set; } + public DateTimeOffset AcceptedDate { get; private set; } + + public MessageId(Guid identityId, int messageCountAccepted, DateTimeOffset acceptedDate) + { + IdentityId = identityId; + MessageCountAccepted = messageCountAccepted; + AcceptedDate = acceptedDate; + } + } +} diff --git a/src/Andy.X.Client/Producer.cs b/src/Andy.X.Client/Producer.cs new file mode 100644 index 0000000..905ef72 --- /dev/null +++ b/src/Andy.X.Client/Producer.cs @@ -0,0 +1,53 @@ +using Andy.X.Client.Abstractions; +using Andy.X.Client.Abstractions.Base; +using Andy.X.Client.Abstractions.Producers; +using Andy.X.Client.Abstractions.XClients; +using Andy.X.Client.Builders; +using Andy.X.Client.Configurations; + +namespace Andy.X.Client +{ + public class Producer : ProducerBase + { + private Producer(IXClient xClient) : base(xClient) + { + } + public static IProducerComponentConnection CreateNewProducer(IXClient xClient) + { + return new Producer(xClient); + } + + private Producer(IXClientFactory xClientFactory) : base(xClientFactory) + { + } + public static IProducerComponentConnection CreateNewProducer(IXClientFactory xClientFactory) + { + return new Producer(xClientFactory); + } + + private Producer(IXClientFactory xClientFactory, ProducerConfiguration producerConfiguration) : base(xClientFactory, producerConfiguration) + { + } + + public static IProducerComponentConnection CreateNewProducer(IXClientFactory xClientFactory, ProducerConfiguration producerConfiguration) + { + return new Producer(xClientFactory, producerConfiguration); + } + + private Producer(IXClientFactory xClientFactory, ProducerBuilder producerBuilder) : base(xClientFactory, producerBuilder) + { + } + public static IProducerComponentConnection CreateNewProducer(IXClientFactory xClientFactory, ProducerBuilder producerBuilder) + { + return new Producer(xClientFactory, producerBuilder); + } + + private Producer(XClient xClient, ProducerConfiguration producerConfiguration) : base(xClient, producerConfiguration) + { + } + public static IProducerComponentConnection CreateNewProducer(XClient xClient, ProducerConfiguration producerConfiguration) + { + return new Producer(xClient, producerConfiguration); + } + } +} diff --git a/src/Andy.X.Client/Providers/ConsumerNodeProvider.cs b/src/Andy.X.Client/Providers/ConsumerNodeProvider.cs new file mode 100644 index 0000000..39cc0b5 --- /dev/null +++ b/src/Andy.X.Client/Providers/ConsumerNodeProvider.cs @@ -0,0 +1,84 @@ +using Andy.X.Client.Configurations; +using Andy.X.Client.Utilities.Helpers; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Andy.X.Client.Providers +{ + internal class ConsumerNodeProvider + { + private readonly XClientConfiguration _xClientConfiguration; + private readonly ConsumerConfiguration _consumerConfiguration; + + private readonly HubConnection _connection; + private readonly IHubConnectionBuilder _hubConnectionBuilder; + + public ConsumerNodeProvider(XClientConfiguration xClientConfiguration, ConsumerConfiguration consumerConfiguration) + { + _xClientConfiguration = xClientConfiguration; + _consumerConfiguration = consumerConfiguration; + + _hubConnectionBuilder = new HubConnectionBuilder() + .WithUrl($"{_xClientConfiguration.NodeUrl.AbsoluteUri}realtime/v3/consumer", option => + { + // Implement HttpMessageHandler given from the client. + option.HttpMessageHandlerFactory = (message) => + { + return xClientConfiguration.Settings.HttpClientHandler; + }; + + // Authorization tokens for tenant, product and component + if (_xClientConfiguration.Tenant.Key != "") + option.Headers["x-andyx-tenant-authoriziation"] = AuthorizationHelpers.GenerateToken(_xClientConfiguration.Tenant.Key, _xClientConfiguration.Tenant.Secret); + + if (_xClientConfiguration.Product.Key != "") + option.Headers["x-andyx-product-authoriziation"] = AuthorizationHelpers.GenerateToken(_xClientConfiguration.Product.Key, _xClientConfiguration.Product.Secret); + + if (_consumerConfiguration.Component.Key != "") + option.Headers["x-andyx-component-authoriziation"] = AuthorizationHelpers.GenerateToken(_consumerConfiguration.Component.Key, _consumerConfiguration.Component.Secret); + + // Headers for general location + option.Headers["x-andyx-tenant"] = xClientConfiguration.Tenant.Name; + option.Headers["x-andyx-product"] = xClientConfiguration.Product.Name; + option.Headers["x-andyx-component"] = _consumerConfiguration.Component.Name; + option.Headers["x-andyx-topic"] = _consumerConfiguration.Topic.Name; + option.Headers["x-andyx-topic-description"] = _consumerConfiguration.Topic.Description; + + + // Consumer related headers + option.Headers["x-andyx-consumer-name"] = _consumerConfiguration.Name; + + // Subscription related configuration headers + option.Headers["x-andyx-subscription-name"] = _consumerConfiguration.Subscription.Name; + option.Headers["x-andyx-subscription-mode"] = _consumerConfiguration.Subscription.Mode.ToString(); + option.Headers["x-andyx-subscription-type"] = _consumerConfiguration.Subscription.Type.ToString(); + option.Headers["x-andyx-subscription-initial-position"] = _consumerConfiguration.Subscription.InitialPosition.ToString(); + }) + .AddMessagePackProtocol(); + + if (_xClientConfiguration.Settings.EnableAutoReconnect) + _hubConnectionBuilder.WithAutomaticReconnect(); + + // adding logging only for testing purposes. + _hubConnectionBuilder.ConfigureLogging(builder => + { + builder.AddSystemdConsole(); + builder.AddFilter("Console", level => level >= LogLevel.Information); + }); + + _connection = _hubConnectionBuilder + .Build(); + } + + public HubConnection GetHubConnection() + { + return _connection; + } + + public ConsumerConfiguration GetConsumerConfiguration() + { + return _consumerConfiguration; + } + } +} diff --git a/src/Andy.X.Client/Providers/ProducerNodeProvider.cs b/src/Andy.X.Client/Providers/ProducerNodeProvider.cs new file mode 100644 index 0000000..a776064 --- /dev/null +++ b/src/Andy.X.Client/Providers/ProducerNodeProvider.cs @@ -0,0 +1,79 @@ +using Andy.X.Client.Configurations; +using Andy.X.Client.Utilities.Helpers; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Andy.X.Client.Providers +{ + internal class ProducerNodeProvider + { + private readonly XClientConfiguration _xClientConfiguration; + private readonly ProducerConfiguration _producerConfiguration; + + private readonly HubConnection _connection; + private readonly IHubConnectionBuilder _hubConnectionBuilder; + + public ProducerNodeProvider(XClientConfiguration xClientConfiguration, ProducerConfiguration producerConfiguration) + { + _xClientConfiguration = xClientConfiguration; + _producerConfiguration = producerConfiguration; + + _hubConnectionBuilder = new HubConnectionBuilder() + .WithUrl($"{_xClientConfiguration.NodeUrl.AbsoluteUri}realtime/v3/producer", option => + { + // Implement HttpMessageHandler given from the client. + option.HttpMessageHandlerFactory = (message) => + { + return xClientConfiguration.Settings.HttpClientHandler; + }; + + // Authorization tokens for tenant, product and component + if (_xClientConfiguration.Tenant.Key != "") + option.Headers["x-andyx-tenant-authoriziation"] = AuthorizationHelpers.GenerateToken(_xClientConfiguration.Tenant.Key, _xClientConfiguration.Tenant.Secret); + + if (_xClientConfiguration.Product.Key != "") + option.Headers["x-andyx-product-authoriziation"] = AuthorizationHelpers.GenerateToken(_xClientConfiguration.Product.Key, _xClientConfiguration.Product.Secret); + + if (_producerConfiguration.Component.Key != "") + option.Headers["x-andyx-component-authoriziation"] = AuthorizationHelpers.GenerateToken(_producerConfiguration.Component.Key, _producerConfiguration.Component.Secret); + + // Headers for general location + option.Headers["x-andyx-tenant"] = xClientConfiguration.Tenant.Name; + option.Headers["x-andyx-product"] = xClientConfiguration.Product.Name; + option.Headers["x-andyx-component"] = _producerConfiguration.Component.Name; + option.Headers["x-andyx-topic"] = _producerConfiguration.Topic.Name; + option.Headers["x-andyx-topic-description"] = _producerConfiguration.Topic.Description; + + option.Headers["x-andyx-producer-name"] = _producerConfiguration.Name; + + }) + .AddMessagePackProtocol(); + + + if (_xClientConfiguration.Settings.EnableAutoReconnect) + _hubConnectionBuilder.WithAutomaticReconnect(); + + // adding logging only for testing purposes. + _hubConnectionBuilder.ConfigureLogging(builder => + { + builder.AddSystemdConsole(); + builder.AddFilter("Console", level => level >= LogLevel.Information); + }); + + + _connection = _hubConnectionBuilder + .Build(); + } + + public HubConnection GetHubConnection() + { + return _connection; + } + + public ProducerConfiguration GetProducerConfiguration() + { + return _producerConfiguration; + } + } +} diff --git a/src/Andy.X.Client/Services/ConsumerNodeService.cs b/src/Andy.X.Client/Services/ConsumerNodeService.cs new file mode 100644 index 0000000..37e2b5a --- /dev/null +++ b/src/Andy.X.Client/Services/ConsumerNodeService.cs @@ -0,0 +1,106 @@ +using Andy.X.Client.Commands; +using Andy.X.Client.Configurations; +using Andy.X.Client.Events.Consumers; +using Andy.X.Client.Providers; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Andy.X.Client.Services +{ + internal class ConsumerNodeService + { + private readonly ILogger _logger; + + private readonly HubConnection _connection; + + private readonly ConsumerNodeProvider _consumerNodeProvider; + private readonly XClientConfiguration _xClientConfiguration; + + public event Action ConsumerConnected; + public event Action ConsumerDisconnected; + public event Action MessageInternalReceived; + public event Action AndyOrderedDisconnect; + + public ConsumerNodeService(ConsumerNodeProvider consumerNodeProvider, XClientConfiguration xClientConfiguration) + { + _consumerNodeProvider = consumerNodeProvider; + _xClientConfiguration = xClientConfiguration; + + _logger = xClientConfiguration + .Settings + .Logging + .GetLoggerFactory() + .CreateLogger(); + + _connection = consumerNodeProvider.GetHubConnection(); + + _connection.Closed += Connection_Closed; + _connection.Reconnected += Connection_Reconnected; + _connection.Reconnecting += Connection_Reconnecting; + + + _connection.On("ConsumerConnected", connectedArgs => ConsumerConnected?.Invoke(connectedArgs)); + _connection.On("ConsumerDisconnected", disconnected => ConsumerDisconnected?.Invoke(disconnected)); + _connection.On("MessageSent", received => MessageInternalReceived?.Invoke(received)); + + _connection.On("AndyOrderedDisconnect", message => AndyOrderedDisconnect?.Invoke(message)); + } + + public async Task ConnectAsync() + { + await _connection.StartAsync().ContinueWith(async task => + { + if (task.Exception != null) + { + _logger.LogError($"Consumer {_consumerNodeProvider.GetConsumerConfiguration().Name} failed to connect to Andy X, details {task.Exception.Message}"); + if (_xClientConfiguration.Settings.EnableAutoReconnect == true) + { + // retry connection + Thread.Sleep(_xClientConfiguration.Settings.ReconnectionTimeoutMs); + _logger.LogWarning($"Consumer {_consumerNodeProvider.GetConsumerConfiguration().Name} is connectiong to Andy X"); + await ConnectAsync(); + } + } + }); + } + + public async Task DisconnectAsync() + { + await _connection.StopAsync().ContinueWith(task => + { + if (task.Exception != null) + { + _logger.LogError($"Consumer {_consumerNodeProvider.GetConsumerConfiguration().Name} failed to disconnect from Andy X, details {task.Exception.Message}"); + } + }); + } + + + public async Task AcknowledgeMessage(AcknowledgeMessageArgs acknowledgeMessageArgs) + { + await _connection.SendAsync("AcknowledgeMessage", acknowledgeMessageArgs); + } + + + private Task Connection_Reconnecting(Exception arg) + { + _logger.LogWarning($"Consumer {_consumerNodeProvider.GetConsumerConfiguration().Name} connection is lost, agent is reconnecting to node, details {arg.Message}"); + return Task.CompletedTask; + } + + private Task Connection_Reconnected(string arg) + { + _logger.LogInformation($"Consumer {_consumerNodeProvider.GetConsumerConfiguration().Name} is reconnected"); + return Task.CompletedTask; + } + + private Task Connection_Closed(Exception arg) + { + _logger.LogError($"Consumer {_consumerNodeProvider.GetConsumerConfiguration().Name} connection is closed, details {arg.Message}"); + return Task.CompletedTask; + } + } +} diff --git a/src/Andy.X.Client/Services/ProducerNodeService.cs b/src/Andy.X.Client/Services/ProducerNodeService.cs new file mode 100644 index 0000000..e23766d --- /dev/null +++ b/src/Andy.X.Client/Services/ProducerNodeService.cs @@ -0,0 +1,116 @@ +using Andy.X.Client.Commands; +using Andy.X.Client.Configurations; +using Andy.X.Client.Events.Producers; +using Andy.X.Client.Providers; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Andy.X.Client.Services +{ + internal class ProducerNodeService + { + private readonly ILogger _logger; + + private readonly HubConnection _connection; + + private readonly ProducerNodeProvider _producerNodeProvider; + private readonly XClientConfiguration _xClientConfiguration; + + public event Action ProducerConnected; + public event Action ProducerDisconnected; + public event Action MessageAccepted; + public event Action AndyOrderedDisconnect; + + public ProducerNodeService(ProducerNodeProvider producerNodeProvider, XClientConfiguration xClientConfiguration) + { + _producerNodeProvider = producerNodeProvider; + _xClientConfiguration = xClientConfiguration; + + _logger = xClientConfiguration + .Settings + .Logging + .GetLoggerFactory() + .CreateLogger(); + + _connection = producerNodeProvider.GetHubConnection(); + + _connection.Closed += Connection_Closed; + _connection.Reconnected += Connection_Reconnected; + _connection.Reconnecting += Connection_Reconnecting; + + _connection.On("ProducerConnected", connectedArgs => ProducerConnected?.Invoke(connectedArgs)); + _connection.On("ProducerDisconnected", disconnected => ProducerDisconnected?.Invoke(disconnected)); + _connection.On("MessageAccepted", msgAccepted => MessageAccepted?.Invoke(msgAccepted)); + + _connection.On("AndyOrderedDisconnect", message => AndyOrderedDisconnect?.Invoke(message)); + + } + + public async Task ConnectAsync() + { + await _connection.StartAsync().ContinueWith(async task => + { + if (task.Exception != null) + { + _logger.LogError($"Producer {_producerNodeProvider.GetProducerConfiguration().Name} failed to connect to Andy X Node, details {task.Exception.Message}"); + if (_xClientConfiguration.Settings.EnableAutoReconnect == true) + { + Thread.Sleep(_xClientConfiguration.Settings.ReconnectionTimeoutMs); + _logger.LogWarning($"Producer {_producerNodeProvider.GetProducerConfiguration().Name} is connecting to Andy X"); + + await ConnectAsync(); + } + } + }); + } + + public async Task DisconnectAsync() + { + await _connection.StopAsync().ContinueWith(task => + { + if (task.Exception != null) + { + _logger.LogError($"Producer {_producerNodeProvider.GetProducerConfiguration().Name} failed to disconnect from Andy X, details {task.Exception.Message}"); + } + }); + } + + public async Task TransmitMessage(TransmitMessageArgs transmitMessageArgs) + { + await _connection.SendAsync("TransmitMessage", transmitMessageArgs); + } + + public async Task TransmitMessages(List transmitMessagesArgs) + { + await _connection.SendAsync("TransmitMessages", transmitMessagesArgs); + } + + + private Task Connection_Reconnecting(Exception arg) + { + _logger.LogWarning($"Producer {_producerNodeProvider.GetProducerConfiguration().Name} connection is lost, agent is reconnecting to node, details {arg.Message}"); + return Task.CompletedTask; + } + + private Task Connection_Reconnected(string arg) + { + _logger.LogInformation($"Producer {_producerNodeProvider.GetProducerConfiguration().Name} is reconnected"); + return Task.CompletedTask; + } + + private Task Connection_Closed(Exception arg) + { + _logger.LogError($"Producer {_producerNodeProvider.GetProducerConfiguration().Name} connection is closed, details {arg.Message}"); + return Task.CompletedTask; + } + + public HubConnectionState GetConnectionState() + { + return _connection.State; + } + } +} diff --git a/src/Andy.X.Client/Utilities/Helpers/AuthorizationHelpers.cs b/src/Andy.X.Client/Utilities/Helpers/AuthorizationHelpers.cs new file mode 100644 index 0000000..3bc1478 --- /dev/null +++ b/src/Andy.X.Client/Utilities/Helpers/AuthorizationHelpers.cs @@ -0,0 +1,16 @@ +using System; +using System.Text; + +namespace Andy.X.Client.Utilities.Helpers +{ + internal static class AuthorizationHelpers + { + public static string GenerateToken(string key, string secret) + { + string encodedPassword = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1") + .GetBytes(key + ":" + secret)); + + return encodedPassword; + } + } +} diff --git a/src/Andy.X.Client/XClient.cs b/src/Andy.X.Client/XClient.cs index 1273307..9cb75d6 100644 --- a/src/Andy.X.Client/XClient.cs +++ b/src/Andy.X.Client/XClient.cs @@ -1,76 +1,113 @@ using Andy.X.Client.Abstractions.XClients; using Andy.X.Client.Configurations; -using Microsoft.Extensions.Logging; using System; -using System.Net.Http; namespace Andy.X.Client { - public class XClient : IXClientServiceConnection, IXClientTenantConnection, IXClientProductConnection, IXClientConfiguration + public class XClient : IXClient { + private readonly XClientConfiguration _configuration; + private XClient() { + _configuration = new XClientConfiguration(); + } + private XClient(XClientConfiguration configuration) + { + _configuration = configuration; } - public static IXClientTenantConnection CreateClient() + public static IXClientServiceConnection CreateClient() { return new XClient(); } - public XClient Build() + public static XClient CreateClient(XClientConfiguration configuration) { - throw new NotImplementedException(); + return new XClient(configuration); } - IXClientConfiguration IXClientConfiguration.AddLoggingSupport(ILoggerFactory loggerFactory) + public IXClientConfiguration AndProduct(string product) { - throw new NotImplementedException(); + _configuration.Product.Name = product; + + return this; } - IXClientConfiguration IXClientProductConnection.AndProduct(string product) + public IXClientConfiguration AndProduct(string product, string key, string secret) { - throw new NotImplementedException(); + _configuration.Product.Name = product; + _configuration.Product.Key = key; + _configuration.Product.Secret = secret; + + return this; } - IXClientConfiguration IXClientProductConnection.AndProduct(string product, string key, string secret) + public IXClientProductConnection AndTenant(string tenant) { - throw new NotImplementedException(); + _configuration.Tenant.Name = tenant; + + return this; } - IXClientProductConnection IXClientTenantConnection.AndTenant(string tenant) + public IXClientProductConnection AndTenant(string tenant, string key, string secret) { - throw new NotImplementedException(); + _configuration.Tenant.Name = tenant; + _configuration.Tenant.Key = key; + _configuration.Tenant.Secret = secret; + + return this; } - IXClientProductConnection IXClientTenantConnection.AndTenant(string tenant, string key, string secret) + public XClient Build() { - throw new NotImplementedException(); + return this; } - IXClientTenantConnection IXClientServiceConnection.ForService(string nodeUrl) + public IXClientTenantConnection ForService(Uri nodeUrl) { - throw new NotImplementedException(); + _configuration.NodeUrl = nodeUrl; + + return this; } - IXClientTenantConnection IXClientServiceConnection.ForService(string nodeHostName, int hostPort) + + public IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType) { - throw new NotImplementedException(); + if (nodeConnectionType == NodeConnectionType.NON_SSL) + _configuration.NodeUrl = new Uri($"http://{nodeHostName}:{hostPort}"); + else + _configuration.NodeUrl = new Uri($"https://{nodeHostName}:{hostPort}"); + + return this; } - IXClientTenantConnection IXClientServiceConnection.ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType) + public IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType, bool isSSLCertsSkipped) { - throw new NotImplementedException(); + ForService(nodeHostName, hostPort, nodeConnectionType); + + if (isSSLCertsSkipped == true) + _configuration.Settings.HttpClientHandler.ServerCertificateCustomValidationCallback += + (sender, certificate, chain, sslPolicyErrors) => { return true; }; + + return this; } - IXClientTenantConnection IXClientServiceConnection.ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType, bool isSSLCertsSkipped) + public IXClientConfiguration WithSettings(Action settings) { - throw new NotImplementedException(); + settings.Invoke(_configuration.Settings); + + return this; } - IXClientConfiguration IXClientConfiguration.WithHttpClientHandler(Action httpHandler) + /// + /// Get ClientConfiguration. + /// + /// Object of XClientConfiguration. + public XClientConfiguration GetClientConfiguration() { - throw new NotImplementedException(); + return _configuration; } } } diff --git a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj b/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj deleted file mode 100644 index cb7fefa..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj +++ /dev/null @@ -1,103 +0,0 @@ - - - net6.0;net5;netcoreapp3.1;netstandard2.1;netstandard2.0 - 3.0.0 - 3.0.0 - Buildersoft Andy X - Buildersoft - Buildersoft - Copyright © Buildersoft 2022 - - Andy X Client extensions for ASP.NET Core - - https://github.com/buildersoftdev/andyxclient - andyx eda streaming distributed - 3.0.0-alpha1 - license.md - andyX.png - Andy.X.Client.Extensions.Microsoft.DependencyInjection - README.md - True - True - True - - https://buildersoftdev.azurewebsites.net/andyx - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - - - - - - - - - - - True - - - - True - - - - True - - - - - - - - - diff --git a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Assets/andyX.png b/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Assets/andyX.png deleted file mode 100644 index 0411c961ec3e156c621bea6b4e9e28bcac07c439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58471 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 I*+2RJ01TmIVgLXD diff --git a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Assets/license.md b/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Assets/license.md deleted file mode 100644 index 6413433..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/Assets/license.md +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2022 Buildersoft - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/ConsumerDependencyInjectionExtensions.cs b/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/ConsumerDependencyInjectionExtensions.cs deleted file mode 100644 index 5ec3c90..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/ConsumerDependencyInjectionExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Andy.X.Client.Builders; -using Andy.X.Client.Configurations; -using Microsoft.Extensions.DependencyInjection; -using System; - -namespace Andy.X.Client.Extensions.DependencyInjection -{ - public static class ConsumerDependencyInjectionExtensions - { - public static IServiceCollection AddConsumerBuilder(this IServiceCollection services, Action> configuration) - { - ConsumerConfiguration ConsumerConfiguration = new ConsumerConfiguration(); - configuration.Invoke(ConsumerConfiguration); - - var builder = new ConsumerBuilder(ConsumerConfiguration); - - return services.AddSingleton>(builder); - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/ProducerDependencyInjectionExtensions.cs b/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/ProducerDependencyInjectionExtensions.cs deleted file mode 100644 index 7398f4a..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client.Extensions.DependencyInjection/ProducerDependencyInjectionExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Andy.X.Client.Builders; -using Andy.X.Client.Configurations; -using Microsoft.Extensions.DependencyInjection; -using System; - -namespace Andy.X.Client.Extensions.DependencyInjection -{ - public static class ProducerDependencyInjectionExtensions - { - public static IServiceCollection AddProducerBuilder(this IServiceCollection services, Action> configuration) - { - ProducerConfiguration producerConfiguration = new ProducerConfiguration(); - configuration.Invoke(producerConfiguration); - - var builder = new ProducerBuilder(producerConfiguration); - - return services.AddSingleton>(builder); - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientConfiguration.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientConfiguration.cs deleted file mode 100644 index 981d4b9..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientConfiguration.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Net.Http; - -namespace Andy.X.Client.Abstractions.Client -{ - public interface IXClientConfiguration - { - IXClientConfiguration ConfigLogging(ILoggerFactory loggerFactory); - IXClientConfiguration WithHttpClientHandler(Action httpHandler); - - XClient Build(); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientProductConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientProductConnection.cs deleted file mode 100644 index 250cd70..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientProductConnection.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Andy.X.Client.Abstractions.Client -{ - public interface IXClientProductConnection - { - IXClientConfiguration AndProduct(string product); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientServiceConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientServiceConnection.cs deleted file mode 100644 index 70a6e9b..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientServiceConnection.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Andy.X.Client.Nodes; - -namespace Andy.X.Client.Abstractions.Client -{ - public interface IXClientServiceConnection - { - IXClientTenantConnection ForService(string nodeUrl); - IXClientTenantConnection ForService(string nodeHostName, int hostPort); - IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType); - IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType, bool isSSLCertsSkipped); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientTenantConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientTenantConnection.cs deleted file mode 100644 index 6022c6d..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Client/IXClientTenantConnection.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Andy.X.Client.Abstractions.Client -{ - public interface IXClientTenantConnection - { - IXClientProductConnection AndTenant(string tenant); - IXClientProductConnection AndTenant(string tenant, string token); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs deleted file mode 100644 index b8a9851..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ConsumerBase.cs +++ /dev/null @@ -1,300 +0,0 @@ -using Andy.X.Client.Abstractions.Consumers; -using Andy.X.Client.Builders; -using Andy.X.Client.Configurations; -using Andy.X.Client.Events.Consumers; -using Andy.X.Client.Extensions; -using MessagePack; -using Microsoft.Extensions.Logging; -using System; -using System.Threading.Tasks; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ConsumerBase : - IConsumerComponentConnection, - IConsumerTopicConnection, - IConsumerNameConnection, - IConsumerInitialPositionConnection, - IConsumerSubscriptionTypeConnection, - IConsumerOtherConfiguration - { - public delegate MessageAcknowledgement OnMessageReceivedHandler(object sender, MessageReceivedArgs e); - public event OnMessageReceivedHandler MessageReceived; - - private readonly XClient xClient; - private readonly ConsumerConfiguration consumerConfiguration; - private readonly ILogger logger; - - - private ConsumerNodeService consumerNodeService; - private bool isBuilt = false; - private bool isConnected = false; - - public ConsumerBase(XClient xClient) : this(xClient, new ConsumerConfiguration()) - { - } - - public ConsumerBase(IXClientFactory xClient) : this(xClient.CreateClient(), new ConsumerConfiguration()) - { - } - - public ConsumerBase(IXClientFactory xClient, ConsumerConfiguration consumerConfiguration) : this(xClient.CreateClient(), consumerConfiguration) - { - } - - public ConsumerBase(IXClientFactory xClient, ConsumerBuilder consumerBuilder) : this(xClient.CreateClient(), consumerBuilder.ConsumerConfiguration) - { - } - - public ConsumerBase(XClient xClient, ConsumerConfiguration consumerConfiguration) - { - this.xClient = xClient; - this.consumerConfiguration = consumerConfiguration; - logger = this.xClient.GetClientConfiguration() - .Logging - .GetLoggerFactory() - .CreateLogger(typeof(T)); - } - - ///

- /// Subscription name - /// - /// Subscription type - /// ConsumerBase - public ConsumerBase SubscriptionName(string subscriptionName) - { - consumerConfiguration.SubscriptionSettings.SubscriptionName = subscriptionName; - return this; - } - - /// - /// Subscription Type represents how the Consumer consumes messages - /// Default value SubscriptionType=Exclusive - /// - /// Subscription type - /// ConsumerBase - public ConsumerBase SubscriptionType(SubscriptionType subscriptionType) - { - consumerConfiguration.SubscriptionSettings.SubscriptionType = subscriptionType; - return this; - } - - public ConsumerBase SubscriptionMode(SubscriptionMode subscriptionMode) - { - consumerConfiguration.SubscriptionSettings.SubscriptionMode = subscriptionMode; - return this; - } - - /// - /// Build Consumer - /// - /// Consumer object - public Consumer Build() - { - consumerNodeService = new ConsumerNodeService(new ConsumerNodeProvider(xClient.GetClientConfiguration(), consumerConfiguration), xClient.GetClientConfiguration()); - consumerNodeService.ConsumerConnected += ConsumerNodeService_ConsumerConnected; - consumerNodeService.ConsumerDisconnected += ConsumerNodeService_ConsumerDisconnected; - consumerNodeService.MessageInternalReceived += ConsumerNodeService_MessageInternalReceived; - consumerNodeService.AndyOrderedDisconnect += ConsumerNodeService_AndyOrderedDisconnect; - - isBuilt = true; - - return this as Consumer; - } - - private async void ConsumerNodeService_AndyOrderedDisconnect(string obj) - { - await consumerNodeService.DisconnectAsync(); - } - - public async Task AcknowledgeMessage(string nodeId, long entryId, MessageAcknowledgement messageAcknowledgement) - { - await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() - { - EntryId = entryId, - NodeId = nodeId, - Acknowledgement = (int)messageAcknowledgement - }); - } - - private async void ConsumerNodeService_MessageInternalReceived(MessageInternalReceivedArgs obj) - { - T parsedPayload = MessagePackSerializer.Deserialize(obj.Payload, MessagePack.Resolvers.ContractlessStandardResolver.Options); - try - { - MessageAcknowledgement? isMessageAcknowledged = MessageReceived?.Invoke(this, new MessageReceivedArgs(obj.Tenant, - obj.Product, - obj.Component, - obj.Topic, - obj.EntryId, - obj.NodeId, - obj.MessageId, - obj.Headers, - obj.Payload, - parsedPayload, - obj.SentDate)); - - // Ignore acknowlegment of message is topic is not persistent - if (consumerConfiguration.IsTopicPersistent != true) - return; - if (isMessageAcknowledged.HasValue) - { - await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() - { - EntryId = obj.EntryId, - NodeId = obj.NodeId, - Acknowledgement = (int)isMessageAcknowledged.Value - }); - } - } - catch (Exception ex) - { - // ignore acknowlegment of message is topic is not persistent - if (consumerConfiguration.IsTopicPersistent != true) - return; - - await consumerNodeService.AcknowledgeMessage(new AcknowledgeMessageArgs() - { - EntryId = obj.EntryId, - NodeId = obj.NodeId, - Acknowledgement = (int)MessageAcknowledgement.Unacknowledged - }); - - logger.LogError($"MessageReceived failed to process, message is not acknowledged. Error description: '{ex.Message}'"); - } - } - - private void ConsumerNodeService_ConsumerDisconnected(ConsumerDisconnectedArgs obj) - { - logger.LogWarning($"andyx-client | Consumer '{obj.ConsumerName}|{obj.Id}' is disconnected"); - } - - private void ConsumerNodeService_ConsumerConnected(ConsumerConnectedArgs obj) - { - logger.LogWarning($"andyx-client | Consumer '{obj.ConsumerName}|{obj.Id}' is connected"); - } - - /// - /// Connect to component. - /// - /// Component Name - /// Instance of ConsumerBase for Topic Configuration. - public IConsumerTopicConnection ForComponent(string component) - { - return ForComponent(component, ""); - } - - /// - /// Connect to component with component token. - /// - /// Component name. - /// Component token - /// Instance of ConsumerBase for Topic Configuration. - public IConsumerTopicConnection ForComponent(string component, string token) - { - consumerConfiguration.Component = component; - consumerConfiguration.ComponentToken = token; - - return this; - } - - /// - /// Connect to persistent topic. - /// - /// Topic name - /// Instance of ConsumerBase for Name configuration. - public IConsumerNameConnection AndTopic(string topic) - { - return AndTopic(topic, true); - } - - /// - /// Connect to topic with as persistent or not. - /// - /// Topic name. - /// Topic type - /// Instance of ConsumerBase for Name configuration. - public IConsumerNameConnection AndTopic(string topic, bool isPersistent) - { - consumerConfiguration.Topic = topic; - consumerConfiguration.IsTopicPersistent = isPersistent; - - return this; - } - - /// - /// Give the name for the consumer. - /// - /// - /// Is recommended to call the consumer with the application name. - /// - /// Name of consumer - /// Instance of ConsumerBase for InitialPosition. - public IConsumerInitialPositionConnection WithName(string name) - { - consumerConfiguration.Name = name; - - return this; - } - - /// - /// InitialPosition tells the node where to start consuming - /// Latest - starts consuming from the moment of connection to topic, - /// Earlest - starts consuming from the beginning. - /// - /// Initial Position - /// Instance of ConsumerBase for SubscriptionType. - public IConsumerSubscriptionTypeConnection WithInitialPosition(InitialPosition initialPosition) - { - consumerConfiguration.SubscriptionSettings.InitialPosition = initialPosition; - - return this; - } - - /// - /// Configurate consumption type for the consumer. - /// - /// Subscription type - /// Instance of ConsumerBase. - public IConsumerOtherConfiguration AndSubscriptionType(SubscriptionType subscriptionType) - { - consumerConfiguration.SubscriptionSettings.SubscriptionType = subscriptionType; - - return this; - } - - - /// - /// Start consuming messages from the topic. - /// - /// Task - /// If consumer is not built before. - public async Task ConnectAsync() - { - if (isBuilt != true) - throw new Exception("Consumer should be built before subscribing to topic"); - - if (isConnected != true) - { - await consumerNodeService.ConnectAsync(); - isConnected = true; - } - } - - /// - /// Stop consuming of the messages from the topic. - /// - /// Task - /// If consumer is not built before. - public async Task DisconnectAsync() - { - if (isBuilt != true) - throw new Exception("Consumer should be built before unsubscribing to topic"); - if (isConnected == true) - { - await consumerNodeService.DisconnectAsync(); - isConnected = false; - } - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerComponentConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerComponentConnection.cs deleted file mode 100644 index 2b67152..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerComponentConnection.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Andy.X.Client.Abstractions.Consumers -{ - public interface IConsumerComponentConnection - { - IConsumerTopicConnection ForComponent(string component); - IConsumerTopicConnection ForComponent(string component, string token); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerInitialPositionConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerInitialPositionConnection.cs deleted file mode 100644 index f432b72..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerInitialPositionConnection.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Andy.X.Client.Configurations; - -namespace Andy.X.Client.Abstractions.Consumers -{ - public interface IConsumerInitialPositionConnection - { - IConsumerSubscriptionTypeConnection WithInitialPosition(InitialPosition initialPosition); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerNameConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerNameConnection.cs deleted file mode 100644 index 7707272..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerNameConnection.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Andy.X.Client.Abstractions.Consumers -{ - public interface IConsumerNameConnection - { - IConsumerInitialPositionConnection WithName(string name); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerOtherConfiguration.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerOtherConfiguration.cs deleted file mode 100644 index 6e183ce..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerOtherConfiguration.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading.Tasks; - -namespace Andy.X.Client.Abstractions.Consumers -{ - public interface IConsumerOtherConfiguration - { - Consumer Build(); - - Task ConnectAsync(); - Task DisconnectAsync(); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionTypeConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionTypeConnection.cs deleted file mode 100644 index c4779f4..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionTypeConnection.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Andy.X.Client.Configurations; - -namespace Andy.X.Client.Abstractions.Consumers -{ - public interface IConsumerSubscriptionTypeConnection - { - IConsumerOtherConfiguration AndSubscriptionType(SubscriptionType subscriptionType); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerTopicConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerTopicConnection.cs deleted file mode 100644 index dd5f270..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Consumers/IConsumerTopicConnection.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Andy.X.Client.Abstractions.Consumers -{ - public interface IConsumerTopicConnection - { - IConsumerNameConnection AndTopic(string topic); - IConsumerNameConnection AndTopic(string topic, bool isPersistent); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/IConsumer.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/IConsumer.cs deleted file mode 100644 index 5b11d40..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/IConsumer.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Andy.X.Client.Abstractions -{ - public interface IConsumer - { - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/IProducer.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/IProducer.cs deleted file mode 100644 index 46f0826..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/IProducer.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Andy.X.Client.Abstractions -{ - public interface IProducer - { - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs deleted file mode 100644 index c4ad88c..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/ProducerBase.cs +++ /dev/null @@ -1,436 +0,0 @@ -using Andy.X.Client.Configurations; -using Andy.X.Client.Events.Producers; -using Microsoft.AspNetCore.SignalR.Client; -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; -using System.Threading; -using Microsoft.Extensions.Logging; -using System.Collections.Generic; -using Andy.X.Client.Builders; -using Andy.X.Client.Abstractions.Producers; -using Andy.X.Client.Extensions; -using System.Text; -using MessagePack; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ProducerBase : - IProducerComponentConnection, - IProducerTopicConnection, - IProducerNameConnection, - IProducerOtherConfiguration - { - private readonly XClient _xClient; - private readonly ProducerConfiguration _producerConfiguration; - private readonly ILogger _logger; - - public delegate void OnMessageStoredHandler(object sender, MessageStoredArgs e); - public event OnMessageStoredHandler MessageStored; - - private ProducerNodeService producerNodeService; - private bool isBuilt = false; - private bool isConnected = false; - - private ConcurrentQueue unsentMessagesBuffer; - private bool isUnsentMessagesProcessorWorking = false; - - private Dictionary defaultHeaders; - - public ProducerBase(XClient xClient) : this(xClient, new ProducerConfiguration()) - { - - } - - public ProducerBase(IXClientFactory xClient) : this(xClient.CreateClient(), new ProducerConfiguration()) - { - - } - public ProducerBase(IXClientFactory xClient, ProducerConfiguration producerConfiguration) : this(xClient.CreateClient(), producerConfiguration) - { - - } - - public ProducerBase(IXClientFactory xClient, ProducerBuilder producerBuilder) : this(xClient.CreateClient(), producerBuilder.ProducerConfiguration) - { - - } - - public ProducerBase(XClient xClient, ProducerConfiguration producerConfiguration) - { - _xClient = xClient; - _producerConfiguration = producerConfiguration; - - _logger = _xClient.GetClientConfiguration() - .Logging - .GetLoggerFactory() - .CreateLogger(typeof(T)); - - if (producerConfiguration.RetryProducing == true) - unsentMessagesBuffer = new ConcurrentQueue(); - - defaultHeaders = new Dictionary(); - } - - /// - /// Close connection - /// - /// Task - /// Producer should be built before closing - public async Task CloseAsync() - { - if (isBuilt != true) - throw new Exception("Producer should be built before closing the connection"); - if (isConnected == true) - { - await producerNodeService.DisconnectAsync(); - isConnected = false; - } - } - - /// - /// Open connection and start producer - /// - /// Task - /// Producer should be built before closing - - public async Task OpenAsync() - { - if (isBuilt != true) - throw new Exception("Producer should be built before connecting the connection"); - - if (isConnected != true) - { - await producerNodeService.ConnectAsync(); - isConnected = true; - } - } - - - private void ProducerNodeService_MessageStored(MessageStoredArgs obj) - { - MessageStored?.Invoke(this, obj); - } - - private void ProducerNodeService_ProducerDisconnected(ProducerDisconnectedArgs obj) - { - _logger.LogWarning($"andyx-client | Producer '{obj.ProducerName}|{obj.Id}' is disconnected"); - } - - private void ProducerNodeService_ProducerConnected(ProducerConnectedArgs obj) - { - _logger.LogWarning($"andyx-client | Producer '{obj.ProducerName}|{obj.Id}' is connected"); - } - - public Guid Produce(T tObject, Dictionary headers = null) - { - return ProduceAsync(tObject, headers).Result; - } - - public List Produce(IList messages, Dictionary headers = null) - { - return ProduceAsync(messages, headers).Result; - } - - public async Task> ProduceAsync(IList messages, Dictionary headers = null) - { - headers = AddDefaultHeaderIntoMessage(headers); - var ids = new List(); - var messagesArgs = new List(); - foreach (var msg in messages) - { - Guid id = Guid.NewGuid(); - // MessagePackSerializer.Serialize(msg); - messagesArgs.Add(new TransmitMessageArgs() - { - Id = id.ToString(), - Tenant = _xClient.GetClientConfiguration().Tenant, - Product = _xClient.GetClientConfiguration().Product, - Component = _producerConfiguration.Component, - Topic = _producerConfiguration.Topic, - // this one works too, but for now we will use ContractlessStandardResolver. When we will create the schema registry we will enable resolvers. - //Payload = MessagePackSerializer.Typeless.Serialize(msg), - Payload = MessagePackSerializer.Serialize(msg, MessagePack.Resolvers.ContractlessStandardResolver.Options), - Headers = headers, - SentDate = DateTimeOffset.UtcNow, - }); - ids.Add(id); - } - - if (producerNodeService.GetConnectionState() == HubConnectionState.Connected) - { - try - { - await producerNodeService.TransmitMessages(messagesArgs); - return ids; - } - catch (Exception) - { - // moved the code to the end of method. - } - } - - // Messages are not send - messagesArgs.ForEach(x => - { - EnqueueMessageToBuffer(x); - }); - - return new List(); - } - - public async Task ProduceAsync(T message, Dictionary headers = null) - { - headers = AddDefaultHeaderIntoMessage(headers); - - var messageArgs = new TransmitMessageArgs() - { - Id = Guid.NewGuid().ToString(), - Tenant = _xClient.GetClientConfiguration().Tenant, - Product = _xClient.GetClientConfiguration().Product, - Component = _producerConfiguration.Component, - Topic = _producerConfiguration.Topic, - // this one works too, but for now we will use ContractlessStandardResolver. When we will create the schema registry we will enable resolvers. - //Payload = MessagePackSerializer.Typeless.Serialize(message), - Payload = MessagePackSerializer.Serialize(message, MessagePack.Resolvers.ContractlessStandardResolver.Options), - Headers = headers, - SentDate = DateTimeOffset.UtcNow, - }; - - if (producerNodeService.GetConnectionState() == HubConnectionState.Connected) - { - try - { - await producerNodeService.TransmitMessage(messageArgs); - return Guid.Parse(messageArgs.Id); - } - catch (Exception) - { - // moved the code to the end of method. - } - } - - EnqueueMessageToBuffer(messageArgs); - return Guid.Empty; - } - - private Dictionary AddDefaultHeaderIntoMessage(Dictionary headers) - { - if (headers == null) - headers = new Dictionary(); - - foreach (var defaultHeader in defaultHeaders) - { - headers.Add(defaultHeader.Key, defaultHeader.Value); - } - return headers; - } - - private void EnqueueMessageToBuffer(TransmitMessageArgs message) - { - _logger.LogWarning($"andyx-client | Producing of message '{message.Id}' at {message.Tenant}/{message.Product}/{message.Component}/{message.Topic} failed, retrying 1 of {_producerConfiguration.RetryProducingMessageNTimes} tires"); - if (_producerConfiguration.RetryProducing == true) - { - unsentMessagesBuffer.Enqueue(new RetryTransmitMessage() - { - TransmitMessageArgs = message, - RetryCounter = 1 - }); - - InitializeUnsentMessageProcessor(); - } - } - - #region UnsentMessageProcessor - private void InitializeUnsentMessageProcessor() - { - if (isUnsentMessagesProcessorWorking != true) - { - isUnsentMessagesProcessorWorking = true; - - new Thread(() => UnsentMessageProcessor()).Start(); - } - } - - private async void UnsentMessageProcessor() - { - while (unsentMessagesBuffer.IsEmpty != true) - { - RetryTransmitMessage retryTransmitMessage; - bool isMessageReturned = unsentMessagesBuffer.TryDequeue(out retryTransmitMessage); - if (isMessageReturned == true) - { - if (retryTransmitMessage.RetryCounter < _producerConfiguration.RetryProducingMessageNTimes) - { - retryTransmitMessage.RetryCounter++; - - if (producerNodeService.GetConnectionState() == HubConnectionState.Connected) - { - try - { - await producerNodeService.TransmitMessage(retryTransmitMessage.TransmitMessageArgs); - } - catch (Exception) - { - unsentMessagesBuffer.Enqueue(retryTransmitMessage); - } - } - else - unsentMessagesBuffer.Enqueue(retryTransmitMessage); - } - else - { - // If RetryCounter is bigger than RetryProducerMessageNTimes ignore that message. - _logger.LogError($"andyx-client | Producing of message '{retryTransmitMessage.TransmitMessageArgs.Id}' " + - $"at {retryTransmitMessage.TransmitMessageArgs.Tenant}/{retryTransmitMessage.TransmitMessageArgs.Product}" + - $"/{retryTransmitMessage.TransmitMessageArgs.Component}/{retryTransmitMessage.TransmitMessageArgs.Topic} failed, message is lost"); - } - } - } - isUnsentMessagesProcessorWorking = false; - } - #endregion - - /// - /// Connect to component. - /// - /// Component Name - /// Instance of ProducerBase for Topic Configuration. - public IProducerTopicConnection ForComponent(string component) - { - ForComponent(component, ""); - - return this; - } - - /// - /// Connect to component with component token. - /// - /// Component name. - /// Component token - /// Instance of ProducerBase for Topic Configuration. - public IProducerTopicConnection ForComponent(string component, string token) - { - _producerConfiguration.Component = component; - _producerConfiguration.ComponentToken = token; - - return this; - } - - /// - /// Connect to persistent topic. - /// - /// Topic name - /// Instance of ProducerBase for Name configuration. - public IProducerNameConnection AndTopic(string topic) - { - return AndTopic(topic, true); - } - - /// - /// Connect to topic with as persistent or not. - /// - /// Topic name. - /// Topic type - /// Instance of ProducerBase for Name configuration. - public IProducerNameConnection AndTopic(string topic, bool isPersistent) - { - _producerConfiguration.Topic = topic; - _producerConfiguration.IsTopicPersistent = isPersistent; - - return this; - } - - /// - /// Give the name for the producer. - /// - /// - /// Is recommended to call the producer with the application name. - /// - /// Name of producer - /// Instance of ProducerBase. - public IProducerOtherConfiguration WithName(string name) - { - _producerConfiguration.Name = name; - return this; - } - - /// - /// Add Headers to the message. - /// - /// key string value - /// value object for the header - /// Instance of ProducerBase. - public IProducerOtherConfiguration AddDefaultHeader(string key, string value) - { - defaultHeaders.Add(key, value); - return this; - } - - /// - /// Add Headers to the message. - /// - /// Dictionary object for the headers - /// Instance of ProducerBase. - public IProducerOtherConfiguration AddDefaultHeader(IDictionary headers) - { - foreach (var header in headers) - { - defaultHeaders.Add(header.Key, header.Value); - } - return this; - } - - /// - /// Enable retrying of message producing, if production of messages fails. - /// - /// Instance of ProducerBase. - public IProducerOtherConfiguration RetryProducingIfFails() - { - _producerConfiguration.RetryProducing = true; - unsentMessagesBuffer = new ConcurrentQueue(); - return this; - } - - /// - /// Configure the number of tries before droping the messages if connection fails. - /// - /// Max number of tries, type int - /// Instance of ProducerBase - public IProducerOtherConfiguration HowManyTimesToTryProducing(int nTimesRetry) - { - _producerConfiguration.RetryProducingMessageNTimes = nTimesRetry; - - return this; - } - - /// - /// Build Producer - /// - /// Producer object - public Producer Build() - { - // Add default headers - defaultHeaders.Add("andyx-client", "Andy X Client for .NET"); - defaultHeaders.Add("andyx-client-version", "v3.0.0-alpha1"); - defaultHeaders.Add("andyx-producer-name", _producerConfiguration.Name); - defaultHeaders.Add("andyx-content-type", "application/json"); - - producerNodeService = new ProducerNodeService(new ProducerNodeProvider(_xClient.GetClientConfiguration(), _producerConfiguration), _xClient.GetClientConfiguration()); - producerNodeService.ProducerConnected += ProducerNodeService_ProducerConnected; - producerNodeService.ProducerDisconnected += ProducerNodeService_ProducerDisconnected; - producerNodeService.MessageStored += ProducerNodeService_MessageStored; - producerNodeService.AndyOrderedDisconnect += ProducerNodeService_AndyOrderedDisconnect; - - isConnected = false; - isBuilt = true; - - return this as Producer; - } - - private async void ProducerNodeService_AndyOrderedDisconnect(string obj) - { - await producerNodeService.DisconnectAsync(); - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerComponentConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerComponentConnection.cs deleted file mode 100644 index 35ad076..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerComponentConnection.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Andy.X.Client.Abstractions.Producers -{ - public interface IProducerComponentConnection - { - IProducerTopicConnection ForComponent(string component); - IProducerTopicConnection ForComponent(string component, string token); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerNameConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerNameConnection.cs deleted file mode 100644 index 6540e97..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerNameConnection.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Andy.X.Client.Abstractions.Producers -{ - public interface IProducerNameConnection - { - IProducerOtherConfiguration WithName(string name); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerOtherConfiguration.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerOtherConfiguration.cs deleted file mode 100644 index 3e559cf..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerOtherConfiguration.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Andy.X.Client.Abstractions.Producers -{ - public interface IProducerOtherConfiguration - { - IProducerOtherConfiguration AddDefaultHeader(string key, string value); - IProducerOtherConfiguration AddDefaultHeader(IDictionary headers); - IProducerOtherConfiguration RetryProducingIfFails(); - IProducerOtherConfiguration HowManyTimesToTryProducing(int nTimesRetry); - - - Producer Build(); - Task OpenAsync(); - Task CloseAsync(); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerTopicConnection.cs b/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerTopicConnection.cs deleted file mode 100644 index 30f84fb..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Abstractions/Producers/IProducerTopicConnection.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Andy.X.Client.Abstractions.Producers -{ - public interface IProducerTopicConnection - { - IProducerNameConnection AndTopic(string topic); - IProducerNameConnection AndTopic(string topic, bool isPersistent); - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Andy.X.Client.csproj b/src/net/andy-x-client-lib/Andy.X.Client/Andy.X.Client.csproj deleted file mode 100644 index 85f182d..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Andy.X.Client.csproj +++ /dev/null @@ -1,107 +0,0 @@ - - - - net6.0;net5;netcoreapp3.1;netstandard2.1;netstandard2.0 - 3.0.0 - 3.0.0 - Buildersoft Andy X - Buildersoft - Buildersoft - Copyright © Buildersoft 2022 - - Andy X is an open-source distributed streaming platform designed to deliver the best performance possible for high-performance data pipelines, streaming analytics, streaming between microservices and data integrations. - This library offers flexible messaging models with high-level APIs for NET. - - https://github.com/buildersoftdev/andyxclient - andyx eda streaming distributed - 3.0.0-alpha1 - license.md - andyX.png - Buildersoft.Andy.X.Client - README.md - True - True - True - - In this version we added support for Bulk Producing, we re-designed the entire apis for Andy X Client. - https://buildersoftdev.azurewebsites.net/andyx - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - AnyCPU - - - - - - - - - - - - - - - True - - - - True - - - - True - - - - - - - - - diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Assets/andyX.png b/src/net/andy-x-client-lib/Andy.X.Client/Assets/andyX.png deleted file mode 100644 index 0411c961ec3e156c621bea6b4e9e28bcac07c439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58471 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 I*+2RJ01TmIVgLXD diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Assets/license.md b/src/net/andy-x-client-lib/Andy.X.Client/Assets/license.md deleted file mode 100644 index 6413433..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Assets/license.md +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2022 Buildersoft - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Builders/ConsumerBuilder.cs b/src/net/andy-x-client-lib/Andy.X.Client/Builders/ConsumerBuilder.cs deleted file mode 100644 index b7dd040..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Builders/ConsumerBuilder.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Andy.X.Client.Configurations; -using System; - -namespace Andy.X.Client.Builders -{ - public class ConsumerBuilder - { - public ConsumerConfiguration ConsumerConfiguration { get; private set; } - - public ConsumerBuilder(ConsumerConfiguration consumerConfiguration) - { - ConsumerConfiguration = consumerConfiguration; - } - - public ConsumerBuilder(Action> config) - { - ConsumerConfiguration = new ConsumerConfiguration(); - config.Invoke(ConsumerConfiguration); - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Builders/ProducerBuilder.cs b/src/net/andy-x-client-lib/Andy.X.Client/Builders/ProducerBuilder.cs deleted file mode 100644 index 707a162..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Builders/ProducerBuilder.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Andy.X.Client.Configurations; -using System; - -namespace Andy.X.Client.Builders -{ - public class ProducerBuilder - { - public ProducerConfiguration ProducerConfiguration { get; private set; } - - public ProducerBuilder(ProducerConfiguration producerConfiguration) - { - ProducerConfiguration = producerConfiguration; - } - - public ProducerBuilder(Action> config) - { - ProducerConfiguration = new ProducerConfiguration(); - config.Invoke(ProducerConfiguration); - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/ConsumerConfiguration.cs b/src/net/andy-x-client-lib/Andy.X.Client/Configurations/ConsumerConfiguration.cs deleted file mode 100644 index 1150850..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/ConsumerConfiguration.cs +++ /dev/null @@ -1,98 +0,0 @@ -namespace Andy.X.Client.Configurations -{ - public class ConsumerConfiguration - { - ///

- /// Component Token, is needed only if the node asks for it - /// - public string ComponentToken { get; set; } - - /// - /// Component name where consumer will consume. - /// - public string Component { get; set; } - - /// - /// Topic name where consumer will consume messages - /// - public string Topic { get; set; } - - /// - /// Name is the consumer name, is mandatory field. - /// Default value is Name=default - /// - public string Name { get; set; } - - /// - /// If topic doesn't exists it creates topic. If IsTopicPersistent is false it will create the topic without storing the messages permanently. - /// Default value is IsTopicPersistent=true - /// - public bool IsTopicPersistent { get; set; } - - public SubscriptionSettings SubscriptionSettings { get; set; } - - - public ConsumerConfiguration() - { - SubscriptionSettings = new SubscriptionSettings(); - IsTopicPersistent = true; - Name = "default"; - } - } - - public class SubscriptionSettings - { - public string SubscriptionName { get; set; } - /// - /// Subscription Type represents how the Consumer consumes messages - /// Default value SubscriptionType=Exclusive - /// - public SubscriptionType SubscriptionType { get; set; } - - /// - /// InitialPosition tells the node where to start consuming - /// Latest - starts consuming from the moment of connection to topic, - /// Earlest - starts consuming from the bigenning. - /// Default value is Latest - /// - public InitialPosition InitialPosition { get; set; } - public SubscriptionMode SubscriptionMode { get; set; } - - - public SubscriptionSettings() - { - SubscriptionType = SubscriptionType.Unique; - SubscriptionMode = SubscriptionMode.Resilient; - InitialPosition = InitialPosition.Latest; - } - } - - public enum SubscriptionType - { - /// - /// Only one consumer - /// - Unique, - /// - /// One consumer with one backup - /// - Failover, - /// - /// Shared to more than one consumer. - /// - Shared - } - - public enum SubscriptionMode - { - /// - /// Durable - /// - Resilient, - - /// - /// Non Durable - /// - NonResilient - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/InitialPosition.cs b/src/net/andy-x-client-lib/Andy.X.Client/Configurations/InitialPosition.cs deleted file mode 100644 index 20c1df8..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/InitialPosition.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Andy.X.Client.Configurations -{ - public enum InitialPosition - { - Earliest, - Latest - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/NodeConnectionType.cs b/src/net/andy-x-client-lib/Andy.X.Client/Configurations/NodeConnectionType.cs deleted file mode 100644 index 9f5da64..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/NodeConnectionType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Andy.X.Client.Nodes -{ - public enum NodeConnectionType - { - SSL, - NON_SSL - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/ProducerConfiguration.cs b/src/net/andy-x-client-lib/Andy.X.Client/Configurations/ProducerConfiguration.cs deleted file mode 100644 index 2ab199f..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/ProducerConfiguration.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace Andy.X.Client.Configurations -{ - public class ProducerConfiguration - { - /// - /// Component Token, is needed only if the node asks for it - /// - public string ComponentToken { get; set; } - - /// - /// Component name where consumer will consume. - /// - public string Component { get; set; } - - /// - /// If topic doesn't exists it creates topic. If IsTopicPersistent is false it will create the topic without storing the messages permanently. - /// Default value is IsTopicPersistent=true - /// - public bool IsTopicPersistent { get; set; } - - /// - /// Topic name where consumer will consume messages - /// - public string Topic { get; set; } - - /// - /// Name is the producer name, is mandatory field. - /// Default value is Name=default - /// - public string Name { get; set; } - - /// - /// If RetryProducing is TRUE, the client need a lot of memory if there will be a lot of unsendMessages - /// - public bool RetryProducing { get; set; } - - /// - /// Configure how many times should produce tries to produce undelivered messages. - /// - public int RetryProducingMessageNTimes { get; set; } - - public ProducerConfiguration() - { - RetryProducing = false; - RetryProducingMessageNTimes = 3; - IsTopicPersistent = true; - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/XClientConfiguration.cs b/src/net/andy-x-client-lib/Andy.X.Client/Configurations/XClientConfiguration.cs deleted file mode 100644 index ad3cb2d..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Configurations/XClientConfiguration.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.Extensions.Logging; -using System.Net.Http; - -namespace Andy.X.Client.Configurations -{ - public class XClientConfiguration - { - public string ServiceUrl { get; set; } - public string TenantToken { get; set; } - public string Tenant { get; set; } - public string Product { get; set; } - public bool AutoConnect { get; set; } - public HttpClientHandler HttpClientHandler { get; set; } - public XClientLogging Logging { get; set; } - - /// - /// Initialize XClient - /// - public XClientConfiguration() : this("https://localhost:6541") - { - - } - - /// - /// Initialize XClient - /// - /// serviceUrl, default value is https://localhost:6541 - public XClientConfiguration(string serviceUrl) - { - ServiceUrl = serviceUrl; - Tenant = "default"; - Product = "default"; - AutoConnect = true; - Logging = new XClientLogging(); - HttpClientHandler = new HttpClientHandler(); - } - } - - public class XClientLogging - { - private readonly ILoggerFactory _loggerFactory = null; - - public XClientLogging() - { - _loggerFactory = new LoggerFactory(); - } - - public XClientLogging(ILoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory; - } - - public ILoggerFactory GetLoggerFactory() - { - return _loggerFactory; - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Consumer.cs b/src/net/andy-x-client-lib/Andy.X.Client/Consumer.cs deleted file mode 100644 index 5aa48cf..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Consumer.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Andy.X.Client.Abstractions; -using Andy.X.Client.Abstractions.Consumers; -using Andy.X.Client.Builders; -using Andy.X.Client.Configurations; - -namespace Andy.X.Client -{ - public class Consumer : ConsumerBase, IConsumer - { - private Consumer(XClient xClient) : base(xClient) { } - - public static IConsumerComponentConnection CreateNewConsumer(XClient xClient) - { - return new Consumer(xClient); - } - - private Consumer(IXClientFactory xClient) : base(xClient) { } - public static IConsumerComponentConnection CreateNewConsumer(IXClientFactory xClient) - { - return new Consumer(xClient); - } - - - private Consumer(XClient xClient, ConsumerConfiguration consumerConfiguration) : base(xClient, consumerConfiguration) { } - public static IConsumerComponentConnection CreateNewConsumer(XClient xClient, ConsumerConfiguration consumerConfiguration) - { - return new Consumer(xClient, consumerConfiguration); - } - - private Consumer(IXClientFactory xClient, ConsumerConfiguration consumerConfiguration) : base(xClient, consumerConfiguration) { } - public static IConsumerComponentConnection CreateNewConsumer(IXClientFactory xClient, ConsumerConfiguration consumerConfiguration) - { - return new Consumer(xClient, consumerConfiguration); - } - - private Consumer(IXClientFactory xClient, ConsumerBuilder consumerBuilder) : base(xClient, consumerBuilder) { } - public static IConsumerComponentConnection CreateNewConsumer(IXClientFactory xClient, ConsumerBuilder consumerBuilder) - { - return new Consumer(xClient, consumerBuilder); - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs deleted file mode 100644 index 3e73a9f..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/AcknowledgeMessageArgs.cs +++ /dev/null @@ -1,27 +0,0 @@ -using MessagePack; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ConsumerBase - { - [MessagePackObject] - public class AcknowledgeMessageArgs - { - [Key(0)] - public long EntryId { get; set; } - - [Key(1)] - public string NodeId { get; set; } - - [Key(2)] - public int Acknowledgement { get; set; } - } - } - - public enum MessageAcknowledgement - { - Acknowledged, - Unacknowledged, - Skipped - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs deleted file mode 100644 index 34c33b6..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerConnectedArgs.cs +++ /dev/null @@ -1,29 +0,0 @@ -using MessagePack; -using System; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ConsumerBase - { - [MessagePackObject] - public class ConsumerConnectedArgs - { - [Key(0)] - public string Tenant { get; set; } - [Key(1)] - public string Product { get; set; } - [Key(2)] - public string Component { get; set; } - [Key(3)] - public string Topic { get; set; } - - [Key(4)] - public string Subscription { get; set; } - - [Key(5)] - public Guid Id { get; set; } - [Key(6)] - public string ConsumerName { get; set; } - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs deleted file mode 100644 index 85034c4..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/ConsumerDisconnectedArgs.cs +++ /dev/null @@ -1,26 +0,0 @@ -using MessagePack; -using System; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ConsumerBase - { - [MessagePackObject] - private class ConsumerDisconnectedArgs - { - [Key(0)] - public string Tenant { get; set; } - [Key(1)] - public string Product { get; set; } - [Key(2)] - public string Component { get; set; } - [Key(3)] - public string Topic { get; set; } - - [Key(4)] - public Guid Id { get; set; } - [Key(5)] - public string ConsumerName { get; set; } - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs deleted file mode 100644 index ec8b1fb..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageInternalReceivedArgs.cs +++ /dev/null @@ -1,40 +0,0 @@ -using MessagePack; -using System; -using System.Collections.Generic; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ConsumerBase - { - [MessagePackObject] - public class MessageInternalReceivedArgs - { - [Key(0)] - public string Tenant { get; set; } - [Key(1)] - public string Product { get; set; } - [Key(2)] - public string Component { get; set; } - [Key(3)] - public string Topic { get; set; } - - [Key(4)] - public long EntryId { get; set; } - - [Key(5)] - public string NodeId { get; set; } - - [Key(6)] - public Dictionary Headers { get; set; } - - [Key(7)] - public string MessageId { get; set; } - - [Key(8)] - public byte[] Payload { get; set; } - - [Key(9)] - public DateTimeOffset SentDate { get; set; } - } - } -} \ No newline at end of file diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs deleted file mode 100644 index 03a95d7..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Consumers/MessageReceivedArgs.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Andy.X.Client.Events.Consumers -{ - public class MessageReceivedArgs - { - public string Tenant { get; private set; } - public string Product { get; private set; } - public string Component { get; private set; } - public string Topic { get; private set; } - - public long EntryId { get; set; } - public string NodeId { get; set; } - - public string MessageId { get; private set; } - - public Dictionary Headers { get; set; } - - // Summary: - // Gets the message data as a byte[]. - public byte[] Payload { get; private set; } - public T GenericPayload { get; private set; } - - public DateTimeOffset SentDate { get; private set; } - public DateTimeOffset ReceivedDate { get; private set; } - - - - public MessageReceivedArgs(string tenant, - string product, - string component, - string topic, - long entryId, - string nodeId, - string messageId, - Dictionary headers, - byte[] payload, - T genericPayload, - DateTimeOffset sentDate) - { - Tenant = tenant; - Product = product; - Component = component; - Topic = topic; - - EntryId = entryId; - NodeId = nodeId; - - MessageId = messageId; - Headers = headers; - Payload = payload; - GenericPayload = genericPayload; - - SentDate = sentDate; - ReceivedDate = DateTimeOffset.UtcNow; - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/MessageStoredArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/MessageStoredArgs.cs deleted file mode 100644 index df16f1e..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/MessageStoredArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Andy.X.Client.Events.Producers -{ - public class MessageStoredArgs - { - public Guid Id { get; set; } - public DateTime StoredDate { get; set; } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs deleted file mode 100644 index 4436c03..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerConnectedArgs.cs +++ /dev/null @@ -1,26 +0,0 @@ -using MessagePack; -using System; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ProducerBase - { - [MessagePackObject] - public class ProducerConnectedArgs - { - [Key(0)] - public string Tenant { get; set; } - [Key(1)] - public string Product { get; set; } - [Key(2)] - public string Component { get; set; } - [Key(3)] - public string Topic { get; set; } - - [Key(4)] - public string Id { get; set; } - [Key(5)] - public string ProducerName { get; set; } - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs deleted file mode 100644 index ed085f9..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/ProducerDisconnectedArgs.cs +++ /dev/null @@ -1,26 +0,0 @@ -using MessagePack; -using System; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ProducerBase - { - [MessagePackObject] - public class ProducerDisconnectedArgs - { - [Key(0)] - public string Tenant { get; set; } - [Key(1)] - public string Product { get; set; } - [Key(2)] - public string Component { get; set; } - [Key(3)] - public string Topic { get; set; } - - [Key(4)] - public Guid Id { get; set; } - [Key(5)] - public string ProducerName { get; set; } - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/TransmitMessageArgs.cs b/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/TransmitMessageArgs.cs deleted file mode 100644 index b9ce3db..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Events/Producers/TransmitMessageArgs.cs +++ /dev/null @@ -1,44 +0,0 @@ -using MessagePack; -using System; -using System.Collections.Generic; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ProducerBase - { - [MessagePackObject] - public class TransmitMessageArgs - { - [Key(0)] - public string Tenant { get; set; } - [Key(1)] - public string Product { get; set; } - [Key(2)] - public string Component { get; set; } - [Key(3)] - public string Topic { get; set; } - - [Key(4)] - public Dictionary Headers { get; set; } - - [Key(5)] - public string Id { get; set; } - - [Key(6)] - public byte[] Payload { get; set; } - - [Key(7)] - public DateTimeOffset SentDate { get; set; } - } - - private class RetryTransmitMessage - { - public TransmitMessageArgs TransmitMessageArgs { get; set; } - public int RetryCounter { get; set; } - public RetryTransmitMessage() - { - RetryCounter = 0; - } - } - } -} \ No newline at end of file diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Extensions/Json.cs b/src/net/andy-x-client-lib/Andy.X.Client/Extensions/Json.cs deleted file mode 100644 index 273624a..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Extensions/Json.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Text.Json; - -namespace Andy.X.Client.Extensions -{ - public static class Json - { - public static string ToJson(this object obj) - { - return JsonSerializer.Serialize(obj, typeof(object), new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - IgnoreReadOnlyProperties = true, - }); - } - public static string ObjectToJson(this object obj) - { - return JsonSerializer.Serialize(obj, typeof(T), new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - IgnoreReadOnlyProperties = true, - }); - } - - public static string TryObjectToJson(this object obj) - { - try - { - return JsonSerializer.Serialize(obj, typeof(T), new JsonSerializerOptions() - { - - PropertyNameCaseInsensitive = true, - IgnoreReadOnlyProperties = true, - }); - } - catch (Exception) - { - return "{}"; - } - - } - - public static T JsonToObject(this string jsonMessage) - { - return (T)(JsonSerializer.Deserialize(jsonMessage, typeof(T), new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - IgnoreReadOnlyProperties = true, - })); - } - - public static dynamic JsonToDynamic(this string jsonMessage, Type type) - { - return (JsonSerializer.Deserialize(jsonMessage, type, new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - IgnoreReadOnlyProperties = true, - })) as dynamic; - } - - public static T TryJsonToObject(this string jsonMessage) - { - try - { - return (T)JsonSerializer.Deserialize(jsonMessage, typeof(T), new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - IgnoreReadOnlyProperties = true, - }); - } - catch (Exception) - { - return default(T); - } - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Producer.cs b/src/net/andy-x-client-lib/Andy.X.Client/Producer.cs deleted file mode 100644 index 26f09dd..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Producer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Andy.X.Client.Abstractions; -using Andy.X.Client.Abstractions.Producers; -using Andy.X.Client.Builders; -using Andy.X.Client.Configurations; - -namespace Andy.X.Client -{ - public class Producer : ProducerBase, IProducer - { - private Producer(XClient xClient) : base(xClient) { } - public static IProducerComponentConnection CreateNewProducer(XClient xClient) - { - return new Producer(xClient); - } - - private Producer(IXClientFactory xClient) : base(xClient) { } - public static IProducerComponentConnection CreateNewProducer(IXClientFactory xClient) - { - return new Producer(xClient); - } - - private Producer(XClient xClient, ProducerConfiguration producerConfiguration) : base(xClient, producerConfiguration) { } - public static IProducerComponentConnection CreateNewProducer(XClient xClient, ProducerConfiguration producerConfiguration) - { - return new Producer(xClient, producerConfiguration); - } - - private Producer(IXClientFactory xClient, ProducerConfiguration producerConfiguration) : base(xClient, producerConfiguration) { } - public static IProducerComponentConnection CreateNewProducer(IXClientFactory xClient, ProducerConfiguration producerConfiguration) - { - return new Producer(xClient, producerConfiguration); - } - - private Producer(IXClientFactory xClient, ProducerBuilder producerBuilder) : base(xClient, producerBuilder) { } - public static IProducerComponentConnection CreateNewProducer(IXClientFactory xClient, ProducerBuilder producerBuilder) - { - return new Producer(xClient, producerBuilder); - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs deleted file mode 100644 index 05ca9f7..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeProvider.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Andy.X.Client.Configurations; -using MessagePack; -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System.Net.Http; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ConsumerBase - { - private class ConsumerNodeProvider - { - private readonly XClientConfiguration xClientConfig; - private readonly ConsumerConfiguration consumerConfig; - - private readonly HubConnection _connection; - - public ConsumerNodeProvider(XClientConfiguration xClientConfig, ConsumerConfiguration consumerConfig) - { - this.xClientConfig = xClientConfig; - this.consumerConfig = consumerConfig; - - _connection = new HubConnectionBuilder() - .WithUrl($"{xClientConfig.ServiceUrl}/realtime/v3/consumer", option => - { - option.HttpMessageHandlerFactory = (message) => - { - return xClientConfig.HttpClientHandler; - }; - - - option.Headers["x-andyx-tenant-authoriziation"] = xClientConfig.TenantToken; - option.Headers["x-andyx-component-authoriziation"] = consumerConfig.ComponentToken; - - option.Headers["x-andyx-tenant"] = xClientConfig.Tenant; - option.Headers["x-andyx-product"] = xClientConfig.Product; - option.Headers["x-andyx-component"] = consumerConfig.Component; - option.Headers["x-andyx-topic"] = consumerConfig.Topic; - option.Headers["x-andyx-consumer-name"] = consumerConfig.Name; - option.Headers["x-andyx-topic-is-persistent"] = consumerConfig.IsTopicPersistent.ToString(); - - option.Headers["x-andyx-subscription-name"] = consumerConfig.SubscriptionSettings.SubscriptionName; - option.Headers["x-andyx-subscription-type"] = consumerConfig.SubscriptionSettings.SubscriptionType.ToString(); - option.Headers["x-andyx-subscription-mode"] = consumerConfig.SubscriptionSettings.SubscriptionMode.ToString(); - option.Headers["x-andyx-subscription-initial-position"] = consumerConfig.SubscriptionSettings.InitialPosition.ToString(); - }) - .WithAutomaticReconnect() - .AddMessagePackProtocol() - .ConfigureLogging(factory => - { - factory.AddSystemdConsole(); - factory.AddFilter("Console", level => level >= LogLevel.Information); - }) - .Build(); - } - public HubConnection GetHubConnection() - { - return _connection; - } - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeService.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeService.cs deleted file mode 100644 index dba38a7..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Consumers/ConsumerNodeService.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Andy.X.Client.Configurations; -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ConsumerBase - { - private class ConsumerNodeService - { - private readonly ILogger _logger; - - private readonly HubConnection _connection; - private readonly XClientConfiguration _xClientConfiguration; - - public event Action ConsumerConnected; - public event Action ConsumerDisconnected; - public event Action MessageInternalReceived; - public event Action AndyOrderedDisconnect; - - public ConsumerNodeService(ConsumerNodeProvider consumerNodeProvider, - XClientConfiguration xClientConfiguration) - { - - _logger = xClientConfiguration - .Logging - .GetLoggerFactory() - .CreateLogger(); - - _connection = consumerNodeProvider.GetHubConnection(); - - // events of consumer connection - _connection.Closed += Connection_Closed; - _connection.Reconnected += Connection_Reconnected; - _connection.Reconnecting += Connection_Reconnecting; - - _xClientConfiguration = xClientConfiguration; - - _connection.On("ConsumerConnected", connectedArgs => ConsumerConnected?.Invoke(connectedArgs)); - _connection.On("ConsumerDisconnected", disconnected => ConsumerDisconnected?.Invoke(disconnected)); - _connection.On("MessageSent", received => MessageInternalReceived?.Invoke(received)); - - _connection.On("AndyOrderedDisconnect", message => AndyOrderedDisconnect?.Invoke(message)); - } - - private Task Connection_Reconnecting(Exception arg) - { - _logger.LogWarning($"Consumer connection is lost, agent is reconnecting to node, details {arg.Message}"); - return Task.CompletedTask; - } - - private Task Connection_Reconnected(string arg) - { - _logger.LogInformation($"Consumer is reconnected"); - return Task.CompletedTask; - } - - private Task Connection_Closed(Exception arg) - { - _logger.LogError($"Consumer connection is closed, details {arg.Message}"); - return Task.CompletedTask; - } - - public async Task ConnectAsync() - { - await _connection.StartAsync().ContinueWith(async task => - { - if (task.Exception != null) - { - _logger.LogError($"Consumer failed to connect to Andy X Node, details {task.Exception.Message}"); - if (_xClientConfiguration.AutoConnect == true) - { - // retry connection - Thread.Sleep(3000); - _logger.LogWarning($"Consumer is connectiong to Andy X Node"); - await ConnectAsync(); - } - } - }); - } - - public async Task DisconnectAsync() - { - await _connection.StopAsync().ContinueWith(task => - { - if (task.Exception != null) - { - _logger.LogError($"Consumer failed to disconnect from Andy X Node, details {task.Exception.Message}"); - } - }); - } - public async Task AcknowledgeMessage(AcknowledgeMessageArgs acknowledgeMessageArgs) - { - await _connection.SendAsync("AcknowledgeMessage", acknowledgeMessageArgs); - } - } - } -} \ No newline at end of file diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs deleted file mode 100644 index 6b88765..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeProvider.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Andy.X.Client.Configurations; -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System.Net.Http; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ProducerBase - { - private class ProducerNodeProvider - { - private readonly XClientConfiguration xClientConfig; - private readonly ProducerConfiguration producerConfig; - - private readonly HubConnection _connection; - - public ProducerNodeProvider(XClientConfiguration xClientConfig, ProducerConfiguration producerConfig) - { - this.xClientConfig = xClientConfig; - this.producerConfig = producerConfig; - - _connection = new HubConnectionBuilder() - .WithUrl($"{xClientConfig.ServiceUrl}/realtime/v3/producer", option => - { - option.HttpMessageHandlerFactory = (message) => - { - return xClientConfig.HttpClientHandler; - }; - - - //option.HttpMessageHandlerFactory = (message) => - //{ - // if (message is HttpClientHandler clientHandler) - // // always verify the SSL certificate - // clientHandler.ServerCertificateCustomValidationCallback += - // (sender, certificate, chain, sslPolicyErrors) => { return true; }; - // return message; - //}; - - option.Headers["x-andyx-tenant-authoriziation"] = xClientConfig.TenantToken; - option.Headers["x-andyx-component-authoriziation"] = producerConfig.ComponentToken; - - option.Headers["x-andyx-tenant"] = xClientConfig.Tenant; - option.Headers["x-andyx-product"] = xClientConfig.Product; - option.Headers["x-andyx-component"] = producerConfig.Component; - option.Headers["x-andyx-topic"] = producerConfig.Topic; - option.Headers["x-andyx-producer-name"] = producerConfig.Name; - - option.Headers["x-andyx-topic-is-persistent"] = producerConfig.IsTopicPersistent.ToString(); - }) - .WithAutomaticReconnect() - .AddMessagePackProtocol() - .ConfigureLogging(factory => - { - factory.AddSystemdConsole(); - factory.AddFilter("Console", level => level >= LogLevel.Information); - }) - .Build(); - } - - public HubConnection GetHubConnection() - { - return _connection; - } - } - } - -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeService.cs b/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeService.cs deleted file mode 100644 index e2626dd..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/Providers/Producers/ProducerNodeService.cs +++ /dev/null @@ -1,112 +0,0 @@ -using Andy.X.Client.Configurations; -using Andy.X.Client.Events.Producers; -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Andy.X.Client.Abstractions -{ - public abstract partial class ProducerBase - { - private class ProducerNodeService - { - private readonly ILogger _logger; - - private readonly HubConnection _connection; - private readonly XClientConfiguration _xClientConfiguration; - - public event Action ProducerConnected; - public event Action ProducerDisconnected; - public event Action MessageStored; - public event Action AndyOrderedDisconnect; - - public ProducerNodeService(ProducerNodeProvider producerNodeProvider, - XClientConfiguration xClientConfiguration) - { - _logger = xClientConfiguration - .Logging - .GetLoggerFactory() - .CreateLogger(); - - _connection = producerNodeProvider.GetHubConnection(); - - // events of producer connection - _connection.Closed += Connection_Closed; - _connection.Reconnected += Connection_Reconnected; - _connection.Reconnecting += Connection_Reconnecting; - - _xClientConfiguration = xClientConfiguration; - - _connection.On("ProducerConnected", connectedArgs => ProducerConnected?.Invoke(connectedArgs)); - _connection.On("ProducerDisconnected", disconnected => ProducerDisconnected?.Invoke(disconnected)); - _connection.On("MessageStored", received => MessageStored?.Invoke(received)); - _connection.On("AndyOrderedDisconnect", message => AndyOrderedDisconnect?.Invoke(message)); - } - - private Task Connection_Reconnecting(Exception arg) - { - _logger.LogWarning($"Producer connection is lost, agent is reconnecting to node, details {arg.Message}"); - return Task.CompletedTask; - } - - private Task Connection_Reconnected(string arg) - { - _logger.LogInformation($"Producer is reconnected"); - return Task.CompletedTask; - } - - private Task Connection_Closed(Exception arg) - { - _logger.LogError($"Producer connection is closed, details {arg.Message}"); - return Task.CompletedTask; - } - - public async Task ConnectAsync() - { - await _connection.StartAsync().ContinueWith(async task => - { - if (task.Exception != null) - { - _logger.LogError($"Producer failed to connect to Andy X Node, details {task.Exception.Message}"); - if (_xClientConfiguration.AutoConnect == true) - { - // retry connection - Thread.Sleep(3000); - _logger.LogWarning($"Producer is connecting to Andy X Node"); - await ConnectAsync(); - } - } - }); - } - - public async Task DisconnectAsync() - { - await _connection.StopAsync().ContinueWith(task => - { - if (task.Exception != null) - { - _logger.LogError($"Producer failed to disconnect from Andy X Node, details {task.Exception.Message}"); - } - }); - } - - public async Task TransmitMessage(TransmitMessageArgs transmitMessageArgs) - { - await _connection.SendAsync("TransmitMessage", transmitMessageArgs); - } - - public async Task TransmitMessages(List transmitMessageArgs) - { - await _connection.SendAsync("TransmitMessages", transmitMessageArgs); - } - - public HubConnectionState GetConnectionState() - { - return _connection.State; - } - } - } -} diff --git a/src/net/andy-x-client-lib/Andy.X.Client/XClient.cs b/src/net/andy-x-client-lib/Andy.X.Client/XClient.cs deleted file mode 100644 index 71fc9a1..0000000 --- a/src/net/andy-x-client-lib/Andy.X.Client/XClient.cs +++ /dev/null @@ -1,142 +0,0 @@ -using Andy.X.Client.Abstractions.Client; -using Andy.X.Client.Configurations; -using Andy.X.Client.Nodes; -using Microsoft.Extensions.Logging; -using System; -using System.Net.Http; - -namespace Andy.X.Client -{ - public class XClient : IXClientServiceConnection, IXClientTenantConnection, IXClientProductConnection, IXClientConfiguration - { - private readonly XClientConfiguration _xClientConfiguration; - - /// - /// Initialize XClient object - /// serviceUrl default value is https://localhost:6541 - /// - /// xClient Configuration - private XClient(XClientConfiguration xClientConfiguration) - { - _xClientConfiguration = xClientConfiguration; - } - - public static IXClientServiceConnection CreateConnection() - { - return new XClient(new XClientConfiguration()); - } - - public static XClient CreateConnection(XClientConfiguration xClientConfiguration) - { - return new XClient(xClientConfiguration); - } - - - /// - /// Get ClientConfiguration. - /// - /// Object of XClientConfiguration. - public XClientConfiguration GetClientConfiguration() - { - return _xClientConfiguration; - } - - /// - /// Add Andy X Node Service. - /// - /// andy x node url, required to provide http or https also the port if is needed like http://localhost:6540. - /// Instance of XClient Builder for Tenant Configuration. - public IXClientTenantConnection ForService(string nodeUrl) - { - _xClientConfiguration.ServiceUrl = nodeUrl; - return this; - } - - /// - /// Add Andy X Node Service. - /// - /// XNode Hostname, by default is NON_SSL Connection - /// XNode Port - /// Instance of XClient Builder for Tenant Configuration. - public IXClientTenantConnection ForService(string nodeHostName, int hostPort) - { - _xClientConfiguration.ServiceUrl = $"http://{nodeHostName}:{hostPort}"; - return this; - } - - /// - /// Add Andy X Node Service. - /// - /// XNode Hostname - /// XNode Port - /// XNode Connection Type, can be SSL or NON_SSL Connection - /// Instance of XClient Builder for Tenant Configuration. - public IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType) - { - return ForService(nodeHostName, hostPort, nodeConnectionType, false); - } - - /// - /// Add Andy X Node Service. - /// - /// XNode Hostname - /// XNode Port - /// XNode Connection Type, can be SSL or NON_SSL Connection - /// This flag is applicable only if the connection is secured. - /// - public IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType, bool isSSLCertsSkipped) - { - if (nodeConnectionType == NodeConnectionType.SSL) - _xClientConfiguration.ServiceUrl = $"https://{nodeHostName}:{hostPort}"; - else - _xClientConfiguration.ServiceUrl = $"http://{nodeHostName}:{hostPort}"; - - if (isSSLCertsSkipped == true) - { - _xClientConfiguration.HttpClientHandler.ServerCertificateCustomValidationCallback += - (sender, certificate, chain, sslPolicyErrors) => { return true; }; - - - } - - return this; - } - - - public IXClientProductConnection AndTenant(string tenant) - { - return AndTenant(tenant, ""); - } - - public IXClientProductConnection AndTenant(string tenant, string token) - { - _xClientConfiguration.Tenant = tenant; - _xClientConfiguration.TenantToken = token; - - return this; - } - - public IXClientConfiguration AndProduct(string product) - { - _xClientConfiguration.Product = product; - return this; - } - - public IXClientConfiguration ConfigLogging(ILoggerFactory loggerFactory) - { - _xClientConfiguration.Logging = new XClientLogging(loggerFactory); - return this; - } - - public IXClientConfiguration WithHttpClientHandler(Action httpHandler) - { - httpHandler.Invoke(_xClientConfiguration.HttpClientHandler); - return this; - } - - public XClient Build() - { - return this; - } - } -} diff --git a/src/net/andy-x-client-lib/andy-x-client-lib.sln b/src/net/andy-x-client-lib/andy-x-client-lib.sln deleted file mode 100644 index 2ffb7b3..0000000 --- a/src/net/andy-x-client-lib/andy-x-client-lib.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31410.414 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Andy.X.Client", "Andy.X.Client\Andy.X.Client.csproj", "{893472D9-5E72-42F8-8E3D-2177315EFA15}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Andy.X.Client.Extensions.DependencyInjection", "Andy.X.Client.Extensions.DependencyInjection\Andy.X.Client.Extensions.DependencyInjection.csproj", "{861F3D68-ECF0-4419-9AF4-394D17273D66}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {893472D9-5E72-42F8-8E3D-2177315EFA15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {893472D9-5E72-42F8-8E3D-2177315EFA15}.Debug|Any CPU.Build.0 = Debug|Any CPU - {893472D9-5E72-42F8-8E3D-2177315EFA15}.Release|Any CPU.ActiveCfg = Release|Any CPU - {893472D9-5E72-42F8-8E3D-2177315EFA15}.Release|Any CPU.Build.0 = Release|Any CPU - {861F3D68-ECF0-4419-9AF4-394D17273D66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {861F3D68-ECF0-4419-9AF4-394D17273D66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {861F3D68-ECF0-4419-9AF4-394D17273D66}.Release|Any CPU.ActiveCfg = Release|Any CPU - {861F3D68-ECF0-4419-9AF4-394D17273D66}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E20F501A-34F6-4D61-B92C-EE463AC7FBEB} - EndGlobalSection -EndGlobal From ea5a9b7419e739c4086065097e683c2f685733ca Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Fri, 21 Oct 2022 22:29:58 +0200 Subject: [PATCH 06/13] v3/main Add online check for XClient. While building the XClient it will connect to the server side and it will ask for the name, and version. --- .dockerignore | 25 ---------- examples/andyx-examples.sln | 25 ---------- .../andyx-examples/Models/SimpleMessage.cs | 9 ---- examples/andyx-examples/Program.cs | 34 ------------- examples/andyx-examples/SimpleConsumer.cs | 44 ----------------- examples/andyx-examples/SimpleProducer.cs | 33 ------------- examples/andyx-examples/andyx-examples.csproj | 13 ----- .../Abstractions/XClients/IXClient.cs | 5 ++ src/Andy.X.Client/Andy.X.Client.csproj | 6 +-- .../Configurations/XClientConfiguration.cs | 4 ++ .../Configurations/XClientCredentials.cs | 15 ++++++ src/Andy.X.Client/Consumer.cs | 2 +- .../InternalServices/XClientHttpService.cs | 49 +++++++++++++++++++ .../Models/Internal/ApplicationDetails.cs | 9 ++++ src/Andy.X.Client/Producer.cs | 2 +- .../Utilities/ApplicationParameters.cs | 7 +++ src/Andy.X.Client/XClient.cs | 40 +++++++++++++-- src/Andy.X.Client/XClientConnection.cs | 24 +++++++++ 18 files changed, 154 insertions(+), 192 deletions(-) delete mode 100644 .dockerignore delete mode 100644 examples/andyx-examples.sln delete mode 100644 examples/andyx-examples/Models/SimpleMessage.cs delete mode 100644 examples/andyx-examples/Program.cs delete mode 100644 examples/andyx-examples/SimpleConsumer.cs delete mode 100644 examples/andyx-examples/SimpleProducer.cs delete mode 100644 examples/andyx-examples/andyx-examples.csproj create mode 100644 src/Andy.X.Client/Configurations/XClientCredentials.cs create mode 100644 src/Andy.X.Client/InternalServices/XClientHttpService.cs create mode 100644 src/Andy.X.Client/Models/Internal/ApplicationDetails.cs create mode 100644 src/Andy.X.Client/Utilities/ApplicationParameters.cs create mode 100644 src/Andy.X.Client/XClientConnection.cs diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 3729ff0..0000000 --- a/.dockerignore +++ /dev/null @@ -1,25 +0,0 @@ -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/azds.yaml -**/bin -**/charts -**/docker-compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -LICENSE -README.md \ No newline at end of file diff --git a/examples/andyx-examples.sln b/examples/andyx-examples.sln deleted file mode 100644 index 7fe16ac..0000000 --- a/examples/andyx-examples.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "andyx-examples", "andyx-examples\andyx-examples.csproj", "{07B2AACD-D290-4E9B-8781-9D019ED098A6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {07B2AACD-D290-4E9B-8781-9D019ED098A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07B2AACD-D290-4E9B-8781-9D019ED098A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07B2AACD-D290-4E9B-8781-9D019ED098A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07B2AACD-D290-4E9B-8781-9D019ED098A6}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {420562BA-D325-402D-B9BF-E4214ABF39EB} - EndGlobalSection -EndGlobal diff --git a/examples/andyx-examples/Models/SimpleMessage.cs b/examples/andyx-examples/Models/SimpleMessage.cs deleted file mode 100644 index e1a860b..0000000 --- a/examples/andyx-examples/Models/SimpleMessage.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace andyx_examples.Models -{ - public class SimpleMessage - { - public int Id { get; set; } - public string Name { get; set; } - public string Message { get; set; } - } -} diff --git a/examples/andyx-examples/Program.cs b/examples/andyx-examples/Program.cs deleted file mode 100644 index a13eb79..0000000 --- a/examples/andyx-examples/Program.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; - -namespace andyx_examples -{ - internal class Program - { - static void Main(string[] args) - { - Console.WriteLine("Andy X Client"); - Console.WriteLine(".NET Example"); - Console.WriteLine("Simple Consumer and Producer"); - - // initialize Consumer - SimpleConsumer consumer = new SimpleConsumer(); - - // initialize Producer - Console.WriteLine($"Production started"); - Stopwatch stopwatch = Stopwatch.StartNew(); - SimpleProducer producer = new SimpleProducer(); - for (int i = 0; i < 20000000; i++) - { - producer.ProduceSimpleMessages(i, "Andy", "Hello for Andy X the newest distributed streaming platform!"); - Thread.Sleep(2); - } - - stopwatch.Stop(); - Console.WriteLine($"Finished for : {stopwatch.Elapsed.TotalSeconds} sec"); - - Console.ReadLine(); - } - } -} diff --git a/examples/andyx-examples/SimpleConsumer.cs b/examples/andyx-examples/SimpleConsumer.cs deleted file mode 100644 index da7edfe..0000000 --- a/examples/andyx-examples/SimpleConsumer.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Andy.X.Client; -using Andy.X.Client.Configurations; -using Andy.X.Client.Nodes; -using andyx_examples.Models; -using System; - -namespace andyx_examples -{ - public class SimpleConsumer - { - private readonly Consumer consumer; - public SimpleConsumer() - { - XClient client = XClient.CreateConnection() - .ForService("192.168.0.24", 6540, NodeConnectionType.NON_SSL) - .AndTenant("default") - .AndProduct("default") - .Build(); - - consumer = Consumer.CreateNewConsumer(client) - .ForComponent("simple") - .AndTopic("simple-message") - .WithName("simple-consumer") - .WithInitialPosition(InitialPosition.Earliest) - .AndSubscriptionType(SubscriptionType.Exclusive) - .Build(); - - consumer.MessageReceived += Consumer_MessageReceived; - - consumer - .ConnectAsync() - .Wait(); - } - int k = 0; - private bool Consumer_MessageReceived(object sender, Andy.X.Client.Events.Consumers.MessageReceivedArgs e) - { - k++; - Console.WriteLine($"index:{k} => Message arrived: payload as raw: '{e.Payload}'; payload as simpleMessage name='{e.GenericPayload.Name}'"); - - // Message acknowledged - return true; - } - } -} diff --git a/examples/andyx-examples/SimpleProducer.cs b/examples/andyx-examples/SimpleProducer.cs deleted file mode 100644 index d01942b..0000000 --- a/examples/andyx-examples/SimpleProducer.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Andy.X.Client; -using Andy.X.Client.Nodes; -using andyx_examples.Models; - -namespace andyx_examples -{ - public class SimpleProducer - { - private readonly Producer producer; - public SimpleProducer() - { - XClient client = XClient.CreateConnection() - .ForService("192.168.0.24", 6540, NodeConnectionType.NON_SSL) - .AndTenant("default") - .AndProduct("default") - .Build(); - - producer = Producer.CreateNewProducer(client) - .ForComponent("simple") - .AndTopic("simple-message") - .WithName("simple-producer") - .Build(); - - producer.OpenAsync().Wait(); - } - - public void ProduceSimpleMessages(int id, string name, string message) - { - var simpleMessage = new SimpleMessage() { Id = id, Name = name, Message = message }; - producer.Produce(simpleMessage); - } - } -} diff --git a/examples/andyx-examples/andyx-examples.csproj b/examples/andyx-examples/andyx-examples.csproj deleted file mode 100644 index 6a4c5f1..0000000 --- a/examples/andyx-examples/andyx-examples.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - Exe - net6.0 - andyx_examples - - - - - - - diff --git a/src/Andy.X.Client/Abstractions/XClients/IXClient.cs b/src/Andy.X.Client/Abstractions/XClients/IXClient.cs index 3dce80f..e828911 100644 --- a/src/Andy.X.Client/Abstractions/XClients/IXClient.cs +++ b/src/Andy.X.Client/Abstractions/XClients/IXClient.cs @@ -5,5 +5,10 @@ namespace Andy.X.Client.Abstractions.XClients public interface IXClient : IXClientServiceConnection, IXClientTenantConnection, IXClientProductConnection, IXClientConfiguration { XClientConfiguration GetClientConfiguration(); + + XConnectionState GetState(); + string GetServerName(); + string GetServerVersion(); + void UpdateConnection(); } } diff --git a/src/Andy.X.Client/Andy.X.Client.csproj b/src/Andy.X.Client/Andy.X.Client.csproj index ee534da..2aaed64 100644 --- a/src/Andy.X.Client/Andy.X.Client.csproj +++ b/src/Andy.X.Client/Andy.X.Client.csproj @@ -9,7 +9,7 @@ Buildersoft Buildersoft Copyright © Buildersoft 2022 - + Andy X is an open-source distributed streaming platform designed to deliver the best performance possible for high-performance data pipelines, streaming analytics, streaming between microservices and data integrations. This library offers flexible messaging models with high-level APIs for NET. @@ -17,7 +17,7 @@ https://github.com/buildersoftdev/andyxclient andyx eda streaming distributed - + 3.0.0 license.md andyX.png @@ -26,7 +26,7 @@ True True - In this version we added support for Bulk Producing, we re-designed the entire apis for Andy X Client. + Added support to check client connectivity online while building the Client. Here is totally new library developed for Andy X v3. https://buildersoftdev.azurewebsites.net/andyx
diff --git a/src/Andy.X.Client/Configurations/XClientConfiguration.cs b/src/Andy.X.Client/Configurations/XClientConfiguration.cs index c0b210a..abb5e26 100644 --- a/src/Andy.X.Client/Configurations/XClientConfiguration.cs +++ b/src/Andy.X.Client/Configurations/XClientConfiguration.cs @@ -41,6 +41,8 @@ public class XClientSettings public int ConnectionTimeoutMs { get; set; } public int ReconnectionTimeoutMs { get; set; } + public bool ShouldCheckOnline { get; set; } + public XClientSettings() { @@ -50,6 +52,8 @@ public XClientSettings() EnableAutoReconnect = true; ConnectionTimeoutMs = 10000; ReconnectionTimeoutMs = 1000; + + ShouldCheckOnline = true; } } diff --git a/src/Andy.X.Client/Configurations/XClientCredentials.cs b/src/Andy.X.Client/Configurations/XClientCredentials.cs new file mode 100644 index 0000000..002de19 --- /dev/null +++ b/src/Andy.X.Client/Configurations/XClientCredentials.cs @@ -0,0 +1,15 @@ +namespace Andy.X.Client.Configurations +{ + public sealed class XClientCredentials + { + // this class will be needed for getting the cluster configuration. + public string Username { get; set; } + public string Password { get; set; } + + public XClientCredentials() + { + Username = ""; + Password = ""; + } + } +} diff --git a/src/Andy.X.Client/Consumer.cs b/src/Andy.X.Client/Consumer.cs index b90e571..3303fed 100644 --- a/src/Andy.X.Client/Consumer.cs +++ b/src/Andy.X.Client/Consumer.cs @@ -7,7 +7,7 @@ namespace Andy.X.Client { - public class Consumer : ConsumerBase + public sealed class Consumer : ConsumerBase { private Consumer(IXClient xClient) : base(xClient) { diff --git a/src/Andy.X.Client/InternalServices/XClientHttpService.cs b/src/Andy.X.Client/InternalServices/XClientHttpService.cs new file mode 100644 index 0000000..b38f018 --- /dev/null +++ b/src/Andy.X.Client/InternalServices/XClientHttpService.cs @@ -0,0 +1,49 @@ +using Andy.X.Client.Configurations; +using Andy.X.Client.Models.Internal; +using Andy.X.Client.Utilities; +using System; +using System.Net.Http; +using System.Text.Json; + +namespace Andy.X.Client.InternalServices +{ + internal class XClientHttpService + { + private readonly XClientConfiguration _xClientConfiguration; + private HttpClient _httpClient; + public XClientHttpService(XClientConfiguration xClientConfiguration) + { + _xClientConfiguration = xClientConfiguration; + } + + public XClientConnection GetApplicationDetails() + { + _httpClient = new HttpClient(_xClientConfiguration.Settings.HttpClientHandler); + _httpClient.DefaultRequestHeaders.Add("x-called-by", ApplicationParameters.LibraryName); + + string request = $"{_xClientConfiguration.NodeUrl.AbsoluteUri}api/v3/node/version"; + + try + { + HttpResponseMessage httpResponseMessage = _httpClient.GetAsync(request).Result; + string content = httpResponseMessage.Content.ReadAsStringAsync().Result; + if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.OK) + { + var applicationDetails = JsonSerializer.Deserialize(content); + return new XClientConnection(XConnectionState.Open, applicationDetails.Name, applicationDetails.Version); + } + + if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.Unauthorized || + httpResponseMessage.StatusCode == System.Net.HttpStatusCode.Forbidden) + throw new Exception("Credentials of Andy X are not correct"); + + } + catch (Exception) + { + + } + + return null; + } + } +} diff --git a/src/Andy.X.Client/Models/Internal/ApplicationDetails.cs b/src/Andy.X.Client/Models/Internal/ApplicationDetails.cs new file mode 100644 index 0000000..9a50584 --- /dev/null +++ b/src/Andy.X.Client/Models/Internal/ApplicationDetails.cs @@ -0,0 +1,9 @@ +namespace Andy.X.Client.Models.Internal +{ + internal class ApplicationDetails + { + public string Name { get; set; } + public string ShortName { get; set; } + public string Version { get; set; } + } +} diff --git a/src/Andy.X.Client/Producer.cs b/src/Andy.X.Client/Producer.cs index 905ef72..8bd0c77 100644 --- a/src/Andy.X.Client/Producer.cs +++ b/src/Andy.X.Client/Producer.cs @@ -7,7 +7,7 @@ namespace Andy.X.Client { - public class Producer : ProducerBase + public sealed class Producer : ProducerBase { private Producer(IXClient xClient) : base(xClient) { diff --git a/src/Andy.X.Client/Utilities/ApplicationParameters.cs b/src/Andy.X.Client/Utilities/ApplicationParameters.cs new file mode 100644 index 0000000..0be622c --- /dev/null +++ b/src/Andy.X.Client/Utilities/ApplicationParameters.cs @@ -0,0 +1,7 @@ +namespace Andy.X.Client.Utilities +{ + internal static class ApplicationParameters + { + internal static string LibraryName = "andyx-client for .net"; + } +} diff --git a/src/Andy.X.Client/XClient.cs b/src/Andy.X.Client/XClient.cs index 9cb75d6..af82db4 100644 --- a/src/Andy.X.Client/XClient.cs +++ b/src/Andy.X.Client/XClient.cs @@ -1,21 +1,23 @@ using Andy.X.Client.Abstractions.XClients; using Andy.X.Client.Configurations; +using Andy.X.Client.InternalServices; using System; namespace Andy.X.Client { - public class XClient : IXClient + public sealed class XClient : IXClient { private readonly XClientConfiguration _configuration; + private XClientConnection _clientConnection; - private XClient() + private XClient() : this(new XClientConfiguration()) { - _configuration = new XClientConfiguration(); } private XClient(XClientConfiguration configuration) { _configuration = configuration; + _clientConnection = new XClientConnection(state: XConnectionState.Unknown, server: "n/a", version: "n/a"); } public static IXClientServiceConnection CreateClient() @@ -62,6 +64,14 @@ public IXClientProductConnection AndTenant(string tenant, string key, string sec public XClient Build() { + if (_configuration.Settings.ShouldCheckOnline == true) + { + var clientHttpService = new XClientHttpService(_configuration); + var clientCon = clientHttpService.GetApplicationDetails(); + if (clientCon != null) + _clientConnection = clientCon; + } + return this; } @@ -72,7 +82,6 @@ public IXClientTenantConnection ForService(Uri nodeUrl) return this; } - public IXClientTenantConnection ForService(string nodeHostName, int hostPort, NodeConnectionType nodeConnectionType) { if (nodeConnectionType == NodeConnectionType.NON_SSL) @@ -109,5 +118,28 @@ public XClientConfiguration GetClientConfiguration() { return _configuration; } + + public XConnectionState GetState() + { + return _clientConnection.State; + } + + public string GetServerName() + { + return _clientConnection.Server; + } + + public string GetServerVersion() + { + return _clientConnection.Version; + } + + public void UpdateConnection() + { + var clientHttpService = new XClientHttpService(_configuration); + var clientCon = clientHttpService.GetApplicationDetails(); + if (clientCon != null) + _clientConnection = clientCon; + } } } diff --git a/src/Andy.X.Client/XClientConnection.cs b/src/Andy.X.Client/XClientConnection.cs new file mode 100644 index 0000000..be742b9 --- /dev/null +++ b/src/Andy.X.Client/XClientConnection.cs @@ -0,0 +1,24 @@ +namespace Andy.X.Client +{ + public sealed class XClientConnection + { + public XConnectionState State { get; private set; } + + public string Server { get; private set; } + public string Version { get; private set; } + public XClientConnection(XConnectionState state, string server, string version) + { + State = state; + Server = server; + Version = version; + } + } + + public enum XConnectionState + { + Open, + Closed, + Invalid, + Unknown + } +} From 5b81df9d5f25bbd451bcf00a84d4e09265c825b6 Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Fri, 21 Oct 2022 22:53:51 +0200 Subject: [PATCH 07/13] v3/main update JsonSerializer Settings --- src/Andy.X.Client/InternalServices/XClientHttpService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Andy.X.Client/InternalServices/XClientHttpService.cs b/src/Andy.X.Client/InternalServices/XClientHttpService.cs index b38f018..34b0ea5 100644 --- a/src/Andy.X.Client/InternalServices/XClientHttpService.cs +++ b/src/Andy.X.Client/InternalServices/XClientHttpService.cs @@ -29,7 +29,9 @@ public XClientConnection GetApplicationDetails() string content = httpResponseMessage.Content.ReadAsStringAsync().Result; if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.OK) { - var applicationDetails = JsonSerializer.Deserialize(content); + var applicationDetails = JsonSerializer.Deserialize(content, + new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); + return new XClientConnection(XConnectionState.Open, applicationDetails.Name, applicationDetails.Version); } From 5d21bcd0f449a40f3687f6576367c35eeba77424 Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Thu, 3 Nov 2022 15:27:09 +0100 Subject: [PATCH 08/13] v3/feature/29: Implementation of Cluster API Add Cluster, Add XClientCredentials, Implement Cluster Public API --- .../Abstractions/Base/ProducerBase.cs | 6 +- src/Andy.X.Client/Cluster.cs | 44 ++++++++++++++ .../Configurations/XClientCredentials.cs | 12 ++-- src/Andy.X.Client/Consumer.cs | 2 +- .../InternalServices/XClientHttpService.cs | 28 +++++++++ src/Andy.X.Client/Models/Internal/Cluster.cs | 60 +++++++++++++++++++ src/Andy.X.Client/Producer.cs | 2 +- .../Utilities/AuthorizationExtensions.cs | 17 ++++++ 8 files changed, 160 insertions(+), 11 deletions(-) create mode 100644 src/Andy.X.Client/Cluster.cs create mode 100644 src/Andy.X.Client/Models/Internal/Cluster.cs create mode 100644 src/Andy.X.Client/Utilities/AuthorizationExtensions.cs diff --git a/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs b/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs index 107bfe4..e4caa24 100644 --- a/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs +++ b/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs @@ -279,11 +279,11 @@ private MessageId CheckForCallbackResponse(Guid identityId, MessageId result) if (_producerConfiguration.Settings.RequireCallback == true) { int milisecondsWait = 0; - int sleepTime = 10; + int sleepTimeMilisec = 1; while (callBackResponses.ContainsKey(identityId) != true) { - Thread.Sleep(sleepTime); - milisecondsWait += sleepTime; + Thread.Sleep(sleepTimeMilisec); + milisecondsWait += sleepTimeMilisec; if (milisecondsWait >= _producerConfiguration.Settings.TimeoutInSyncResponseMs) throw new Exception($"Run in timeout, couldnot get response from Andy X. TimeoutInSyncResponseMs: {_producerConfiguration.Settings.TimeoutInSyncResponseMs}. You can increase TimeoutInSyncResponseMs at Producer Settings."); diff --git a/src/Andy.X.Client/Cluster.cs b/src/Andy.X.Client/Cluster.cs new file mode 100644 index 0000000..7b03915 --- /dev/null +++ b/src/Andy.X.Client/Cluster.cs @@ -0,0 +1,44 @@ +using Andy.X.Client.Abstractions.XClients; +using Andy.X.Client.InternalServices; +using Andy.X.Client.Models.Internal; +using Andy.X.Security.Credentials; +using System.Collections.Generic; + +namespace Andy.X.Client +{ + public sealed class Cluster + { + private readonly IXClient _xClient; + private readonly XClientCredentials _xClientCredentials; + private readonly ClusterDetails _clusterDetails; + + public Cluster(IXClient xClient, XClientCredentials xClientCredentials) + { + _xClient = xClient; + _xClientCredentials = xClientCredentials; + + var restService = new XClientHttpService(xClient.GetClientConfiguration()); + _clusterDetails = restService.GetClusterDetails(xClientCredentials.Username, xClientCredentials.Password); + + if (_clusterDetails == null) + { + throw new System.Exception("Credentials of Andy X are not correct"); + } + } + + public string GetClusterName() + { + return _clusterDetails.Name; + } + + public ClusterStatus GetClusterStatus() + { + return _clusterDetails.Status; + } + + public List GetShards() + { + return _clusterDetails.Shards; + } + } +} diff --git a/src/Andy.X.Client/Configurations/XClientCredentials.cs b/src/Andy.X.Client/Configurations/XClientCredentials.cs index 002de19..4e44cda 100644 --- a/src/Andy.X.Client/Configurations/XClientCredentials.cs +++ b/src/Andy.X.Client/Configurations/XClientCredentials.cs @@ -1,15 +1,15 @@ -namespace Andy.X.Client.Configurations +namespace Andy.X.Security.Credentials { public sealed class XClientCredentials { // this class will be needed for getting the cluster configuration. - public string Username { get; set; } - public string Password { get; set; } + public string Username { get; private set; } + public string Password { get; private set; } - public XClientCredentials() + public XClientCredentials(string username, string password) { - Username = ""; - Password = ""; + Username = username; + Password = password; } } } diff --git a/src/Andy.X.Client/Consumer.cs b/src/Andy.X.Client/Consumer.cs index 3303fed..b90e571 100644 --- a/src/Andy.X.Client/Consumer.cs +++ b/src/Andy.X.Client/Consumer.cs @@ -7,7 +7,7 @@ namespace Andy.X.Client { - public sealed class Consumer : ConsumerBase + public class Consumer : ConsumerBase { private Consumer(IXClient xClient) : base(xClient) { diff --git a/src/Andy.X.Client/InternalServices/XClientHttpService.cs b/src/Andy.X.Client/InternalServices/XClientHttpService.cs index 34b0ea5..67410e5 100644 --- a/src/Andy.X.Client/InternalServices/XClientHttpService.cs +++ b/src/Andy.X.Client/InternalServices/XClientHttpService.cs @@ -34,6 +34,33 @@ public XClientConnection GetApplicationDetails() return new XClientConnection(XConnectionState.Open, applicationDetails.Name, applicationDetails.Version); } + } + catch (Exception) + { + + } + + return null; + } + + public ClusterDetails GetClusterDetails(string username, string password) + { + _httpClient = new HttpClient(_xClientConfiguration.Settings.HttpClientHandler); + _httpClient.DefaultRequestHeaders.Add("x-called-by", ApplicationParameters.LibraryName); + _httpClient.AddBasicAuthorizationHeader(username, password); + + string request = $"{_xClientConfiguration.NodeUrl.AbsoluteUri}api/v3/clusters"; + + try + { + HttpResponseMessage httpResponseMessage = _httpClient.GetAsync(request).Result; + string content = httpResponseMessage.Content.ReadAsStringAsync().Result; + if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.OK) + { + var clusterDetails = JsonSerializer.Deserialize(content, + new JsonSerializerOptions() { PropertyNameCaseInsensitive = true, }); + return clusterDetails; + } if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.Unauthorized || httpResponseMessage.StatusCode == System.Net.HttpStatusCode.Forbidden) @@ -47,5 +74,6 @@ public XClientConnection GetApplicationDetails() return null; } + } } diff --git a/src/Andy.X.Client/Models/Internal/Cluster.cs b/src/Andy.X.Client/Models/Internal/Cluster.cs new file mode 100644 index 0000000..a3410a7 --- /dev/null +++ b/src/Andy.X.Client/Models/Internal/Cluster.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Andy.X.Client.Models.Internal +{ + internal class ClusterDetails + { + public string Name { get; set; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public DistributionTypes ShardDistributionType { get; set; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public ClusterStatus Status { get; set; } + + public List Shards { get; set; } + } + + public enum ClusterStatus + { + Online, + PartiallyOnline, + Starting, + Offline, + Restarting, + Recovering, + Disconnecting, + } + + public enum DistributionTypes + { + // Sync distribution will not be supported for andyx v3.0.0 + Sync, + Async, + } + + public class Shard + { + [JsonConverter(typeof(JsonStringEnumConverter))] + public DistributionTypes ReplicaDistributionType { get; set; } + public List Replicas { get; set; } + } + + public class Replica + { + public string NodeId { get; set; } + public string Host { get; set; } + public string Port { get; set; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public ReplicaTypes Type { get; set; } + } + + public enum ReplicaTypes + { + Main, + Worker, + BackupReplica + } +} diff --git a/src/Andy.X.Client/Producer.cs b/src/Andy.X.Client/Producer.cs index 8bd0c77..905ef72 100644 --- a/src/Andy.X.Client/Producer.cs +++ b/src/Andy.X.Client/Producer.cs @@ -7,7 +7,7 @@ namespace Andy.X.Client { - public sealed class Producer : ProducerBase + public class Producer : ProducerBase { private Producer(IXClient xClient) : base(xClient) { diff --git a/src/Andy.X.Client/Utilities/AuthorizationExtensions.cs b/src/Andy.X.Client/Utilities/AuthorizationExtensions.cs new file mode 100644 index 0000000..593cf0e --- /dev/null +++ b/src/Andy.X.Client/Utilities/AuthorizationExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Net.Http; +using System.Text; + +namespace Andy.X.Client.Utilities +{ + internal static class AuthorizationExtensions + { + internal static void AddBasicAuthorizationHeader(this HttpClient httpClient, string username, string password) + { + string encodedPassword = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1") + .GetBytes(username + ":" + password)); + + httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + encodedPassword); + } + } +} From cf2ddf54386c089e440ff90111e1dea5bcb70e42 Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Thu, 10 Nov 2022 13:50:33 +0100 Subject: [PATCH 09/13] v3/feature/31 Implementation of Data Compression Add CompressionType, Implement CompressionType on ConsumerBase, Implement CompressionType on ProducerBase --- .../Abstractions/Base/ConsumerBase.cs | 27 ++++++++++++++++--- .../Abstractions/Base/ProducerBase.cs | 26 +++++++++++++++--- .../Abstractions/Consumers/IConsumer.cs | 2 +- .../Consumers/IConsumerSettings.cs | 10 +++++++ .../IConsumerSubscriptionConfiguration.cs | 2 +- .../Configurations/CompressionType.cs | 9 +++++++ .../Configurations/ConsumerConfiguration.cs | 5 ++++ .../Configurations/ProducerConfiguration.cs | 7 ++++- 8 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 src/Andy.X.Client/Abstractions/Consumers/IConsumerSettings.cs create mode 100644 src/Andy.X.Client/Configurations/CompressionType.cs diff --git a/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs b/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs index eaa7f26..81a493c 100644 --- a/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs +++ b/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs @@ -9,7 +9,6 @@ using MessagePack; using Microsoft.Extensions.Logging; using System; -using System.Net.Http.Headers; using System.Threading.Tasks; namespace Andy.X.Client.Abstractions.Base @@ -25,6 +24,10 @@ public abstract class ConsumerBase : IConsumer private ConsumerNodeProvider consumerNodeProvider; private ConsumerNodeService consumerNodeService; + + private MessagePackSerializerOptions messagePackSerializerOptions; + + private bool isBuilt = false; public ConsumerBase(IXClient xClient) : this(xClient, new ConsumerConfiguration()) @@ -59,7 +62,7 @@ public ConsumerBase(IXClient xClient, ConsumerConfiguration consumerConfiguratio } - public IConsumerConfiguration AndSubscription(Action config) + public IConsumerSettings AndSubscription(Action config) { config.Invoke(_consumerConfiguration.Subscription); @@ -126,6 +129,22 @@ public Consumer Build() consumerNodeProvider = new ConsumerNodeProvider(_xClient.GetClientConfiguration(), _consumerConfiguration); consumerNodeService = new ConsumerNodeService(consumerNodeProvider, _xClient.GetClientConfiguration()); + switch (_consumerConfiguration.Settings.CompressionType) + { + case CompressionType.None: + messagePackSerializerOptions = MessagePack.Resolvers.ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None); + break; + case CompressionType.Lz4Block: + messagePackSerializerOptions = MessagePack.Resolvers.ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4Block); + break; + case CompressionType.Lz4BlockArray: + messagePackSerializerOptions = MessagePack.Resolvers.ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4BlockArray); + break; + default: + messagePackSerializerOptions = MessagePack.Resolvers.ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None); + break; + } + consumerNodeService.ConsumerConnected += ConsumerNodeService_ConsumerConnected; consumerNodeService.ConsumerDisconnected += ConsumerNodeService_ConsumerDisconnected; consumerNodeService.AndyOrderedDisconnect += ConsumerNodeService_AndyOrderedDisconnect; @@ -169,8 +188,8 @@ public Task UnacknowledgeMessage(Message message) private void ConsumerNodeService_MessageInternalReceived(Events.Consumers.MessageInternalReceivedArgs obj) { - K keyParsed = MessagePackSerializer.Deserialize(obj.MessageId, MessagePack.Resolvers.ContractlessStandardResolver.Options); - V valueParsed = MessagePackSerializer.Deserialize(obj.Payload, MessagePack.Resolvers.ContractlessStandardResolver.Options); + K keyParsed = MessagePackSerializer.Deserialize(obj.MessageId, messagePackSerializerOptions); + V valueParsed = MessagePackSerializer.Deserialize(obj.Payload, messagePackSerializerOptions); _clientAction?.Invoke(keyParsed, new Message( diff --git a/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs b/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs index e4caa24..0f6d133 100644 --- a/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs +++ b/src/Andy.X.Client/Abstractions/Base/ProducerBase.cs @@ -30,6 +30,8 @@ public abstract class ProducerBase : IProducer private ProducerNodeProvider producerNodeProvider; private ConcurrentDictionary callBackResponses; + private MessagePackSerializerOptions messagePackSerializerOptions; + private bool isBuilt = false; #region Constuctors @@ -109,6 +111,22 @@ public Producer Build() if (_producerConfiguration.Settings.RequireCallback == true) callBackResponses = new ConcurrentDictionary(); + switch (_producerConfiguration.Settings.CompressionType) + { + case CompressionType.None: + messagePackSerializerOptions = MessagePack.Resolvers.ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None); + break; + case CompressionType.Lz4Block: + messagePackSerializerOptions = MessagePack.Resolvers.ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4Block); + break; + case CompressionType.Lz4BlockArray: + messagePackSerializerOptions = MessagePack.Resolvers.ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4BlockArray); + break; + default: + messagePackSerializerOptions = MessagePack.Resolvers.ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None); + break; + } + _headers.Add("andyx-client", "Andy X Client for .NET"); _headers.Add("andyx-client-version", "v3.0.0-alpha10"); _headers.Add("andyx-producer-name", _producerConfiguration.Name); @@ -185,8 +203,8 @@ await producerNodeService.TransmitMessage(new TransmitMessageArgs() IdentityId = identityId, Headers = headers as Dictionary, - Id = MessagePackSerializer.Serialize(id, MessagePack.Resolvers.ContractlessStandardResolver.Options), - Payload = MessagePackSerializer.Serialize(message, MessagePack.Resolvers.ContractlessStandardResolver.Options), + Id = MessagePackSerializer.Serialize(id, messagePackSerializerOptions), + Payload = MessagePackSerializer.Serialize(message, messagePackSerializerOptions), SentDate = DateTimeOffset.UtcNow, NodeId = sendNodeId, @@ -225,8 +243,8 @@ public async Task SendAsync(IDictionary messages, IDictionary, - Id = MessagePackSerializer.Serialize(message.Key, MessagePack.Resolvers.ContractlessStandardResolver.Options), - Payload = MessagePackSerializer.Serialize(message.Value, MessagePack.Resolvers.ContractlessStandardResolver.Options), + Id = MessagePackSerializer.Serialize(message.Key, messagePackSerializerOptions), + Payload = MessagePackSerializer.Serialize(message.Value, messagePackSerializerOptions), SentDate = DateTimeOffset.UtcNow, NodeId = sendNodeId, diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs index 52b5aa5..d9d66ee 100644 --- a/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs @@ -5,7 +5,7 @@ namespace Andy.X.Client.Abstractions.Consumers { - public interface IConsumer : IConsumerComponentConnection, IConsumerConfiguration, IConsumerConnection, IConsumerSubscriptionConfiguration, IConsumerTopicConnection, IConsumerMessageHandler + public interface IConsumer : IConsumerComponentConnection, IConsumerConfiguration, IConsumerConnection, IConsumerSubscriptionConfiguration, IConsumerTopicConnection, IConsumerMessageHandler, IConsumerSettings { IConsumerMessageHandler MessageReceivedHandler(Action> messageReceived); diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerSettings.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerSettings.cs new file mode 100644 index 0000000..effae8d --- /dev/null +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumerSettings.cs @@ -0,0 +1,10 @@ +using Andy.X.Client.Configurations; +using System; + +namespace Andy.X.Client.Abstractions.Consumers +{ + public interface IConsumerSettings + { + IConsumerConfiguration WithSettings(Action settings); + } +} diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs index eb7a534..fcc46dc 100644 --- a/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs @@ -5,6 +5,6 @@ namespace Andy.X.Client.Abstractions.Consumers { public interface IConsumerSubscriptionConfiguration { - IConsumerConfiguration AndSubscription(Action config); + IConsumerSettings AndSubscription(Action config); } } diff --git a/src/Andy.X.Client/Configurations/CompressionType.cs b/src/Andy.X.Client/Configurations/CompressionType.cs new file mode 100644 index 0000000..3ace651 --- /dev/null +++ b/src/Andy.X.Client/Configurations/CompressionType.cs @@ -0,0 +1,9 @@ +namespace Andy.X.Client.Configurations +{ + public enum CompressionType + { + None, + Lz4Block, + Lz4BlockArray + } +} diff --git a/src/Andy.X.Client/Configurations/ConsumerConfiguration.cs b/src/Andy.X.Client/Configurations/ConsumerConfiguration.cs index 61ecfc5..2b9417b 100644 --- a/src/Andy.X.Client/Configurations/ConsumerConfiguration.cs +++ b/src/Andy.X.Client/Configurations/ConsumerConfiguration.cs @@ -33,6 +33,11 @@ public ConsumerConfiguration(string name, SubscriptionConfiguration subscription public class ConsumerSettings { + public CompressionType CompressionType { get; set; } + public ConsumerSettings() + { + CompressionType = CompressionType.None; + } } } diff --git a/src/Andy.X.Client/Configurations/ProducerConfiguration.cs b/src/Andy.X.Client/Configurations/ProducerConfiguration.cs index 490c996..7dcbc4a 100644 --- a/src/Andy.X.Client/Configurations/ProducerConfiguration.cs +++ b/src/Andy.X.Client/Configurations/ProducerConfiguration.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using MessagePack; +using System.Collections.Generic; namespace Andy.X.Client.Configurations { @@ -46,6 +47,8 @@ public class ProducerSettings public bool RequireCallback { get; set; } + public CompressionType CompressionType { get; set; } + public ProducerSettings() { EnableRetryProducing = false; @@ -55,6 +58,8 @@ public ProducerSettings() TimeoutInSyncResponseMs = 3000; BreakIfTryToSendMessageInClosedConneciton = false; RequireCallback = true; + + CompressionType = CompressionType.None; } } } From 3878fa63c8ff7b47ecec0e30a94bdb520b3a3a22 Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Thu, 10 Nov 2022 14:00:27 +0100 Subject: [PATCH 10/13] Add support for .net7.0 --- .../Andy.X.Client.Extensions.DependencyInjection.csproj | 2 +- src/Andy.X.Client/Andy.X.Client.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj b/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj index 6747091..787cb68 100644 --- a/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj +++ b/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj @@ -1,7 +1,7 @@  - net6.0;net5;netcoreapp3.1;netstandard2.1 + net7.0;net6.0;net5;netcoreapp3.1;netstandard2.1 3.0.0 3.0.0 Buildersoft Andy X diff --git a/src/Andy.X.Client/Andy.X.Client.csproj b/src/Andy.X.Client/Andy.X.Client.csproj index 2aaed64..7e75549 100644 --- a/src/Andy.X.Client/Andy.X.Client.csproj +++ b/src/Andy.X.Client/Andy.X.Client.csproj @@ -1,7 +1,7 @@  - net6.0;net5;netcoreapp3.1;netstandard2.1 + net7.0;net6.0;net5;netcoreapp3.1;netstandard2.1 3.0.0 3.0.0 From 77449d72ff7dd32f602d1f88654ad1b3dfae30fa Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Thu, 10 Nov 2022 14:04:27 +0100 Subject: [PATCH 11/13] Remove support for .NET7 --- ...Andy.X.Client.Extensions.DependencyInjection.csproj | 2 +- .../Abstractions/Consumers/IConsumerConfiguration.cs | 8 +++++--- .../Abstractions/Consumers/IConsumerSettings.cs | 10 ---------- .../Consumers/IConsumerSubscriptionConfiguration.cs | 2 +- src/Andy.X.Client/Andy.X.Client.csproj | 2 +- 5 files changed, 8 insertions(+), 16 deletions(-) delete mode 100644 src/Andy.X.Client/Abstractions/Consumers/IConsumerSettings.cs diff --git a/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj b/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj index 787cb68..6747091 100644 --- a/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj +++ b/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj @@ -1,7 +1,7 @@  - net7.0;net6.0;net5;netcoreapp3.1;netstandard2.1 + net6.0;net5;netcoreapp3.1;netstandard2.1 3.0.0 3.0.0 Buildersoft Andy X diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerConfiguration.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerConfiguration.cs index 92df971..6f32744 100644 --- a/src/Andy.X.Client/Abstractions/Consumers/IConsumerConfiguration.cs +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumerConfiguration.cs @@ -1,10 +1,12 @@ -namespace Andy.X.Client.Abstractions.Consumers +using Andy.X.Client.Configurations; +using System; + +namespace Andy.X.Client.Abstractions.Consumers { public interface IConsumerConfiguration { // we do not have settings for consumers for now. - //IConsumerConfiguration WithSettings(Action settings); - + IConsumerConfiguration WithSettings(Action settings); Consumer Build(); } } \ No newline at end of file diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerSettings.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerSettings.cs deleted file mode 100644 index effae8d..0000000 --- a/src/Andy.X.Client/Abstractions/Consumers/IConsumerSettings.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Andy.X.Client.Configurations; -using System; - -namespace Andy.X.Client.Abstractions.Consumers -{ - public interface IConsumerSettings - { - IConsumerConfiguration WithSettings(Action settings); - } -} diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs index fcc46dc..eb7a534 100644 --- a/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumerSubscriptionConfiguration.cs @@ -5,6 +5,6 @@ namespace Andy.X.Client.Abstractions.Consumers { public interface IConsumerSubscriptionConfiguration { - IConsumerSettings AndSubscription(Action config); + IConsumerConfiguration AndSubscription(Action config); } } diff --git a/src/Andy.X.Client/Andy.X.Client.csproj b/src/Andy.X.Client/Andy.X.Client.csproj index 7e75549..2aaed64 100644 --- a/src/Andy.X.Client/Andy.X.Client.csproj +++ b/src/Andy.X.Client/Andy.X.Client.csproj @@ -1,7 +1,7 @@  - net7.0;net6.0;net5;netcoreapp3.1;netstandard2.1 + net6.0;net5;netcoreapp3.1;netstandard2.1 3.0.0 3.0.0 From 750249a8b7b38b40dea3e45e47227ab667683ce6 Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Thu, 10 Nov 2022 18:02:23 +0100 Subject: [PATCH 12/13] Fix bug on build --- src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs | 2 +- src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs | 2 +- src/Andy.X.Client/Configurations/EncryptionSettings.cs | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 src/Andy.X.Client/Configurations/EncryptionSettings.cs diff --git a/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs b/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs index 81a493c..34cbba0 100644 --- a/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs +++ b/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs @@ -62,7 +62,7 @@ public ConsumerBase(IXClient xClient, ConsumerConfiguration consumerConfiguratio } - public IConsumerSettings AndSubscription(Action config) + public IConsumerConfiguration AndSubscription(Action config) { config.Invoke(_consumerConfiguration.Subscription); diff --git a/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs b/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs index d9d66ee..52b5aa5 100644 --- a/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs +++ b/src/Andy.X.Client/Abstractions/Consumers/IConsumer.cs @@ -5,7 +5,7 @@ namespace Andy.X.Client.Abstractions.Consumers { - public interface IConsumer : IConsumerComponentConnection, IConsumerConfiguration, IConsumerConnection, IConsumerSubscriptionConfiguration, IConsumerTopicConnection, IConsumerMessageHandler, IConsumerSettings + public interface IConsumer : IConsumerComponentConnection, IConsumerConfiguration, IConsumerConnection, IConsumerSubscriptionConfiguration, IConsumerTopicConnection, IConsumerMessageHandler { IConsumerMessageHandler MessageReceivedHandler(Action> messageReceived); diff --git a/src/Andy.X.Client/Configurations/EncryptionSettings.cs b/src/Andy.X.Client/Configurations/EncryptionSettings.cs new file mode 100644 index 0000000..7eee597 --- /dev/null +++ b/src/Andy.X.Client/Configurations/EncryptionSettings.cs @@ -0,0 +1,7 @@ +namespace Andy.X.Client.Configurations +{ + public class EncryptionSettings + { + public string PublicKey { get; set; } + } +} From 325c423197a702a0a86d5fabb50502a7b447a73a Mon Sep 17 00:00:00 2001 From: Enes Hoxha Date: Tue, 22 Nov 2022 14:21:52 +0100 Subject: [PATCH 13/13] last changes for prod release of v3 --- .github/workflows/dotnet.yml | 25 ------------------- README.md | 6 ++++- ...ient.Extensions.DependencyInjection.csproj | 9 +++++-- .../Abstractions/Base/ConsumerBase.cs | 1 - src/Andy.X.Client/Andy.X.Client.csproj | 11 +++++--- 5 files changed, 20 insertions(+), 32 deletions(-) delete mode 100644 .github/workflows/dotnet.yml diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml deleted file mode 100644 index a73cc8b..0000000 --- a/.github/workflows/dotnet.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: .NET - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 5.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal diff --git a/README.md b/README.md index 9adde4b..a1eb24c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +

+ Andy X Logo +

+ What is Andy X Client? ============ Andy X is an open-source distributed streaming platform designed to deliver the best performance possible for high-performance data pipelines, streaming analytics, streaming between microservices and data integrations. @@ -16,7 +20,7 @@ and make pull-requests. ## Reporting security issues and bugs -Security issues and bugs should be reported privately, via email, en.buildersoft@gmail.com. You should receive a response within 24 hours. +Security issues and bugs should be reported privately, via email, support@buildersoft.io. You should receive a response within 24 hours. ## Related projects diff --git a/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj b/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj index 6747091..6ea808c 100644 --- a/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj +++ b/src/Andy.X.Client.Extensions.DependencyInjection/Andy.X.Client.Extensions.DependencyInjection.csproj @@ -1,7 +1,7 @@  - net6.0;net5;netcoreapp3.1;netstandard2.1 + net7.0;net6.0;net5;netcoreapp3.1;netstandard2.1 3.0.0 3.0.0 Buildersoft Andy X @@ -10,7 +10,7 @@ Copyright © Buildersoft 2022 - Andy X Client extensions for ASP.NET Core +Andy X Client extensions for ASP.NET Core https://github.com/buildersoftdev/andyxclient @@ -25,6 +25,7 @@ True https://buildersoftdev.azurewebsites.net/andyx + README.md @@ -36,6 +37,10 @@ + + True + \ + True diff --git a/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs b/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs index 34cbba0..ee842c0 100644 --- a/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs +++ b/src/Andy.X.Client/Abstractions/Base/ConsumerBase.cs @@ -15,7 +15,6 @@ namespace Andy.X.Client.Abstractions.Base { public abstract class ConsumerBase : IConsumer { - private Action> _clientAction; private readonly ILogger _logger; diff --git a/src/Andy.X.Client/Andy.X.Client.csproj b/src/Andy.X.Client/Andy.X.Client.csproj index 2aaed64..5306cf6 100644 --- a/src/Andy.X.Client/Andy.X.Client.csproj +++ b/src/Andy.X.Client/Andy.X.Client.csproj @@ -1,7 +1,7 @@  - net6.0;net5;netcoreapp3.1;netstandard2.1 + net7.0;net6.0;net5;netcoreapp3.1;netstandard2.1 3.0.0 3.0.0 @@ -11,8 +11,8 @@ Copyright © Buildersoft 2022 - Andy X is an open-source distributed streaming platform designed to deliver the best performance possible for high-performance data pipelines, streaming analytics, streaming between microservices and data integrations. - This library offers flexible messaging models with high-level APIs for NET. +Andy X is an open-source distributed streaming platform designed to deliver the best performance possible for high-performance data pipelines, streaming analytics, streaming between microservices and data integrations. +This library offers flexible messaging models with high-level APIs for NET. https://github.com/buildersoftdev/andyxclient @@ -28,6 +28,7 @@ Added support to check client connectivity online while building the Client. Here is totally new library developed for Andy X v3. https://buildersoftdev.azurewebsites.net/andyx + README.md @@ -44,6 +45,10 @@ + + True + \ + True