diff --git a/README.md b/README.md index 8f76d10..b33b8ed 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,6 @@ Do you need to reliably broadcast messages between Actors and Services? This code will help you do that. It supports both Actors and Services as publishers and subscribers. -It uses extension methods to -- Actor -- StatelessService -- StatefulService - ## Contribute! Contributions are welcome. Please upgrade the package version with a minor tick if there are no breaking changes. And add a line to the readme.md, stating the changes, e.g. 'upgraded to SF version x.y.z'. @@ -17,6 +12,7 @@ Please also make sure all feature additions have a corresponding unit test. ## Release notes: +- 8.0.0 Major cleanup and usability improvements. Replaced Helper classes with a single `BrokerClient`. Removed obsolete code (BrokerActor, RelayBrokerActor, extension methods). Removed the `ServiceFabric.PubSubActors.Interfaces` library. Simplified the BrokerService interface. - 7.6.2 Fix routing key issue. - 7.6.1 Fix hashing helper null ref issue. - 7.6.0 Add routing key support, to support attribute based messaging. Fix hashing issue in dotnet core. @@ -58,7 +54,6 @@ Please also make sure all feature additions have a corresponding unit test. ## Nuget: https://www.nuget.org/packages/ServiceFabric.PubSubActors -https://www.nuget.org/packages/ServiceFabric.PubSubActors.Interfaces (for Actor interfaces) ## Getting started @@ -66,9 +61,8 @@ https://www.nuget.org/packages/ServiceFabric.PubSubActors.Interfaces (for Actor Using this package you can reliably send messages from Publishers (Actors/Services) to many Subscribers (Actors/Services). -This is done using an intermediate, which is the BrokerActor or BrokerService. +This is done using an intermediate, which is the BrokerService. Add this package to all Reliable Actor & Service projects that participate in the pub/sub messaging. -Add the package 'ServiceFabric.PubSubActors.Interfaces' to all (*ReliableActor).Interfaces projects. | publisher | broker |subscriber| @@ -88,7 +82,6 @@ Add the package 'ServiceFabric.PubSubActors.Interfaces' to all (*ReliableActor). Add a new Stateful Reliable Service project. Call it 'PubSubService' (optional). Add Nuget package 'ServiceFabric.PubSubActors' to the 'PubSubActor' project -Add Nuget package 'ServiceFabric.PubSubActors.Interfaces' to the project. Replace the code of PubSubService with the following code: ```csharp internal sealed class PubSubService : BrokerService @@ -96,8 +89,8 @@ internal sealed class PubSubService : BrokerService public PubSubService(StatefulServiceContext context) : base(context) { - //optional: provide a logging callback - ServiceEventSourceMessageCallback = message => ServiceEventSource.Current.ServiceMessage(this, message); + //optional: provide a logging callback + ServiceEventSourceMessageCallback = message => ServiceEventSource.Current.ServiceMessage(this, message); } } ``` @@ -123,37 +116,52 @@ public class PublishedMessageTwo } ``` +### Publishing messages from Service or Actor +```csharp +var brokerClient = new BrokerClient(); +brokerClient.PublishMessageAsync(new PublishedMessageOne { Content = "Hello PubSub World, from Subscriber, using Broker Service!" }) +``` + ### Subscribing to messages using Actors *Create a sample Actor that implements 'ISubscriberActor', to become a subscriber to messages.* In this example, the Actor called 'SubscribingActor' subscribes to messages of Type 'PublishedMessageOne'. Add a Reliable Stateless Actor project called 'SubscribingActor'. Add Nuget package 'ServiceFabric.PubSubActors' to the 'SubscribingActor' project -Add Nuget package 'ServiceFabric.PubSubActors.Interfaces' to the 'SubscribingActor.Interfaces' project. Add a project reference to the shared data contracts library ('DataContracts'). -Go to the SubscribingActor.Interfaces project, open the file 'ISubscribingActor' and replace the contents with this code: -**notice this implements ISubscriberActor from the package 'ServiceFabric.PubSubActors.Interfaces' which adds a Receive method. The additional methods are to enable this actor to be manipulated from the outside.** +Open the file 'SubscribingActor.cs' and replace the contents with the code below. +**notice that this Actor now implements 'ISubscriberActor' indirectly.** ```csharp -public interface ISubscribingActor : ISubscriberActor +[ActorService(Name = nameof(SubscribingActor))] +[StatePersistence(StatePersistence.None)] +internal class SubscribingActor : Actor, ISubscriberActor +{ + private readonly IBrokerClient _brokerClient; + + public SubscribingActor(ActorService actorService, ActorId actorId, IBrokerClient brokerClient) + : base(actorService, actorId) { - // allow external callers to manipulate register/unregister on this sample actor: - //for regular messaging: - Task RegisterAsync(); - Task UnregisterAsync(); - //for relayed messaging: - Task RegisterWithRelayAsync(); - Task UnregisterWithRelayAsync(); - //for service broker messaging: - Task RegisterWithBrokerServiceAsync(); - Task UnregisterWithBrokerServiceAsync(); + _brokerClient = brokerClient ?? new BrokerClient(); } -``` -Open the file 'SubscribingActor.cs' and replace the contents with the code below. -**notice that this Actor now implements 'ISubscriberActor' indirectly.** -https://github.com/loekd/ServiceFabric.PubSubActors/blob/master/ServiceFabric.PubSubActors.Demo/SubscribingActor/SubscribingActor.cs + protected override async Task OnActivateAsync() + { + await _brokerClient.SubscribeAsync(this, HandleMessageOne); + } + public Task ReceiveMessageAsync(MessageWrapper message) + { + return _brokerClient.ProcessMessageAsync(message); + } + + private Task HandleMessageOne(PublishedMessageOne message) + { + ActorEventSource.Current.ActorMessage(this, $"Received message: {message.Content}"); + return Task.CompletedTask; + } +} +``` ### Subscribing to messages using Services using our base class @@ -162,14 +170,14 @@ In this example, the Service called 'SubscribingStatelessService' subscribes to Add a Reliable Stateless Service project called 'SubscribingStatelessService'. Add Nuget package 'ServiceFabric.PubSubActors'. -Add Nuget package 'ServiceFabric.PubSubActors.Interfaces'. Add a project reference to the shared data contracts library ('DataContracts'). Now open the file SubscribingStatelessService.cs in the project 'SubscribingStatelessService' and replace the SubscribingStatelessService class with this code: ```csharp internal sealed class SubscribingStatelessService : SubscriberStatelessServiceBase { - public SubscribingStatelessService(StatelessServiceContext serviceContext, ISubscriberServiceHelper subscriberServiceHelper = null) : base(serviceContext, subscriberServiceHelper) + public SubscribingStatelessService(StatelessServiceContext serviceContext, IBrokerClient brokerClient = null) + : base(serviceContext, brokerClient) { } @@ -198,9 +206,9 @@ If you don't want to inherit from our base classes, you can use the `StatefulSub The code in `Program` will look like this: ```csharp - var helper = new SubscriberServiceHelper(); + var brokerClient = new BrokerClient(); ServiceRuntime.RegisterServiceAsync("SubscribingStatelessServiceType", - context => new StatelessSubscriberServiceBootstrapper(context, ctx => new SubscribingStatelessService (ctx, helper), helper).Build()) + context => new StatelessSubscriberServiceBootstrapper(context, ctx => new SubscribingStatelessService (ctx, brokerClient), brokerClient).Build()) .GetAwaiter().GetResult(); ``` @@ -209,14 +217,17 @@ The service looks like this: ```csharp internal sealed class SubscribingStatelessService : StatelessService, ISubscriberService { - public SubscribingStatelessService(StatelessServiceContext serviceContext, ISubscriberServiceHelper subscriberServiceHelper = null) : base(serviceContext, subscriberServiceHelper) + private readonly IBrokerClient _brokerClient; + + public SubscribingStatelessService(StatelessServiceContext serviceContext, IBrokerClient brokerClient = null) : base(serviceContext) { + _brokerClient = brokerClient; } public Task ReceiveMessageAsync(MessageWrapper messageWrapper) { // Automatically delegates work to annotated methods withing this class. - return _subscriberServiceHelper.ProccessMessageAsync(messageWrapper); + return _brokerClient.ProccessMessageAsync(messageWrapper); } protected override IEnumerable CreateServiceInstanceListeners() @@ -246,64 +257,11 @@ For stateful services, use the `StatefulSubscriberServiceBootstrapper`. *Check the Demo project for a working reference implementation.* -### Publishing messages from Actors -*Create a sample Actor that publishes messages.* -In this example, the Actor called 'PublishingActor' publishes messages of Type 'PublishedMessageOne'. - -In this example, the Publisher Actor publishes messages of Type 'PublishedMessageOne'. -Add a Reliable Stateless Actor project called 'PublishingActor'. -Add Nuget package 'ServiceFabric.PubSubActors' to 'PublishingActor'. -Add Nuget package 'ServiceFabric.PubSubActors.Interfaces' to 'PublishingActor.Interfaces'. -Add a project reference to the shared data contracts library ('DataContracts'). - -Go to the project 'PublishingActor.Interfaces' and open the file IPublishingActor.cs. -Replace the contents with the code below, to allow external callers to trigger a publish action (not required, Actors can decide for themselves too): - -```csharp -public interface IPublishingActor : IActor -{ - //enables external callers to trigger a publish action, not required for functionality - Task PublishMessageOneAsync(); - Task PublishMessageTwoAsync(); -} -``` - -Now open the file PublishingActor.cs in the project 'PublishingActor' and replace the contents with this code: - -https://github.com/loekd/ServiceFabric.PubSubActors/blob/master/ServiceFabric.PubSubActors.Demo/PublishingActor/PublishingActor.cs - -### Publishing messages from Services -*Create a sample Service that publishes messages.* -In this example, the Service called 'PublishingStatelessService' publishes messages of Type 'PublishedMessageOne'. - -Add a Reliable Stateless Service project called 'PublishingStatelessService'. -Add Nuget package 'ServiceFabric.PubSubActors' to 'PublishingStatelessService'. -Add Nuget package 'ServiceFabric.PubSubActors.Interfaces' to 'PublishingStatelessService'. -Add a project reference to the shared data contracts library ('DataContracts'). - -Go to the project 'DataContracts' and add an interface file IPublishingStatelessService.cs. -Add the code below: -```csharp -[ServiceContract] -public interface IPublishingStatelessService : IService -{ - //allows external callers to trigger a publish action, not required for functionality - [OperationContract] - Task PublishMessageOneAsync(); - [OperationContract] - Task PublishMessageTwoAsync(); -} -``` -Open the file 'PublishingStatelessService.cs'. Replace the contents with the code below: - -https://github.com/loekd/ServiceFabric.PubSubActors/blob/master/ServiceFabric.PubSubActors.Demo/PublishingStatelessService/PublishingStatelessService.cs - - ## Routing **This experimental feature works only when using the `DefaultPayloadSerializer`.** It adds support for an additional subscriber filter, based on message content. -### Example +### Example Given this message type: @@ -322,7 +280,33 @@ And given a subscriber that is interested in Customers named 'Customer1'. The subscription would be registered like this: ```csharp -await brokerService.RegisterServiceSubscriberAsync(serviceReference, typeof(CustomerMessage).FullName, "Customer.Name=Customer1"); +await brokerClient.SubscribeAsync(this, HandleMessageOne, routingKey: "Customer.Name=Customer1"); ``` The routing key is queried by using `JToken.SelectToken`. More info [here](https://www.newtonsoft.com/json/help/html/SelectToken.htm). + + +## Upgrading to version 8 +Significant changes were made in v8.0.0, including breaking changes to interfaces and tools. +* BrokerActor and RelayBrokerActor were removed. Actors don't make a good Broker, use BrokerService instead. +* ServiceFabric.PubSubActors.Interfaces library was removed. Only the main library is required now. +* Obsolete extension methods were removed. +* Helper classes were removed and replaced with `BrokerClient`. +* V2 is the only supported Service Fabric Remoting version. + +Here are some tips to help you through the upgrade process: +1. Upgrade remoting to V2 + * v8.0 only supports remoting V2. If you are using .NET Standard, you should already be using V2 and you can skip this step. + * Refer to documentation here: [Upgrade From Remoting V1 to Remoting V2](https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-communication-remoting#upgrade-from-remoting-v1-to-remoting-v2) + * Upgrade the Broker first. Override `BrokerService.CreateServiceReplicaListeners()` to use the extension method mentioned in the above link so you can use the assembly attribute to set up listening on V1 and V2. + * Upgrade Publisher and Subscriber services. Use the `useRemotingV2` option when creating the BrokerServiceLocator that is used by Helper classes. +2. Upgrade the Broker. + * Remove the ServiceFabric.PubSubActors.Interfaces library and upgrade the ServiceFabric.PubSubActors library to 8.0. + * Publish message and Receive message are backwards compatible, so update BrokerService first and publishers and subscribers should continue to function. +3. Upgrade Subscribers. + * Register/Unregister has been replaced by Subscribe/Unsubscribe, so you won't be able to subscribe/unsubscribe until you update subscriber services. + * Remove the ServiceFabric.PubSubActors.Interfaces library and upgrade the ServiceFabric.PubSubActors library to 8.0. + * Extension methods and helper classes have been removed. If you are using any of them in your Subscriber services, they will need to be refactored to use the BrokerClient or one of the Subscriber base classes (see documentation above). +4. Upgrade Publishers. (Optional, publishing is backwards compatible) + * Remove the ServiceFabric.PubSubActors.Interfaces library and upgrade the ServiceFabric.PubSubActors library to 8.0. + * Extension method and helper classes have been removed. Use BrokerClient.PublishMessageAsync() instead. diff --git a/ServiceFabric.PubSubActors.Interfaces/IBrokerActor.cs b/ServiceFabric.PubSubActors.Interfaces/IBrokerActor.cs deleted file mode 100644 index 298d8d5..0000000 --- a/ServiceFabric.PubSubActors.Interfaces/IBrokerActor.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using Microsoft.ServiceFabric.Actors; -using Microsoft.ServiceFabric.Actors.Runtime; -using System.Threading.Tasks; - -namespace ServiceFabric.PubSubActors.Interfaces -{ - /// - /// Acts as a registry for Subscriber Actors and Services that publishing Actors and Services can publish to. - /// Don't forget to mark implementing classes with - /// the attribute like: [ActorService(Name = nameof(IBrokerActor))] - /// - [Obsolete("This interface will be removed in the next major upgrade. Use the BrokerService instead.")] - public interface IBrokerActor : IActor - { - /// - /// Registers an Actor as a subscriber for messages. - /// - /// Reference to the actor to register. - Task RegisterSubscriberAsync(ActorReference actor); - - /// - /// Unregisters an Actor as a subscriber for messages. - /// - /// Reference to the actor to unregister. - /// Publish any remaining messages. - Task UnregisterSubscriberAsync(ActorReference actor, bool flushQueue); - - /// - /// Registers a service as a subscriber for messages. - /// - /// Reference to the actor to register. - Task RegisterServiceSubscriberAsync(ServiceReference service); - - /// - /// Unregisters a service as a subscriber for messages. - /// - /// Reference to the actor to unregister. - /// Publish any remaining messages. - Task UnregisterServiceSubscriberAsync(ServiceReference actor, bool flushQueue); - - /// - /// Takes a published message and forwards it (indirectly) to all Subscribers. - /// - /// The message to publish - /// - Task PublishMessageAsync(MessageWrapper message); - } -} diff --git a/ServiceFabric.PubSubActors.Interfaces/IRelayBrokerActor.cs b/ServiceFabric.PubSubActors.Interfaces/IRelayBrokerActor.cs deleted file mode 100644 index ecee74e..0000000 --- a/ServiceFabric.PubSubActors.Interfaces/IRelayBrokerActor.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.ServiceFabric.Actors; -using Microsoft.ServiceFabric.Actors.Runtime; -using System; -using System.Threading.Tasks; - -namespace ServiceFabric.PubSubActors.Interfaces -{ - /// - /// Acts as a relay registry for Subscriber Actors that -Actors can publish to. - /// Don't forget to mark implementing classes with - /// the attribute like: [ActorService(Name = nameof(IRelayBrokerActor))] - /// - [Obsolete("This interface will be removed in the next major upgrade. Use the BrokerService instead.")] - public interface IRelayBrokerActor : ISubscriberActor - { - /// - /// Registers an Actor as a subscriber for messages. - /// - /// Reference to the actor to register. - Task RegisterSubscriberAsync(ActorReference actor); - - /// - /// Unregisters an Actor as a subscriber for messages. - /// - /// Reference to the actor to unregister. - /// Publish any remaining messages. - Task UnregisterSubscriberAsync(ActorReference actor, bool flushQueue); - - /// - /// Registers a service as a subscriber for messages. - /// - /// Reference to the actor to register. - Task RegisterServiceSubscriberAsync(ServiceReference service); - - /// - /// Unregisters a service as a subscriber for messages. - /// - /// Reference to the actor to unregister. - /// Publish any remaining messages. - Task UnregisterServiceSubscriberAsync(ServiceReference actor, bool flushQueue); - - - } -} diff --git a/ServiceFabric.PubSubActors.Interfaces/Properties/AssemblyInfo.cs b/ServiceFabric.PubSubActors.Interfaces/Properties/AssemblyInfo.cs deleted file mode 100644 index d559b2b..0000000 --- a/ServiceFabric.PubSubActors.Interfaces/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceFabric.PubSubActors.Interfaces")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ServiceFabric.PubSubActors.Interfaces")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("8e319be1-af0a-4683-997f-7577e369850a")] diff --git a/ServiceFabric.PubSubActors.Interfaces/ServiceFabric.PubSubActors.Interfaces.csproj b/ServiceFabric.PubSubActors.Interfaces/ServiceFabric.PubSubActors.Interfaces.csproj deleted file mode 100644 index f6b1abd..0000000 --- a/ServiceFabric.PubSubActors.Interfaces/ServiceFabric.PubSubActors.Interfaces.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - 2019 - ServiceFabric.PubSubActors.Interfaces - Loek Duys - net452;net46;netstandard2.0 - x64 - x64 - 7.6.2 - ServiceFabric.PubSubActors.Interfaces - ServiceFabric.PubSubActors.Interfaces - ServiceFabric;Service;Fabric;Actor;Pub;Sub;Reliable - https://github.com/loekd/ServiceFabric.PubSubActors - https://github.com/loekd/ServiceFabric.PubSubActors/blob/master/LICENSE.md - true - git - https://github.com/loekd/ServiceFabric.PubSubActors.git - false - false - false - false - false - false - true - true - true - - - true - False - true - ..\ServiceFabric.PubSubActors.snk - - - - - - - - \ No newline at end of file diff --git a/ServiceFabric.PubSubActors.Tests/GivenServiceReference.cs b/ServiceFabric.PubSubActors.Tests/GivenServiceReference.cs index 1ba395c..afd8da7 100644 --- a/ServiceFabric.PubSubActors.Tests/GivenServiceReference.cs +++ b/ServiceFabric.PubSubActors.Tests/GivenServiceReference.cs @@ -1,6 +1,5 @@ using Microsoft.ServiceFabric.Actors; using Microsoft.VisualStudio.TestTools.UnitTesting; -using ServiceFabric.PubSubActors.Interfaces; using ServiceFabric.PubSubActors.State; namespace ServiceFabric.PubSubActors.Tests diff --git a/ServiceFabric.PubSubActors.Tests/GivenServiceReferenceWrapper.cs b/ServiceFabric.PubSubActors.Tests/GivenServiceReferenceWrapper.cs index f8a86f8..85e40ed 100644 --- a/ServiceFabric.PubSubActors.Tests/GivenServiceReferenceWrapper.cs +++ b/ServiceFabric.PubSubActors.Tests/GivenServiceReferenceWrapper.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.IO; +using System.IO; using System.Reflection; using System.Runtime.Serialization; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; -using ServiceFabric.PubSubActors.Interfaces; using ServiceFabric.PubSubActors.State; namespace ServiceFabric.PubSubActors.Tests diff --git a/ServiceFabric.PubSubActors.Tests/GivenSubscriberStatefulServiceBaseTests.cs b/ServiceFabric.PubSubActors.Tests/GivenSubscriberStatefulServiceBaseTests.cs index 0b3b25b..b1207f9 100644 --- a/ServiceFabric.PubSubActors.Tests/GivenSubscriberStatefulServiceBaseTests.cs +++ b/ServiceFabric.PubSubActors.Tests/GivenSubscriberStatefulServiceBaseTests.cs @@ -1,34 +1,25 @@ -using System; -using System.Collections.Generic; using System.Fabric; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.ServiceFabric.Services.Runtime; using Microsoft.VisualStudio.TestTools.UnitTesting; using ServiceFabric.Mocks; using ServiceFabric.PubSubActors.Helpers; -using ServiceFabric.PubSubActors.Interfaces; -using ServiceFabric.PubSubActors.SubscriberServices; +using ServiceFabric.PubSubActors.State; +using ServiceFabric.PubSubActors.Subscriber; namespace ServiceFabric.PubSubActors.Tests { [TestClass] public class GivenSubscriberStatefulServiceBaseTests { - [TestMethod] - public async Task WhenMarkedServiceScansAttributes_ThenCorrectlyRegistered() - { - var service = new MockSubscriberStatefulServiceBase(MockStatefulServiceContextFactory.Default, new MockSubscriberServiceHelper()); - await service.InvokeOnOpenAsync(ReplicaOpenMode.New, CancellationToken.None); - - Assert.AreEqual(1, service.Handlers.Count()); - } - [TestMethod] public async Task WhenMarkedServiceReceivesMessage_ThenCorrectMethodIsInvoked() { - var service = new MockSubscriberStatefulServiceBase(MockStatefulServiceContextFactory.Default, new MockSubscriberServiceHelper()); + var service = new MockSubscriberStatefulServiceBase(MockStatefulServiceContextFactory.Default, new BrokerClient(new MockBrokerServiceLocator())); + service.SetPartition(new MockStatefulServicePartition + { + PartitionInfo = new SingletonPartitionInformation() + }); await service.InvokeOnOpenAsync(ReplicaOpenMode.New, CancellationToken.None); await service.ReceiveMessageAsync(new MockMessage {SomeValue = "SomeValue"}.CreateMessageWrapper()); Assert.IsTrue(service.MethodCalled); @@ -37,78 +28,22 @@ public async Task WhenMarkedServiceReceivesMessage_ThenCorrectMethodIsInvoked() [TestMethod] public async Task WhenMarkedServiceReceivesMessage_ThenCorrectOverloadMethodIsInvoked() { - var service = new MockSubscriberStatefulServiceBase(MockStatefulServiceContextFactory.Default, new MockSubscriberServiceHelper()); + var service = new MockSubscriberStatefulServiceBase(MockStatefulServiceContextFactory.Default, new BrokerClient(new MockBrokerServiceLocator())); + service.SetPartition(new MockStatefulServicePartition + { + PartitionInfo = new SingletonPartitionInformation() + }); await service.InvokeOnOpenAsync(ReplicaOpenMode.New, CancellationToken.None); await service.ReceiveMessageAsync(new MockMessageSpecialized { SomeValue = "SomeValue" }.CreateMessageWrapper()); Assert.IsTrue(service.MethodCalled); } - public class MockSubscriberServiceHelper : ISubscriberServiceHelper - { - private ISubscriberServiceHelper _helper; - - public MockSubscriberServiceHelper() - { - _helper = new SubscriberServiceHelper(); - } - public Task RegisterMessageTypeAsync(StatelessService service, Type messageType, Uri brokerServiceName = null, - string listenerName = null) - { - return Task.CompletedTask; - } - - public Task UnregisterMessageTypeAsync(StatelessService service, Type messageType, bool flushQueue, - Uri brokerServiceName = null) - { - return Task.CompletedTask; - } - - public Task RegisterMessageTypeAsync(StatefulService service, Type messageType, Uri brokerServiceName = null, - string listenerName = null) - { - return Task.CompletedTask; - } - - public Task UnregisterMessageTypeAsync(StatefulService service, Type messageType, bool flushQueue, - Uri brokerServiceName = null) - { - return Task.CompletedTask; - } - - public Dictionary> DiscoverMessageHandlers(T handlerClass) where T : class - { - return _helper.DiscoverMessageHandlers(handlerClass); - } - - public Task SubscribeAsync(ServiceReference serviceReference, IEnumerable messageTypes, Uri broker = null) - { - return _helper.SubscribeAsync(serviceReference, messageTypes, broker); - } - - public Task ProccessMessageAsync(MessageWrapper messageWrapper, Dictionary> handlers) - { - return _helper.ProccessMessageAsync(messageWrapper, handlers); - } - - public ServiceReference CreateServiceReference(StatelessService service, string listenerName = null) - { - return new ServiceReference(); - } - - public ServiceReference CreateServiceReference(StatefulService service, string listenerName = null) - { - return new ServiceReference(); - } - } - public class MockSubscriberStatefulServiceBase : SubscriberStatefulServiceBase { public bool MethodCalled { get; private set; } - internal new IEnumerable> Handlers => base.Handlers.Values; - /// - public MockSubscriberStatefulServiceBase(StatefulServiceContext serviceContext, ISubscriberServiceHelper subscriberServiceHelper = null) : base(serviceContext, subscriberServiceHelper) + public MockSubscriberStatefulServiceBase(StatefulServiceContext serviceContext, IBrokerClient brokerClient = null) : base(serviceContext, brokerClient) { } diff --git a/ServiceFabric.PubSubActors.Tests/GivenSubscriberStatelessServiceBaseTests.cs b/ServiceFabric.PubSubActors.Tests/GivenSubscriberStatelessServiceBaseTests.cs index 3ffda12..9ae212a 100644 --- a/ServiceFabric.PubSubActors.Tests/GivenSubscriberStatelessServiceBaseTests.cs +++ b/ServiceFabric.PubSubActors.Tests/GivenSubscriberStatelessServiceBaseTests.cs @@ -1,34 +1,25 @@ -using System; -using System.Collections.Generic; using System.Fabric; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.ServiceFabric.Services.Runtime; using Microsoft.VisualStudio.TestTools.UnitTesting; using ServiceFabric.Mocks; using ServiceFabric.PubSubActors.Helpers; -using ServiceFabric.PubSubActors.Interfaces; -using ServiceFabric.PubSubActors.SubscriberServices; +using ServiceFabric.PubSubActors.State; +using ServiceFabric.PubSubActors.Subscriber; namespace ServiceFabric.PubSubActors.Tests { [TestClass] public class GivenSubscriberStatelessServiceBaseTests { - [TestMethod] - public async Task WhenMarkedServiceScansAttributes_ThenCorrectlyRegistered() - { - var service = new MockSubscriberStatelessServiceBase(MockStatelessServiceContextFactory.Default, new MockSubscriberServiceHelper()); - await service.InvokeOnOpenAsync(CancellationToken.None); - - Assert.AreEqual(1, service.Handlers.Count()); - } - [TestMethod] public async Task WhenMarkedServiceReceivesMessage_ThenCorrectMethodIsInvoked() { - var service = new MockSubscriberStatelessServiceBase(MockStatelessServiceContextFactory.Default, new MockSubscriberServiceHelper()); + var service = new MockSubscriberStatelessServiceBase(MockStatelessServiceContextFactory.Default, new BrokerClient(new MockBrokerServiceLocator())); + service.SetPartition(new MockStatelessServicePartition + { + PartitionInfo = new SingletonPartitionInformation() + }); await service.InvokeOnOpenAsync(CancellationToken.None); await service.ReceiveMessageAsync(new MockMessage {SomeValue = "SomeValue"}.CreateMessageWrapper()); Assert.IsTrue(service.MethodCalled); @@ -37,78 +28,22 @@ public async Task WhenMarkedServiceReceivesMessage_ThenCorrectMethodIsInvoked() [TestMethod] public async Task WhenMarkedServiceReceivesMessage_ThenCorrectOverloadMethodIsInvoked() { - var service = new MockSubscriberStatelessServiceBase(MockStatelessServiceContextFactory.Default, new MockSubscriberServiceHelper()); + var service = new MockSubscriberStatelessServiceBase(MockStatelessServiceContextFactory.Default, new BrokerClient(new MockBrokerServiceLocator())); + service.SetPartition(new MockStatelessServicePartition + { + PartitionInfo = new SingletonPartitionInformation() + }); await service.InvokeOnOpenAsync(CancellationToken.None); await service.ReceiveMessageAsync(new MockMessageSpecialized { SomeValue = "SomeValue" }.CreateMessageWrapper()); Assert.IsTrue(service.MethodCalled); } - public class MockSubscriberServiceHelper : ISubscriberServiceHelper - { - private ISubscriberServiceHelper _helper; - - public MockSubscriberServiceHelper() - { - _helper = new SubscriberServiceHelper(); - } - public Task RegisterMessageTypeAsync(StatelessService service, Type messageType, Uri brokerServiceName = null, - string listenerName = null) - { - return Task.CompletedTask; - } - - public Task UnregisterMessageTypeAsync(StatelessService service, Type messageType, bool flushQueue, - Uri brokerServiceName = null) - { - return Task.CompletedTask; - } - - public Task RegisterMessageTypeAsync(StatefulService service, Type messageType, Uri brokerServiceName = null, - string listenerName = null) - { - return Task.CompletedTask; - } - - public Task UnregisterMessageTypeAsync(StatefulService service, Type messageType, bool flushQueue, - Uri brokerServiceName = null) - { - return Task.CompletedTask; - } - - public Dictionary> DiscoverMessageHandlers(T handlerClass) where T : class - { - return _helper.DiscoverMessageHandlers(handlerClass); - } - - public Task SubscribeAsync(ServiceReference serviceReference, IEnumerable messageTypes, Uri broker = null) - { - return _helper.SubscribeAsync(serviceReference, messageTypes, broker); - } - - public Task ProccessMessageAsync(MessageWrapper messageWrapper, Dictionary> handlers) - { - return _helper.ProccessMessageAsync(messageWrapper, handlers); - } - - public ServiceReference CreateServiceReference(StatelessService service, string listenerName = null) - { - return new ServiceReference(); - } - - public ServiceReference CreateServiceReference(StatefulService service, string listenerName = null) - { - return new ServiceReference(); - } - } - public class MockSubscriberStatelessServiceBase : SubscriberStatelessServiceBase { public bool MethodCalled { get; private set; } - internal new IEnumerable> Handlers => base.Handlers.Values; - /// - public MockSubscriberStatelessServiceBase(StatelessServiceContext serviceContext, ISubscriberServiceHelper subscriberServiceHelper = null) : base(serviceContext, subscriberServiceHelper) + public MockSubscriberStatelessServiceBase(StatelessServiceContext serviceContext, IBrokerClient brokerClient = null) : base(serviceContext, brokerClient) { } diff --git a/ServiceFabric.PubSubActors.Tests/MockBrokerServiceLocator.cs b/ServiceFabric.PubSubActors.Tests/MockBrokerServiceLocator.cs new file mode 100644 index 0000000..e719a35 --- /dev/null +++ b/ServiceFabric.PubSubActors.Tests/MockBrokerServiceLocator.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using ServiceFabric.PubSubActors.Helpers; +using ServiceFabric.PubSubActors.State; + +namespace ServiceFabric.PubSubActors.Tests +{ + public class MockBrokerServiceLocator : IBrokerServiceLocator + { + public Task LocateAsync() + { + return Task.FromResult(new Uri("mockUri")); + } + + public Task RegisterAsync(Uri brokerServiceName) + { + return Task.CompletedTask; + } + + public Task GetBrokerServiceForMessageAsync(object message, Uri brokerServiceName = null) + { + return Task.FromResult(new MockBrokerService()); + } + + public Task GetBrokerServiceForMessageAsync(string messageTypeName, Uri brokerServiceName = null) + { + return Task.FromResult(new MockBrokerService()); + } + } + + public class MockBrokerService : IBrokerService + { + public Task SubscribeAsync(ReferenceWrapper reference, string messageTypeName) + { + return Task.CompletedTask; + } + + public Task UnsubscribeAsync(ReferenceWrapper reference, string messageTypeName) + { + return Task.CompletedTask; + } + + public Task PublishMessageAsync(MessageWrapper message) + { + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors.Tests/ServiceFabric.PubSubActors.Tests.csproj b/ServiceFabric.PubSubActors.Tests/ServiceFabric.PubSubActors.Tests.csproj index 42fe90b..5b29087 100644 --- a/ServiceFabric.PubSubActors.Tests/ServiceFabric.PubSubActors.Tests.csproj +++ b/ServiceFabric.PubSubActors.Tests/ServiceFabric.PubSubActors.Tests.csproj @@ -10,7 +10,6 @@ - \ No newline at end of file diff --git a/ServiceFabric.PubSubActors.sln b/ServiceFabric.PubSubActors.sln index 16d88dd..9bf10e4 100644 --- a/ServiceFabric.PubSubActors.sln +++ b/ServiceFabric.PubSubActors.sln @@ -12,8 +12,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceFabric.PubSubActors", "ServiceFabric.PubSubActors\ServiceFabric.PubSubActors.csproj", "{266D0C63-57EE-4C0D-873F-B3235B062CF4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceFabric.PubSubActors.Interfaces", "ServiceFabric.PubSubActors.Interfaces\ServiceFabric.PubSubActors.Interfaces.csproj", "{8E319BE1-AF0A-4683-997F-7577E369850A}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceFabric.PubSubActors.Tests", "ServiceFabric.PubSubActors.Tests\ServiceFabric.PubSubActors.Tests.csproj", "{29169DCB-AA96-4B25-8395-32DC00473F34}" EndProject Global @@ -32,14 +30,6 @@ Global {266D0C63-57EE-4C0D-873F-B3235B062CF4}.Release|Any CPU.Build.0 = Release|Any CPU {266D0C63-57EE-4C0D-873F-B3235B062CF4}.Release|x64.ActiveCfg = Release|Any CPU {266D0C63-57EE-4C0D-873F-B3235B062CF4}.Release|x64.Build.0 = Release|Any CPU - {8E319BE1-AF0A-4683-997F-7577E369850A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E319BE1-AF0A-4683-997F-7577E369850A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E319BE1-AF0A-4683-997F-7577E369850A}.Debug|x64.ActiveCfg = Debug|Any CPU - {8E319BE1-AF0A-4683-997F-7577E369850A}.Debug|x64.Build.0 = Debug|Any CPU - {8E319BE1-AF0A-4683-997F-7577E369850A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E319BE1-AF0A-4683-997F-7577E369850A}.Release|Any CPU.Build.0 = Release|Any CPU - {8E319BE1-AF0A-4683-997F-7577E369850A}.Release|x64.ActiveCfg = Release|Any CPU - {8E319BE1-AF0A-4683-997F-7577E369850A}.Release|x64.Build.0 = Release|Any CPU {29169DCB-AA96-4B25-8395-32DC00473F34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {29169DCB-AA96-4B25-8395-32DC00473F34}.Debug|Any CPU.Build.0 = Debug|Any CPU {29169DCB-AA96-4B25-8395-32DC00473F34}.Debug|x64.ActiveCfg = Debug|Any CPU diff --git a/ServiceFabric.PubSubActors/BrokerActor.cs b/ServiceFabric.PubSubActors/BrokerActor.cs deleted file mode 100644 index 83758df..0000000 --- a/ServiceFabric.PubSubActors/BrokerActor.cs +++ /dev/null @@ -1,311 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.ServiceFabric.Actors; -using Microsoft.ServiceFabric.Actors.Runtime; -using ServiceFabric.PubSubActors.Interfaces; -using ServiceFabric.PubSubActors.PublisherActors; -using ServiceFabric.PubSubActors.State; -using ServiceFabric.PubSubActors.SubscriberServices; - -namespace ServiceFabric.PubSubActors -{ - /// - /// Base class for a Stateful Actor that serves as a Broker that accepts messages - /// from Actors & Services calling - /// and forwards them to Actors and Services. - /// Every message type results in 1 BrokerActor instance. - /// - [StatePersistence(StatePersistence.Persisted)] - [Obsolete("This class will be removed in the next major upgrade. Use the BrokerService instead.")] - public abstract class BrokerActor : Actor, IBrokerActor - { - private string _messageType; - private IActorTimer _timer; - private const string StateKey = "__state__"; - - /// - /// Initializes a new instance of - /// - /// - /// The that will host this actor instance. - /// - /// - /// The for this actor instance. - /// - protected BrokerActor(ActorService actorService, ActorId actorId) - : base(actorService, actorId) - { - } - - /// - /// Indicates the maximum size of the Dead Letter Queue for each registered . (Default: 100) - /// - protected int MaxDeadLetterCount { get; set; } = 100; - /// - /// Gets or sets the interval to wait before starting to publish messages. (Default: 5s after Activation) - /// - protected TimeSpan DueTime { get; set; } = TimeSpan.FromSeconds(5); - - /// - /// Gets or sets the interval to wait between batches of publishing messages. (Default: 5s) - /// - protected TimeSpan Period { get; set; } = TimeSpan.FromSeconds(5); - - /// - /// When Set, this callback will be used to trace Actor messages to. - /// - protected Action ActorEventSourceMessageCallback { get; set; } - - /// - /// Registers an Actor as a subscriber for messages. - /// - /// Reference to the actor to register. - public async Task RegisterSubscriberAsync(ActorReference actor) - { - if (actor == null) throw new ArgumentNullException(nameof(actor)); - - ActorEventSourceMessage($"Registering Subscriber '{actor.ServiceUri}' for messages of type '{_messageType}'"); - - var actorReference = new ActorReferenceWrapper(actor); - var state = await StateManager.GetStateAsync(StateKey); - if (!state.SubscriberMessages.ContainsKey(actorReference)) - { - state.SubscriberMessages.Add(actorReference, new Queue()); - } - } - - /// - /// Unregisters an Actor as a subscriber for messages. - /// - /// Reference to the actor to unsubscribe. - /// Publish any remaining messages. - public async Task UnregisterSubscriberAsync(ActorReference actor, bool flushQueue) - { - if (actor == null) throw new ArgumentNullException(nameof(actor)); - ActorEventSourceMessage($"Unregistering Subscriber '{actor.ServiceUri}' for messages of type '{_messageType}'"); - - var actorReference = new ActorReferenceWrapper(actor); - Queue queue; - var state = await StateManager.GetStateAsync(StateKey); - if (flushQueue && state.SubscriberMessages.TryGetValue(actorReference, out queue)) - { - await ProcessQueueAsync(new KeyValuePair>(actorReference, queue)); - } - state.SubscriberMessages.Remove(actorReference); - } - - /// - /// Registers a service as a subscriber for messages. - /// - /// Reference to the service to register. - public async Task RegisterServiceSubscriberAsync(ServiceReference service) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - - ActorEventSourceMessage($"Registering Subscriber '{service.ServiceUri}' for messages of type '{_messageType}'"); - - var serviceReference = new ServiceReferenceWrapper(service); - var state = await StateManager.GetStateAsync(StateKey); - if (!state.SubscriberMessages.ContainsKey(serviceReference)) - { - state.SubscriberMessages.Add(serviceReference, new Queue()); - } - } - - /// - /// Unregisters a service as a subscriber for messages. - /// - /// Reference to the actor to unsubscribe. - /// Publish any remaining messages. - public async Task UnregisterServiceSubscriberAsync(ServiceReference service, bool flushQueue) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - - ActorEventSourceMessage($"Unregistering Subscriber '{service.ServiceUri}' for messages of type '{_messageType}'"); - - var serviceReference = new ServiceReferenceWrapper(service); - Queue queue; - var state = await StateManager.GetStateAsync(StateKey); - if (flushQueue && state.SubscriberMessages.TryGetValue(serviceReference, out queue)) - { - await ProcessQueueAsync(new KeyValuePair>(serviceReference, queue)); - } - state.SubscriberMessages.Remove(serviceReference); - } - - /// - /// Takes a published message and forwards it (indirectly) to all Subscribers. - /// - /// The message to publish - /// - public async Task PublishMessageAsync(MessageWrapper message) - { - ActorEventSourceMessage($"Publishing message of type '{message.MessageType}'"); - var state = await StateManager.GetStateAsync(StateKey); - foreach (var actorRef in state.SubscriberMessages.Keys) - { - state.SubscriberMessages[actorRef].Enqueue(message); - } - } - - /// - /// This method is called whenever an actor is activated. - /// Creates the initial state object and starts the Message forwarding timer. - /// - protected override async Task OnActivateAsync() - { - if (Id.Kind != ActorIdKind.String) - throw new InvalidOperationException("BrokerActor can only be created using a String ID. The ID should be the Full Name of the Message Type."); - - _messageType = Id.GetStringId(); - - - if (!await StateManager.ContainsStateAsync(StateKey)) - { - // This is the first time this actor has ever been activated. - // Set the actor's initial state values. - var state = new BrokerActorState - { - SubscriberMessages = new Dictionary>(), - SubscriberDeadLetters = new Dictionary>() - }; - await StateManager.TryAddStateAsync(StateKey, state); - ActorEventSourceMessage("State initialized."); - } - - if (_timer == null) - { - _timer = RegisterTimer(async _ => - { - await ProcessQueuesAsync(); - }, null, DueTime, Period); - - ActorEventSourceMessage("Timer initialized."); - } - - } - - /// - /// This method is called right before the actor is deactivated. - /// Unregisters the timer. - /// - /// - /// A Task that represents outstanding OnDeactivateAsync operation. - /// - protected override Task OnDeactivateAsync() - { - if (_timer != null) - { - UnregisterTimer(_timer); - } - _timer = null; - - return Task.FromResult(true); - } - - /// - /// When overridden, handles an undeliverable message for listener . - /// By default, it will be added to State.ActorDeadLetters. - /// - /// - /// - protected virtual async Task HandleUndeliverableMessageAsync(ReferenceWrapper reference, MessageWrapper message) - { - var deadLetters = await GetOrAddActorDeadLetterQueueAsync(reference); - ActorEventSourceMessage($"Adding undeliverable message to Actor Dead Letter Queue (Listener: {reference.Name}, Dead Letter Queue depth:{deadLetters.Count})"); - - ValidateQueueDepth(reference, deadLetters); - deadLetters.Enqueue(message); - } - - /// - /// Returns a 'dead letter queue' for the provided ActorReference, to store undeliverable messages. - /// - /// - /// - private async Task> GetOrAddActorDeadLetterQueueAsync(ReferenceWrapper actorReference) - { - Queue actorDeadLetters; - var state = await StateManager.GetStateAsync(StateKey); - if (!state.SubscriberDeadLetters.TryGetValue(actorReference, out actorDeadLetters)) - { - actorDeadLetters = new Queue(); - state.SubscriberDeadLetters[actorReference] = actorDeadLetters; - } - - return actorDeadLetters; - } - - /// - /// Ensures the Queue depth is less than the allowed maximum. - /// - /// - /// - private void ValidateQueueDepth(ReferenceWrapper reference, Queue deadLetters) - { - var queueDepth = deadLetters.Count; - if (queueDepth > MaxDeadLetterCount) - { - ActorEventSourceMessage( - $"Dead Letter Queue for Subscriber '{reference.Name}' has {queueDepth} items, which is more than the allowed {MaxDeadLetterCount}. Clearing it."); - deadLetters.Clear(); - } - } - - /// - /// Callback that is called from a timer. Forwards all published messages to subscribers. - /// - /// - private async Task ProcessQueuesAsync() - { - var state = await StateManager.GetStateAsync(StateKey); - foreach (var actorMessageQueue in state.SubscriberMessages) - { - await ProcessQueueAsync(actorMessageQueue); - } - } - - /// - /// Forwards all published messages to one subscriber. - /// - /// - /// - private async Task ProcessQueueAsync(KeyValuePair> actorMessageQueue) - { - int messagesProcessed = 0; - - while (actorMessageQueue.Value.Count > 0) - { - var message = actorMessageQueue.Value.Peek(); - //ActorEventSourceMessage($"Publishing message to subscribed Actor {actorMessageQueue.Key.Name}"); - try - { - await actorMessageQueue.Key.PublishAsync(message); - ActorEventSourceMessage($"Published message {++messagesProcessed} of {actorMessageQueue.Value.Count} to subscribed Actor {actorMessageQueue.Key.Name}"); - actorMessageQueue.Value.Dequeue(); - } - catch (Exception ex) - { - await HandleUndeliverableMessageAsync(actorMessageQueue.Key, message); - ActorEventSourceMessage($"Suppressed error while publishing message to subscribed Actor {actorMessageQueue.Key.Name}. Error: {ex}."); - } - } - if (messagesProcessed > 0) - { - ActorEventSourceMessage($"Processed {messagesProcessed} queued messages for '{actorMessageQueue.Key.Name}'."); - } - } - - - - /// - /// Outputs the provided message to the if it's configured. - /// - /// - private void ActorEventSourceMessage(string message) - { - ActorEventSourceMessageCallback?.Invoke(message); - } - } -} diff --git a/ServiceFabric.PubSubActors/BrokerService.cs b/ServiceFabric.PubSubActors/BrokerService.cs index 0e13841..070ef6d 100644 --- a/ServiceFabric.PubSubActors/BrokerService.cs +++ b/ServiceFabric.PubSubActors/BrokerService.cs @@ -1,21 +1,20 @@ using Microsoft.ServiceFabric.Data; using Microsoft.ServiceFabric.Data.Collections; using Microsoft.ServiceFabric.Services.Runtime; -using ServiceFabric.PubSubActors.Interfaces; using ServiceFabric.PubSubActors.State; using System; using System.Fabric; using System.Threading.Tasks; using System.Threading; -using ServiceFabric.PubSubActors.PublisherActors; -using ServiceFabric.PubSubActors.SubscriberServices; +using ServiceFabric.PubSubActors.Helpers; +using ServiceFabric.PubSubActors.Subscriber; namespace ServiceFabric.PubSubActors { /// - /// Base class for a that serves as a Broker that accepts messages - /// from Actors & Services calling - /// and forwards them to Actors and Services with strict ordering, so less performant than . + /// Base class for a that serves as a Broker that accepts messages + /// from Actors & Services calling + /// and forwards them to Actors and Services with strict ordering, so less performant than . /// Every message type is mapped to one of the partitions of this service. /// public abstract class BrokerService : BrokerServiceBase @@ -25,9 +24,8 @@ public abstract class BrokerService : BrokerServiceBase /// /// /// - /// Use remoting v2? Ignored in netstandard. - protected BrokerService(StatefulServiceContext serviceContext, bool enableAutoDiscovery = true, bool useRemotingV2 = false) - : base(serviceContext, enableAutoDiscovery, useRemotingV2) + protected BrokerService(StatefulServiceContext serviceContext, bool enableAutoDiscovery = true) + : base(serviceContext, enableAutoDiscovery) { } @@ -37,9 +35,8 @@ protected BrokerService(StatefulServiceContext serviceContext, bool enableAutoDi /// /// /// - /// Use remoting v2? Ignored in netstandard. - protected BrokerService(StatefulServiceContext serviceContext, IReliableStateManagerReplica2 reliableStateManagerReplica, bool enableAutoDiscovery = true, bool useRemotingV2 = false) - : base(serviceContext, reliableStateManagerReplica, enableAutoDiscovery, useRemotingV2) + protected BrokerService(StatefulServiceContext serviceContext, IReliableStateManagerReplica2 reliableStateManagerReplica, bool enableAutoDiscovery = true) + : base(serviceContext, reliableStateManagerReplica, enableAutoDiscovery) { } diff --git a/ServiceFabric.PubSubActors/BrokerServiceBase.cs b/ServiceFabric.PubSubActors/BrokerServiceBase.cs index 5891211..08e61f7 100644 --- a/ServiceFabric.PubSubActors/BrokerServiceBase.cs +++ b/ServiceFabric.PubSubActors/BrokerServiceBase.cs @@ -6,24 +6,21 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.ServiceFabric.Actors; using Microsoft.ServiceFabric.Data; using Microsoft.ServiceFabric.Data.Collections; using Microsoft.ServiceFabric.Services.Communication.Runtime; +using Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime; using Microsoft.ServiceFabric.Services.Runtime; using ServiceFabric.PubSubActors.Helpers; -using ServiceFabric.PubSubActors.Interfaces; -using ServiceFabric.PubSubActors.PublisherActors; using ServiceFabric.PubSubActors.State; -using ServiceFabric.PubSubActors.SubscriberServices; +using ServiceFabric.PubSubActors.Subscriber; namespace ServiceFabric.PubSubActors { /// - /// Base class for a that serves as a Broker that accepts messages - /// from Actors & Services calling - /// and forwards them to Actors and Services. - /// Every message type is mapped to one of the partitions of this service. + /// Base class for a that serves as a Broker that accepts messages from Actors & + /// Services and forwards them to Actors and + /// Services. Every message type is mapped to one of the partitions of this service. /// public abstract class BrokerServiceBase : StatefulService, IBrokerService { @@ -32,7 +29,6 @@ public abstract class BrokerServiceBase : StatefulService, IBrokerService new ConcurrentDictionary(); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1); - private readonly bool _useRemotingV2; /// /// Gets the state key for all subscriber queues. @@ -74,8 +70,7 @@ public abstract class BrokerServiceBase : StatefulService, IBrokerService /// /// /// - /// Use remoting v2? Ignored in netstandard. - protected BrokerServiceBase(StatefulServiceContext serviceContext, bool enableAutoDiscovery = true, bool useRemotingV2 = false) + protected BrokerServiceBase(StatefulServiceContext serviceContext, bool enableAutoDiscovery = true) : base(serviceContext) { if (enableAutoDiscovery) @@ -85,8 +80,6 @@ protected BrokerServiceBase(StatefulServiceContext serviceContext, bool enableAu .GetAwaiter() .GetResult(); } - - _useRemotingV2 = useRemotingV2; } /// @@ -95,9 +88,8 @@ protected BrokerServiceBase(StatefulServiceContext serviceContext, bool enableAu /// /// /// - /// Use remoting v2? Ignored in netstandard. protected BrokerServiceBase(StatefulServiceContext serviceContext, - IReliableStateManagerReplica2 reliableStateManagerReplica, bool enableAutoDiscovery = true, bool useRemotingV2 = false) + IReliableStateManagerReplica2 reliableStateManagerReplica, bool enableAutoDiscovery = true) : base(serviceContext, reliableStateManagerReplica) { if (enableAutoDiscovery) @@ -107,54 +99,78 @@ protected BrokerServiceBase(StatefulServiceContext serviceContext, .GetAwaiter() .GetResult(); } - - _useRemotingV2 = useRemotingV2; - } - /// - /// Registers an Actor as a subscriber for messages. - /// - /// Reference to the actor to register. - /// Full type name of message object. - /// Optional routing key to filter messages based on content. 'Key=Value' where Key is a message property path and Value is the value to match with message payload content. - public async Task RegisterSubscriberAsync(ActorReference actor, string messageTypeName, string routingKey) - { - var actorReference = new ActorReferenceWrapper(actor, routingKey); - await RegisterSubscriberAsync(actorReference, messageTypeName); - } - /// - /// Unregisters an Actor as a subscriber for messages. - /// - /// Reference to the actor to unsubscribe. - /// Full type name of message object. - /// Publish any remaining messages. - public async Task UnregisterSubscriberAsync(ActorReference actor, string messageTypeName, bool flushQueue) - { - var actorReference = new ActorReferenceWrapper(actor); - await UnregisterSubscriberAsync(actorReference, messageTypeName); } + /// - /// Registers a service as a subscriber for messages. + /// Registers a Service or Actor as subscriber for messages of type /// + /// Reference to the Service or Actor to register. /// Full type name of message object. - /// Reference to the service to register. - /// Optional routing key to filter messages based on content. 'Key=Value' where Key is a message property path and Value is the value to match with message payload content. - public async Task RegisterServiceSubscriberAsync(ServiceReference service, string messageTypeName, string routingKey) + /// + public async Task SubscribeAsync(ReferenceWrapper reference, string messageTypeName) { - var serviceReference = new ServiceReferenceWrapper(service, routingKey); - await RegisterSubscriberAsync(serviceReference, messageTypeName); + await WaitForInitializeAsync(CancellationToken.None); + + var myDictionary = await TimeoutRetryHelper.Execute((token, state) => StateManager.GetOrAddAsync>(messageTypeName)); + + await TimeoutRetryHelper.ExecuteInTransaction(StateManager, async (tx, token, state) => + { + var queueName = CreateQueueName(reference, messageTypeName); + + Func addValueFactory = key => + { + var newState = new BrokerServiceState(messageTypeName); + var subscriber = new Reference(reference, queueName); + newState = BrokerServiceState.AddSubscriber(newState, subscriber); + return newState; + }; + + Func updateValueFactory = (key, current) => + { + var subscriber = new Reference(reference, queueName); + var newState = BrokerServiceState.AddSubscriber(current, subscriber); + return newState; + }; + + await myDictionary.AddOrUpdateAsync(tx, Subscribers, addValueFactory, updateValueFactory); + + await CreateQueueAsync(tx, queueName); + + _queues.AddOrUpdate(queueName, reference, (key, old) => reference); + ServiceEventSourceMessage($"Registered subscriber: {reference.Name}"); + }, cancellationToken: CancellationToken.None); } + /// - /// Unregisters a service as a subscriber for messages. + /// Unregisters a Service or Actor as subscriber for messages of type /// - /// Full type name of message object. - /// Reference to the actor to unsubscribe. - /// Publish any remaining messages. - public async Task UnregisterServiceSubscriberAsync(ServiceReference service, string messageTypeName, - bool flushQueue) + /// + /// + /// + public async Task UnsubscribeAsync(ReferenceWrapper reference, string messageTypeName) { - var serviceReference = new ServiceReferenceWrapper(service); - await UnregisterSubscriberAsync(serviceReference, messageTypeName); + await WaitForInitializeAsync(CancellationToken.None); + + var myDictionary = await TimeoutRetryHelper.Execute((token, state) => StateManager.GetOrAddAsync>(messageTypeName)); + var queueName = CreateQueueName(reference, messageTypeName); + + await TimeoutRetryHelper.ExecuteInTransaction(StateManager, async (tx, token, state) => + { + var subscribers = await myDictionary.TryGetValueAsync(tx, Subscribers, LockMode.Update); + if (subscribers.HasValue) + { + var newState = BrokerServiceState.RemoveSubscriber(subscribers.Value, reference); + await myDictionary.SetAsync(tx, Subscribers, newState); + } + + + await StateManager.RemoveAsync(tx, queueName); + + ServiceEventSourceMessage($"Unregistered subscriber: {reference.Name}"); + _queues.TryRemove(queueName, out reference); + }); } + /// /// Takes a published message and forwards it (indirectly) to all Subscribers. /// @@ -209,7 +225,7 @@ protected override async Task RunAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - //process messages for given time, then allow other transactions to enqueue messages + //process messages for given time, then allow other transactions to enqueue messages var cts = new CancellationTokenSource(MaxProcessingPeriod); var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken); try @@ -252,21 +268,7 @@ protected override async Task RunAsync(CancellationToken cancellationToken) protected override IEnumerable CreateServiceReplicaListeners() { //add the pubsub listener -#if NETSTANDARD2_0 - yield return new ServiceReplicaListener(context => - new Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime.FabricTransportServiceRemotingListener(context, this), ListenerName); -#else - if (_useRemotingV2) - { - yield return new ServiceReplicaListener(context => - new Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime.FabricTransportServiceRemotingListener(context, this), ListenerName); - } - else - { - yield return new ServiceReplicaListener(context => - new Microsoft.ServiceFabric.Services.Remoting.V1.FabricTransport.Runtime.FabricTransportServiceRemotingListener(context, this), ListenerName); - } -#endif + yield return new ServiceReplicaListener(context => new FabricTransportServiceRemotingListener(context, this), ListenerName); } /// @@ -341,96 +343,12 @@ protected void ServiceEventSourceMessage(string message, [CallerMemberName] stri ServiceEventSourceMessageCallback?.Invoke($"{caller} - {message}"); } - /// - /// Registers a Service or Actor as subscriber for messages of type - /// - /// - /// - /// - private async Task RegisterSubscriberAsync(ReferenceWrapper reference, string messageTypeName) - { - await WaitForInitializeAsync(CancellationToken.None); - - var myDictionary = await TimeoutRetryHelper.Execute((token, state) => StateManager.GetOrAddAsync>(messageTypeName)); - - await TimeoutRetryHelper.ExecuteInTransaction(StateManager, async (tx, token, state) => - { - var queueName = CreateQueueName(reference, messageTypeName); - var deadLetterQueueName = CreateDeadLetterQueueName(reference, messageTypeName); - - Func addValueFactory = key => - { - var newState = new BrokerServiceState(messageTypeName); - var subscriber = new Reference(reference, queueName, deadLetterQueueName); - newState = BrokerServiceState.AddSubscriber(newState, subscriber); - return newState; - }; - - Func updateValueFactory = (key, current) => - { - var subscriber = new Reference(reference, queueName, deadLetterQueueName); - var newState = BrokerServiceState.AddSubscriber(current, subscriber); - return newState; - }; - - await myDictionary.AddOrUpdateAsync(tx, Subscribers, addValueFactory, updateValueFactory); - - await CreateQueueAsync(tx, queueName); - await CreateQueueAsync(tx, deadLetterQueueName); - - _queues.AddOrUpdate(queueName, reference, (key, old) => reference); - ServiceEventSourceMessage($"Registered subscriber: {reference.Name}"); - }, cancellationToken: CancellationToken.None); - } - protected abstract Task CreateQueueAsync(ITransaction tx, string queueName); - - - /// - /// Unregisters a Service or Actor as subscriber for messages of type - /// - /// - /// - /// - private async Task UnregisterSubscriberAsync(ReferenceWrapper reference, string messageTypeName) - { - await WaitForInitializeAsync(CancellationToken.None); - - var myDictionary = await TimeoutRetryHelper.Execute((token, state) => StateManager.GetOrAddAsync>(messageTypeName)); - var queueName = CreateQueueName(reference, messageTypeName); - var deadLetterQueueName = CreateDeadLetterQueueName(reference, messageTypeName); - - await TimeoutRetryHelper.ExecuteInTransaction(StateManager, async (tx, token, state) => - { - var subscribers = await myDictionary.TryGetValueAsync(tx, Subscribers, LockMode.Update); - if (subscribers.HasValue) - { - var newState = BrokerServiceState.RemoveSubscriber(subscribers.Value, reference); - await myDictionary.SetAsync(tx, Subscribers, newState); - } - - - await StateManager.RemoveAsync(tx, queueName); - await StateManager.RemoveAsync(tx, deadLetterQueueName); - - ServiceEventSourceMessage($"Unregistered subscriber: {reference.Name}"); - _queues.TryRemove(queueName, out reference); - }); - } /// /// Creates a queuename to use for this reference. (message specific) /// /// - private static string CreateDeadLetterQueueName(ReferenceWrapper reference, string messageTypeName) - { - return $"{messageTypeName}_{reference.GetDeadLetterQueueName()}"; - } - - /// - /// Creates a deadletter queuename to use for this reference. (not message specific) - /// - /// private static string CreateQueueName(ReferenceWrapper reference, string messageTypeName) { return $"{messageTypeName}_{reference.GetQueueName()}"; diff --git a/ServiceFabric.PubSubActors/BrokerServiceUnordered.cs b/ServiceFabric.PubSubActors/BrokerServiceUnordered.cs index 9e94c8e..26544fb 100644 --- a/ServiceFabric.PubSubActors/BrokerServiceUnordered.cs +++ b/ServiceFabric.PubSubActors/BrokerServiceUnordered.cs @@ -5,16 +5,15 @@ using Microsoft.ServiceFabric.Data; using Microsoft.ServiceFabric.Data.Collections; using Microsoft.ServiceFabric.Services.Runtime; -using ServiceFabric.PubSubActors.Interfaces; -using ServiceFabric.PubSubActors.PublisherActors; +using ServiceFabric.PubSubActors.Helpers; using ServiceFabric.PubSubActors.State; -using ServiceFabric.PubSubActors.SubscriberServices; +using ServiceFabric.PubSubActors.Subscriber; namespace ServiceFabric.PubSubActors { /// - /// Base class for a that serves as a Broker that accepts messages - /// from Actors & Services calling + /// Base class for a that serves as a Broker that accepts messages + /// from Actors & Services calling /// and forwards them to Actors and Services without strict ordering, so more performant than . /// Every message type is mapped to one of the partitions of this service. /// @@ -25,9 +24,8 @@ public abstract class BrokerServiceUnordered : BrokerServiceBase /// /// /// - /// Use remoting v2? Ignored in netstandard. - protected BrokerServiceUnordered(StatefulServiceContext serviceContext, bool enableAutoDiscovery = true, bool useRemotingV2 = false) - : base(serviceContext, enableAutoDiscovery, useRemotingV2) + protected BrokerServiceUnordered(StatefulServiceContext serviceContext, bool enableAutoDiscovery = true) + : base(serviceContext, enableAutoDiscovery) { } @@ -37,9 +35,8 @@ protected BrokerServiceUnordered(StatefulServiceContext serviceContext, bool ena /// /// /// - /// Use remoting v2? Ignored in netstandard. - protected BrokerServiceUnordered(StatefulServiceContext serviceContext, IReliableStateManagerReplica2 reliableStateManagerReplica, bool enableAutoDiscovery = true, bool useRemotingV2 = false) - : base(serviceContext, reliableStateManagerReplica, enableAutoDiscovery, useRemotingV2) + protected BrokerServiceUnordered(StatefulServiceContext serviceContext, IReliableStateManagerReplica2 reliableStateManagerReplica, bool enableAutoDiscovery = true) + : base(serviceContext, reliableStateManagerReplica, enableAutoDiscovery) { } diff --git a/ServiceFabric.PubSubActors/Helpers/BrokerClient.cs b/ServiceFabric.PubSubActors/Helpers/BrokerClient.cs new file mode 100644 index 0000000..c28074e --- /dev/null +++ b/ServiceFabric.PubSubActors/Helpers/BrokerClient.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Fabric; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.ServiceFabric.Actors; +using Microsoft.ServiceFabric.Actors.Runtime; +using Microsoft.ServiceFabric.Services.Runtime; +using ServiceFabric.PubSubActors.State; + +namespace ServiceFabric.PubSubActors.Helpers +{ + public class BrokerClient : IBrokerClient + { + private readonly IBrokerServiceLocator _brokerServiceLocator; + + /// + /// The message types that this service subscribes to and their respective handler methods. + /// + protected Dictionary> Handlers { get; set; } = new Dictionary>(); + + public BrokerClient(IBrokerServiceLocator brokerServiceLocator = null) + { + _brokerServiceLocator = brokerServiceLocator ?? new BrokerServiceLocator(); + } + + /// + public async Task PublishMessageAsync(T message) where T : class + { + var brokerService = await _brokerServiceLocator.GetBrokerServiceForMessageAsync(message); + await brokerService.PublishMessageAsync(message.CreateMessageWrapper()); + } + + /// + public async Task SubscribeAsync(ReferenceWrapper referenceWrapper, Type messageType, Func handler) where T : class + { + Handlers[messageType] = message => handler((T)message); + var brokerService = await _brokerServiceLocator.GetBrokerServiceForMessageAsync(messageType); + await brokerService.SubscribeAsync(referenceWrapper, messageType.FullName); + } + + /// + public async Task UnsubscribeAsync(ReferenceWrapper referenceWrapper, Type messageType) + { + var brokerService = await _brokerServiceLocator.GetBrokerServiceForMessageAsync(messageType); + await brokerService.UnsubscribeAsync(referenceWrapper, messageType.FullName); + } + + /// + public Task ProcessMessageAsync(MessageWrapper messageWrapper) + { + var messageType = Assembly.Load(messageWrapper.Assembly).GetType(messageWrapper.MessageType, true); + + while (messageType != null) + { + if (Handlers.TryGetValue(messageType, out var handler)) + { + return handler(messageWrapper.CreateMessage()); + } + messageType = messageType.BaseType; + } + + return Task.FromResult(true); + } + } + + public static class BrokerClientExtensions + { + // subscribe/unsubscribe using Generic type (useful when subscibing manually) + + /// + /// Registers this StatelessService as a subscriber for messages of type with the . + /// + /// + /// + /// + /// + /// Optional routing key to filter messages based on content. 'Key=Value' where Key is a message property path and Value is the value to match with message payload content. + /// + public static Task SubscribeAsync(this IBrokerClient brokerClient, StatelessService service, Func handler, string listenerName = null, string routingKey = null) where T : class + { + return brokerClient.SubscribeAsync(CreateReferenceWrapper(service, listenerName, routingKey), typeof(T), handler); + } + + /// + /// Registers this StatefulService as a subscriber for messages of type with the . + /// + /// + /// + /// + /// + /// Optional routing key to filter messages based on content. 'Key=Value' where Key is a message property path and Value is the value to match with message payload content. + /// + public static Task SubscribeAsync(this IBrokerClient brokerClient, StatefulService service, Func handler, string listenerName = null, string routingKey = null) where T : class + { + return brokerClient.SubscribeAsync(CreateReferenceWrapper(service, listenerName, routingKey), typeof(T), handler); + } + + /// + /// Registers this Actor as a subscriber for messages of type with the . + /// + /// + /// + /// + /// Optional routing key to filter messages based on content. 'Key=Value' where Key is a message property path and Value is the value to match with message payload content. + /// + public static Task SubscribeAsync(this IBrokerClient brokerClient, ActorBase actor, Func handler, string routingKey = null) where T : class + { + return brokerClient.SubscribeAsync(CreateReferenceWrapper(actor, routingKey), typeof(T), handler); + } + + /// + /// Unregisters this StatelessService as a subscriber for messages of type with the . + /// + /// + /// + /// + public static Task UnsubscribeAsync(this IBrokerClient brokerClient, StatelessService service) where T : class + { + return brokerClient.UnsubscribeAsync(CreateReferenceWrapper(service), typeof(T)); + } + + /// + /// Unregisters this StatefulService as a subscriber for messages of type with the . + /// + /// + /// + /// + public static Task UnsubscribeAsync(this IBrokerClient brokerClient, StatefulService service) where T : class + { + return brokerClient.UnsubscribeAsync(CreateReferenceWrapper(service), typeof(T)); + } + + /// + /// Unregisters this Actor as a subscriber for messages of type with the . + /// + /// + /// + /// + public static Task UnsubscribeAsync(this IBrokerClient brokerClient, ActorBase actor) where T : class + { + return brokerClient.UnsubscribeAsync(CreateReferenceWrapper(actor), typeof(T)); + } + + // subscribe/unsubscribe using Type (useful when processing Subscribe attributes) + + internal static Task SubscribeAsync(this IBrokerClient brokerClient, StatelessService service, Type messageType, Func handler, string listenerName = null, string routingKey = null) where T : class + { + return brokerClient.SubscribeAsync(CreateReferenceWrapper(service, listenerName, routingKey), messageType, handler); + } + + internal static Task SubscribeAsync(this IBrokerClient brokerClient, StatefulService service, Type messageType, Func handler, string listenerName = null, string routingKey = null) where T : class + { + return brokerClient.SubscribeAsync(CreateReferenceWrapper(service, listenerName, routingKey), messageType, handler); + } + + internal static Task SubscribeAsync(this IBrokerClient brokerClient, ActorBase actor, Type messageType, Func handler, string routingKey = null) where T : class + { + return brokerClient.SubscribeAsync(CreateReferenceWrapper(actor, routingKey), messageType, handler); + } + + internal static Task UnsubscribeAsync(this IBrokerClient brokerClient, StatelessService service, Type messageType) + { + return brokerClient.UnsubscribeAsync(CreateReferenceWrapper(service), messageType); + } + + internal static Task UnsubscribeAsync(this IBrokerClient brokerClient, StatefulService service, Type messageType) + { + return brokerClient.UnsubscribeAsync(CreateReferenceWrapper(service), messageType); + } + + internal static Task UnsubscribeAsync(this IBrokerClient brokerClient, ActorBase actor, Type messageType) + { + return brokerClient.UnsubscribeAsync(CreateReferenceWrapper(actor), messageType); + } + + /// + /// Create a ReferenceWrapper object given this StatelessService. + /// + /// + /// + /// Optional routing key to filter messages based on content. 'Key=Value' where Key is a message property path and Value is the value to match with message payload content. + /// + /// + private static ReferenceWrapper CreateReferenceWrapper(this StatelessService service, string listenerName = null, string routingKey = null) + { + if (service == null) throw new ArgumentNullException(nameof(service)); + var servicePartition = GetPropertyValue(service, "Partition"); + return new ServiceReferenceWrapper(CreateServiceReference(service.Context, servicePartition.PartitionInfo, listenerName), routingKey); + } + + /// + /// Create a ReferenceWrapper object given this StatefulService. + /// + /// + /// + /// Optional routing key to filter messages based on content. 'Key=Value' where Key is a message property path and Value is the value to match with message payload content. + /// + /// + private static ReferenceWrapper CreateReferenceWrapper(this StatefulService service, string listenerName = null, string routingKey = null) + { + if (service == null) throw new ArgumentNullException(nameof(service)); + var servicePartition = GetPropertyValue(service, "Partition"); + return new ServiceReferenceWrapper(CreateServiceReference(service.Context, servicePartition.PartitionInfo, listenerName), routingKey); + } + + /// + /// Create a ReferenceWrapper object given this Actor. + /// + /// + /// Optional routing key to filter messages based on content. 'Key=Value' where Key is a message property path and Value is the value to match with message payload content. + /// + /// + private static ReferenceWrapper CreateReferenceWrapper(this ActorBase actor, string routingKey = null) + { + if (actor == null) throw new ArgumentNullException(nameof(actor)); + return new ActorReferenceWrapper(ActorReference.Get(actor), routingKey); + } + + /// + /// Creates a for the provided service context and partition info. + /// + /// + /// + /// (optional) The name of the listener that is used to communicate with the service + /// + private static ServiceReference CreateServiceReference(ServiceContext context, ServicePartitionInformation info, string listenerName = null) + { + var serviceReference = new ServiceReference + { + ApplicationName = context.CodePackageActivationContext.ApplicationName, + PartitionKind = info.Kind, + ServiceUri = context.ServiceName, + PartitionGuid = context.PartitionId, + ListenerName = listenerName + }; + + if (info is Int64RangePartitionInformation longInfo) + { + serviceReference.PartitionKey = longInfo.LowKey; + } + else if (info is NamedPartitionInformation stringInfo) + { + serviceReference.PartitionName = stringInfo.Name; + } + + return serviceReference; + } + + private static TProperty GetPropertyValue(TClass instance, string propertyName) + { + return (TProperty)(typeof(TClass) + .GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic)? + .GetValue(instance) ?? throw new ArgumentNullException($"Unable to find property: '{propertyName}' on: '{instance}'")); + } + } +} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/Helpers/BrokerServiceLocator.cs b/ServiceFabric.PubSubActors/Helpers/BrokerServiceLocator.cs index a79e7b3..a86be3e 100644 --- a/ServiceFabric.PubSubActors/Helpers/BrokerServiceLocator.cs +++ b/ServiceFabric.PubSubActors/Helpers/BrokerServiceLocator.cs @@ -14,31 +14,19 @@ public class BrokerServiceLocator : IBrokerServiceLocator { private readonly IHashingHelper _hashingHelper; private static ServicePartitionList _cachedPartitions; + private static Uri _cachedBrokerUri; private readonly FabricClient _fabricClient; - private const string _brokerName = nameof(BrokerService); + private const string BrokerName = nameof(BrokerService); private readonly IServiceProxyFactory _serviceProxyFactory; /// /// Creates a new default instance. /// - public BrokerServiceLocator(bool useRemotingV2 = false, IHashingHelper hashingHelper = null) + public BrokerServiceLocator(IHashingHelper hashingHelper = null) { _hashingHelper = hashingHelper ?? new HashingHelper(); _fabricClient = new FabricClient(); - -#if NETSTANDARD2_0 - _serviceProxyFactory = new ServiceProxyFactory(c => new FabricTransportServiceRemotingClientFactory()); -#else - if (useRemotingV2) - { - _serviceProxyFactory = new ServiceProxyFactory(c => new FabricTransportServiceRemotingClientFactory()); - } - else - { - _serviceProxyFactory = new ServiceProxyFactory(); - } -#endif } @@ -46,12 +34,34 @@ public BrokerServiceLocator(bool useRemotingV2 = false, IHashingHelper hashingHe public async Task RegisterAsync(Uri brokerServiceName) { var activationContext = FabricRuntime.GetActivationContext(); - await _fabricClient.PropertyManager.PutPropertyAsync(new Uri(activationContext.ApplicationName), _brokerName, brokerServiceName.ToString()); + await _fabricClient.PropertyManager.PutPropertyAsync(new Uri(activationContext.ApplicationName), BrokerName, brokerServiceName.ToString()); + } + + /// + public async Task GetBrokerServiceForMessageAsync(object message, Uri brokerServiceName = null) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + var resolvedPartition = await GetPartitionForMessageAsync(message, brokerServiceName); + return _serviceProxyFactory.CreateServiceProxy( + brokerServiceName ?? await LocateAsync(), resolvedPartition, listenerName: BrokerServiceBase.ListenerName); + } + + /// + public async Task GetBrokerServiceForMessageAsync(string messageTypeName, Uri brokerServiceName = null) + { + var resolvedPartition = await GetPartitionForMessageAsync(messageTypeName, brokerServiceName); + return _serviceProxyFactory.CreateServiceProxy( + brokerServiceName ?? await LocateAsync(), resolvedPartition, listenerName: BrokerServiceBase.ListenerName); } /// public async Task LocateAsync() { + if (_cachedBrokerUri != null) + { + return _cachedBrokerUri; + } + try { // check current context @@ -62,8 +72,8 @@ public async Task LocateAsync() { // try to find broker name in other application types bool hasPages = true; - - var query = new ApplicationQueryDescription() { MaxResults = 50 }; + + var query = new ApplicationQueryDescription { MaxResults = 50 }; while (hasPages) { @@ -77,20 +87,24 @@ public async Task LocateAsync() { var found = await LocateAsync(app.ApplicationName); if (found != null) - return found; + { + _cachedBrokerUri = found; + return _cachedBrokerUri; + } } } } else { - return new Uri(property.GetValue()); + _cachedBrokerUri = new Uri(property.GetValue()); + return _cachedBrokerUri; } } catch { ; } - return null; + throw new InvalidOperationException("No brokerService was discovered in the cluster."); } private async Task LocateAsync(Uri applicationName) @@ -99,15 +113,17 @@ private async Task LocateAsync(Uri applicationName) return property != null ? new Uri(property.GetValue()) : null; } + private async Task GetBrokerPropertyOrNull(string applicationName) { return await GetBrokerPropertyOrNull(new Uri(applicationName)); } + private async Task GetBrokerPropertyOrNull(Uri applicationName) { try { - return await _fabricClient.PropertyManager.GetPropertyAsync(applicationName, _brokerName); + return await _fabricClient.PropertyManager.GetPropertyAsync(applicationName, BrokerName); } catch { @@ -116,12 +132,17 @@ private async Task GetBrokerPropertyOrNull(Uri applicationName) return null; } - /// - public async Task GetPartitionForMessageAsync(string messageTypeName, Uri brokerServiceName) + /// + /// Resolves the to send the message to, based on message type name. + /// + /// Full type name of message object. + /// + /// + private async Task GetPartitionForMessageAsync(string messageTypeName, Uri brokerServiceName) { if (_cachedPartitions == null) { - _cachedPartitions = await _fabricClient.QueryManager.GetPartitionListAsync(brokerServiceName); + _cachedPartitions = await _fabricClient.QueryManager.GetPartitionListAsync(brokerServiceName ?? await LocateAsync()); } int hashCode; @@ -142,30 +163,17 @@ public async Task GetPartitionForMessageAsync(string messag return resolvedPartition; } - /// - public Task GetPartitionForMessageAsync(object message, Uri brokerServiceName) + /// + /// Resolves the to send the message to, based on message's type. + /// + /// The message to publish + /// + /// + private Task GetPartitionForMessageAsync(object message, Uri brokerServiceName) { if (message == null) throw new ArgumentNullException(nameof(message)); - if (brokerServiceName == null) throw new ArgumentNullException(nameof(brokerServiceName)); - string messageTypeName = message.GetType().FullName; return GetPartitionForMessageAsync(messageTypeName, brokerServiceName); } - - /// - public async Task GetBrokerServiceForMessageAsync(object message, Uri brokerServiceName) - { - var resolvedPartition = await GetPartitionForMessageAsync(message, brokerServiceName); - var brokerService = _serviceProxyFactory.CreateServiceProxy(brokerServiceName, resolvedPartition, listenerName: BrokerServiceBase.ListenerName); - return brokerService; - } - - /// - public async Task GetBrokerServiceForMessageAsync(string messageTypeName, Uri brokerServiceName) - { - var resolvedPartition = await GetPartitionForMessageAsync(messageTypeName, brokerServiceName); - var brokerService = _serviceProxyFactory.CreateServiceProxy(brokerServiceName, resolvedPartition, listenerName: BrokerServiceBase.ListenerName); - return brokerService; - } } } diff --git a/ServiceFabric.PubSubActors.Interfaces/DefaultPayloadSerializer.cs b/ServiceFabric.PubSubActors/Helpers/DefaultPayloadSerializer.cs similarity index 95% rename from ServiceFabric.PubSubActors.Interfaces/DefaultPayloadSerializer.cs rename to ServiceFabric.PubSubActors/Helpers/DefaultPayloadSerializer.cs index 17c58cb..0f0efd2 100644 --- a/ServiceFabric.PubSubActors.Interfaces/DefaultPayloadSerializer.cs +++ b/ServiceFabric.PubSubActors/Helpers/DefaultPayloadSerializer.cs @@ -1,7 +1,8 @@ using System; using Newtonsoft.Json; +using ServiceFabric.PubSubActors.State; -namespace ServiceFabric.PubSubActors.Interfaces +namespace ServiceFabric.PubSubActors.Helpers { /// /// The default serializer to use for diff --git a/ServiceFabric.PubSubActors/Helpers/IBrokerClient.cs b/ServiceFabric.PubSubActors/Helpers/IBrokerClient.cs new file mode 100644 index 0000000..59b7f50 --- /dev/null +++ b/ServiceFabric.PubSubActors/Helpers/IBrokerClient.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using ServiceFabric.PubSubActors.State; + +namespace ServiceFabric.PubSubActors.Helpers +{ + public interface IBrokerClient + { + /// + /// Publish a message of type . + /// + /// + /// + Task PublishMessageAsync(T message) where T : class; + + /// + /// Registers this Service or Actor as a subscriber for messages of type with the . + /// + /// + /// + /// + /// + Task SubscribeAsync(ReferenceWrapper referenceWrapper, Type messageType, Func handler) where T : class; + + /// + /// Unregisters this Service or Actor as a subscriber for messages of type with the . + /// + /// + /// + /// + Task UnsubscribeAsync(ReferenceWrapper referenceWrapper, Type messageType); + + /// + /// Given a , call the handler given for that type when was called. + /// + /// + /// + Task ProcessMessageAsync(MessageWrapper messageWrapper); + } +} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/Helpers/IBrokerServiceLocator.cs b/ServiceFabric.PubSubActors/Helpers/IBrokerServiceLocator.cs index 2ef415a..f5bd070 100644 --- a/ServiceFabric.PubSubActors/Helpers/IBrokerServiceLocator.cs +++ b/ServiceFabric.PubSubActors/Helpers/IBrokerServiceLocator.cs @@ -19,29 +19,13 @@ public interface IBrokerServiceLocator /// Task RegisterAsync(Uri brokerServiceName); - /// - /// Resolves the to send the message to, based on message type name. - /// - /// Full type name of message object. - /// - /// - Task GetPartitionForMessageAsync(string messageTypeName, Uri brokerServiceName); - - /// - /// Resolves the to send the message to, based on message's type. - /// - /// The message to publish - /// - /// - Task GetPartitionForMessageAsync(object message, Uri brokerServiceName); - /// /// Gets the instance for the provided /// /// /// Uri of BrokerService instance /// - Task GetBrokerServiceForMessageAsync(object message, Uri brokerServiceName); + Task GetBrokerServiceForMessageAsync(object message, Uri brokerServiceName = null); /// /// Gets the instance for the provided @@ -49,6 +33,6 @@ public interface IBrokerServiceLocator /// Full type name of message object. /// Uri of BrokerService instance /// - Task GetBrokerServiceForMessageAsync(string messageTypeName, Uri brokerServiceName); + Task GetBrokerServiceForMessageAsync(string messageTypeName, Uri brokerServiceName = null); } } \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/Helpers/IPublisherActorHelper.cs b/ServiceFabric.PubSubActors/Helpers/IPublisherActorHelper.cs deleted file mode 100644 index 03e801a..0000000 --- a/ServiceFabric.PubSubActors/Helpers/IPublisherActorHelper.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.ServiceFabric.Actors.Runtime; - -namespace ServiceFabric.PubSubActors.Helpers -{ - public interface IPublisherActorHelper - { - /// - /// Publish a message. - /// - /// - /// - /// The name of the SF Service of type . - /// - Task PublishMessageAsync(ActorBase actor, object message, Uri brokerServiceName = null); - } -} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/Helpers/IPublisherServiceHelper.cs b/ServiceFabric.PubSubActors/Helpers/IPublisherServiceHelper.cs deleted file mode 100644 index b3b1635..0000000 --- a/ServiceFabric.PubSubActors/Helpers/IPublisherServiceHelper.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.ServiceFabric.Services.Runtime; - -namespace ServiceFabric.PubSubActors.Helpers -{ - public interface IPublisherServiceHelper - { - /// - /// Publish a message. - /// - /// - /// The name of a SF Service of type . - /// - Task PublishMessageAsync(object message, Uri brokerServiceName = null); - - /// - /// Publish a message. - /// - /// - /// - /// The name of a SF Service of type . - /// - Task PublishMessageAsync(StatelessService service, object message, Uri brokerServiceName = null); - - /// - /// Publish a message. - /// - /// - /// - /// The name of a SF Service of type . - /// - Task PublishMessageAsync(StatefulServiceBase service, object message, Uri brokerServiceName = null); - } -} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/Helpers/ISubscriberActorHelper.cs b/ServiceFabric.PubSubActors/Helpers/ISubscriberActorHelper.cs deleted file mode 100644 index 2c797c9..0000000 --- a/ServiceFabric.PubSubActors/Helpers/ISubscriberActorHelper.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.ServiceFabric.Actors.Runtime; - -namespace ServiceFabric.PubSubActors.Helpers -{ - public interface ISubscriberActorHelper - { - /// - /// Registers this Actor as a subscriber for messages of type with the . - /// - /// - Task RegisterMessageTypeAsync(ActorBase actor, Type messageType, Uri brokerServiceName = null, string routingKey = null); - - /// - /// Unregisters this Actor as a subscriber for messages of type with the . - /// - /// - Task UnregisterMessageTypeAsync(ActorBase actor, Type messageType, bool flushQueue, - Uri brokerServiceName = null); - } -} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/Helpers/ISubscriberServiceHelper.cs b/ServiceFabric.PubSubActors/Helpers/ISubscriberServiceHelper.cs deleted file mode 100644 index c6c45c1..0000000 --- a/ServiceFabric.PubSubActors/Helpers/ISubscriberServiceHelper.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Microsoft.ServiceFabric.Services.Runtime; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using ServiceFabric.PubSubActors.Interfaces; -using ServiceFabric.PubSubActors.SubscriberServices; - -namespace ServiceFabric.PubSubActors.Helpers -{ - public interface ISubscriberServiceHelper - { - /// - /// Registers this Actor as a subscriber for messages of type with the . - /// - /// - Task RegisterMessageTypeAsync(StatelessService service, Type messageType, - Uri brokerServiceName = null, string listenerName = null); - - /// - /// Unregisters this Actor as a subscriber for messages of type with the . - /// - /// - Task UnregisterMessageTypeAsync(StatelessService service, Type messageType, bool flushQueue, - Uri brokerServiceName = null); - - /// - /// Registers this Actor as a subscriber for messages of type with the . - /// - /// - Task RegisterMessageTypeAsync(StatefulService service, Type messageType, - Uri brokerServiceName = null, string listenerName = null); - - /// - /// Unregisters this Actor as a subscriber for messages of type with the . - /// - /// - Task UnregisterMessageTypeAsync(StatefulService service, Type messageType, bool flushQueue, - Uri brokerServiceName = null); - - /// - /// Look for Subscribe attributes and create a list of SubscriptionDefinitions to map message types to handlers. - /// - /// - /// - Dictionary> DiscoverMessageHandlers(T handlerClass) where T : class; - - /// - /// Subscribe to all message types in . - /// - /// - /// - /// - /// - Task SubscribeAsync(ServiceReference serviceReference, IEnumerable messageTypes, Uri broker = null); - - /// - /// Given the , invoke the appropriate handler in . - /// - /// - /// - /// - Task ProccessMessageAsync(MessageWrapper messageWrapper, Dictionary> handlers); - - /// - /// Creates a ServiceReference object for a StatelessService. Used for subscribing/unsubscribing. - /// - /// - /// - /// - ServiceReference CreateServiceReference(StatelessService service, string listenerName = null); - - /// - /// Creates a ServiceReference object for a StatelessService. Used for subscribing/unsubscribing. - /// - /// - /// - /// - ServiceReference CreateServiceReference(StatefulService service, string listenerName = null); - } -} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/Helpers/PublisherActorHelper.cs b/ServiceFabric.PubSubActors/Helpers/PublisherActorHelper.cs deleted file mode 100644 index ed4cb42..0000000 --- a/ServiceFabric.PubSubActors/Helpers/PublisherActorHelper.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.ServiceFabric.Actors.Runtime; -using ServiceFabric.PubSubActors.Interfaces; - -namespace ServiceFabric.PubSubActors.Helpers -{ - /// - /// Common operations of - /// - public class PublisherActorHelper : IPublisherActorHelper - { - private readonly IBrokerServiceLocator _brokerServiceLocator; - - public PublisherActorHelper() - { - _brokerServiceLocator = new BrokerServiceLocator(); - } - - public PublisherActorHelper(IBrokerServiceLocator brokerServiceLocator) - { - _brokerServiceLocator = brokerServiceLocator; - } - - /// - /// Publish a message. - /// - /// - /// - /// The name of the SF Service of type . - /// - public async Task PublishMessageAsync(ActorBase actor, object message, Uri brokerServiceName = null) - { - if (actor == null) throw new ArgumentNullException(nameof(actor)); - if (message == null) throw new ArgumentNullException(nameof(message)); - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServiceHelper.DiscoverBrokerServiceNameAsync(); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - - var wrapper = message.CreateMessageWrapper(); - var brokerService = await _brokerServiceLocator.GetBrokerServiceForMessageAsync(message, brokerServiceName); - await brokerService.PublishMessageAsync(wrapper); - } - } -} diff --git a/ServiceFabric.PubSubActors/Helpers/PublisherServiceHelper.cs b/ServiceFabric.PubSubActors/Helpers/PublisherServiceHelper.cs deleted file mode 100644 index 1bbf21b..0000000 --- a/ServiceFabric.PubSubActors/Helpers/PublisherServiceHelper.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.ServiceFabric.Services.Runtime; -using ServiceFabric.PubSubActors.Interfaces; - -namespace ServiceFabric.PubSubActors.Helpers -{ - public class PublisherServiceHelper : IPublisherServiceHelper - { - private readonly IBrokerServiceLocator _brokerServiceLocator; - - public PublisherServiceHelper() - { - _brokerServiceLocator = new BrokerServiceLocator(); - } - - public PublisherServiceHelper(IBrokerServiceLocator brokerServiceLocator) - { - _brokerServiceLocator = brokerServiceLocator; - } - - /// - /// Publish a message. - /// - /// - /// The name of a SF Service of type . - /// - public async Task PublishMessageAsync(object message, Uri brokerServiceName = null) - { - if (message == null) throw new ArgumentNullException(nameof(message)); - if (brokerServiceName == null) - { - brokerServiceName = await DiscoverBrokerServiceNameAsync(); - if (brokerServiceName == null) - { - throw new InvalidOperationException( - "No brokerServiceName was provided or discovered in the current application."); - } - } - - var brokerService = await _brokerServiceLocator.GetBrokerServiceForMessageAsync(message, brokerServiceName); - var wrapper = message.CreateMessageWrapper(); - await brokerService.PublishMessageAsync(wrapper); - } - - /// - /// Publish a message. - /// - /// - /// - /// The name of a SF Service of type . - /// - public async Task PublishMessageAsync(StatelessService service, object message, - Uri brokerServiceName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - await PublishMessageAsync(message, brokerServiceName); - } - - /// - /// Publish a message. - /// - /// - /// - /// The name of a SF Service of type . - /// - public async Task PublishMessageAsync(StatefulServiceBase service, object message, - Uri brokerServiceName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - await PublishMessageAsync(message, brokerServiceName); - } - - /// - /// Attempts to discover the running in this Application. - /// - /// - public static Task DiscoverBrokerServiceNameAsync() - { - var locator = new BrokerServiceLocator(); - return locator.LocateAsync(); - } - } -} diff --git a/ServiceFabric.PubSubActors/Helpers/SubscriberActorHelper.cs b/ServiceFabric.PubSubActors/Helpers/SubscriberActorHelper.cs deleted file mode 100644 index 6940282..0000000 --- a/ServiceFabric.PubSubActors/Helpers/SubscriberActorHelper.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.ServiceFabric.Actors; -using Microsoft.ServiceFabric.Actors.Runtime; - -namespace ServiceFabric.PubSubActors.Helpers -{ - /// - /// Common operations for Actors to become Subscribers - /// - public class SubscriberActorHelper : ISubscriberActorHelper - { - - private readonly IBrokerServiceLocator _brokerServiceLocator; - - public SubscriberActorHelper() - { - _brokerServiceLocator = new BrokerServiceLocator(); - } - - public SubscriberActorHelper(IBrokerServiceLocator brokerServiceLocator) - { - _brokerServiceLocator = brokerServiceLocator; - } - /// - /// Registers this Actor as a subscriber for messages of type with the . - /// - /// - public async Task RegisterMessageTypeAsync(ActorBase actor, Type messageType, Uri brokerServiceName = null, string routingKey = null) - { - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (actor == null) throw new ArgumentNullException(nameof(actor)); - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServiceHelper.DiscoverBrokerServiceNameAsync(); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = await _brokerServiceLocator.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - await brokerService.RegisterSubscriberAsync(ActorReference.Get(actor), messageType.FullName, routingKey); - } - - /// - /// Unregisters this Actor as a subscriber for messages of type with the . - /// - /// - public async Task UnregisterMessageTypeAsync(ActorBase actor, Type messageType, bool flushQueue, - Uri brokerServiceName = null) - { - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (actor == null) throw new ArgumentNullException(nameof(actor)); - - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServiceHelper.DiscoverBrokerServiceNameAsync(); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = await _brokerServiceLocator.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - await brokerService.UnregisterSubscriberAsync(ActorReference.Get(actor), messageType.FullName, flushQueue); - } - } -} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/Helpers/SubscriberServiceHelper.cs b/ServiceFabric.PubSubActors/Helpers/SubscriberServiceHelper.cs deleted file mode 100644 index bfc9036..0000000 --- a/ServiceFabric.PubSubActors/Helpers/SubscriberServiceHelper.cs +++ /dev/null @@ -1,268 +0,0 @@ -using Microsoft.ServiceFabric.Services.Runtime; -using ServiceFabric.PubSubActors.Interfaces; -using System; -using System.Collections.Generic; -using System.Fabric; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using ServiceFabric.PubSubActors.SubscriberServices; - -namespace ServiceFabric.PubSubActors.Helpers -{ - public class SubscriberServiceHelper : ISubscriberServiceHelper - { - /// - /// When Set, this callback will be used to trace Service messages to. - /// - private readonly Action _logCallback; - - private readonly IBrokerServiceLocator _brokerServiceLocator; - - public SubscriberServiceHelper() - { - _brokerServiceLocator = new BrokerServiceLocator(); - } - - public SubscriberServiceHelper(IBrokerServiceLocator brokerServiceLocator = null, Action logCallback = null) - { - _brokerServiceLocator = brokerServiceLocator ?? new BrokerServiceLocator(); - _logCallback = logCallback; - } - - /// - /// Registers this Service as a subscriber for messages of type with the . - /// - /// - public async Task RegisterMessageTypeAsync(StatelessService service, Type messageType, - Uri brokerServiceName = null, string listenerName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - var serviceReference = CreateServiceReference(service, listenerName); - await RegisterMessageTypeAsync(serviceReference, messageType, brokerServiceName); - } - - private async Task RegisterMessageTypeAsync(ServiceReference serviceReference, Type messageType, Uri brokerServiceName = null, string routingKey = null) - { - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServiceHelper.DiscoverBrokerServiceNameAsync(); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = await _brokerServiceLocator.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - await brokerService.RegisterServiceSubscriberAsync(serviceReference, messageType.FullName, routingKey); - } - - /// - /// Unregisters this Service as a subscriber for messages of type with the . - /// - /// - public async Task UnregisterMessageTypeAsync(StatelessService service, Type messageType, bool flushQueue, - Uri brokerServiceName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServiceHelper.DiscoverBrokerServiceNameAsync(); - if (brokerServiceName == null) - { - throw new InvalidOperationException( - "No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = - await _brokerServiceLocator.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - var serviceReference = CreateServiceReference(service); - await brokerService.UnregisterServiceSubscriberAsync(serviceReference, messageType.FullName, flushQueue); - } - - /// - /// Registers this Actor as a subscriber for messages of type with the . - /// - /// - public async Task RegisterMessageTypeAsync(StatefulService service, Type messageType, - Uri brokerServiceName = null, string listenerName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - var serviceReference = CreateServiceReference(service, listenerName); - await RegisterMessageTypeAsync(serviceReference, messageType, brokerServiceName); - } - - /// - /// Unregisters this Actor as a subscriber for messages of type with the . - /// - /// - public async Task UnregisterMessageTypeAsync(StatefulService service, Type messageType, bool flushQueue, - Uri brokerServiceName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (brokerServiceName == null) - { - brokerServiceName = await _brokerServiceLocator.LocateAsync(); - if (brokerServiceName == null) - { - throw new InvalidOperationException( - "No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = - await _brokerServiceLocator.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - var serviceReference = CreateServiceReference(service); - await brokerService.UnregisterServiceSubscriberAsync(serviceReference, messageType.FullName, flushQueue); - } - - /// - public async Task SubscribeAsync(ServiceReference serviceReference, IEnumerable messageTypes, Uri broker = null) - { - foreach (var messageType in messageTypes) - { - try - { - await RegisterMessageTypeAsync(serviceReference, messageType, broker); - LogMessage($"Registered Service:'{serviceReference.ServiceUri}' as Subscriber of {messageType}."); - } - catch (Exception ex) - { - LogMessage($"Failed to register Service:'{serviceReference.ServiceUri}' as Subscriber of {messageType}. Error:'{ex.Message}'."); - } - } - } - - /// - public Dictionary> DiscoverMessageHandlers(T handlerClass) where T : class - { - Dictionary> handlers = new Dictionary>(); - Type taskType = typeof(Task); - var methods = handlerClass.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); - foreach (var method in methods) - { - var subscribeAttribute = method.GetCustomAttributes(typeof(SubscribeAttribute), false) - .Cast() - .SingleOrDefault(); - - if (subscribeAttribute == null) continue; - - var parameters = method.GetParameters(); - if (parameters.Length != 1 || !taskType.IsAssignableFrom(method.ReturnType)) continue; - - handlers[parameters[0].ParameterType] = m => (Task) method.Invoke(handlerClass, new[] {m}); - } - - return handlers; - } - - /// - public Task ProccessMessageAsync(MessageWrapper messageWrapper, Dictionary> handlers) - { - var messageType = Assembly.Load(messageWrapper.Assembly).GetType(messageWrapper.MessageType, true); - - while (messageType != null) - { - if (handlers.TryGetValue(messageType, out var handler)) - { - return handler(messageWrapper.CreateMessage()); - } - messageType = messageType.BaseType; - } - - return Task.FromResult(true); - } - - /// - public ServiceReference CreateServiceReference(StatelessService service, string listenerName = null) - { - return CreateServiceReference(service.Context, GetServicePartition(service).PartitionInfo, listenerName); - } - - /// - public ServiceReference CreateServiceReference(StatefulService service, string listenerName = null) - { - return CreateServiceReference(service.Context, GetServicePartition(service).PartitionInfo, listenerName); - } - - /// - /// Gets the Partition info for the provided StatefulServiceBase instance. - /// - /// - /// - private IStatefulServicePartition GetServicePartition(StatefulServiceBase serviceBase) - { - if (serviceBase == null) throw new ArgumentNullException(nameof(serviceBase)); - return (IStatefulServicePartition)serviceBase - .GetType() - .GetProperty("Partition", BindingFlags.Instance | BindingFlags.NonPublic)? - .GetValue(serviceBase) ?? throw new ArgumentNullException($"Unable to find partition information for service: {serviceBase}"); - } - - /// - /// Gets the Partition info for the provided StatelessService instance. - /// - /// - /// - private static IStatelessServicePartition GetServicePartition(StatelessService serviceBase) - { - if (serviceBase == null) throw new ArgumentNullException(nameof(serviceBase)); - return (IStatelessServicePartition)serviceBase - .GetType() - .GetProperty("Partition", BindingFlags.Instance | BindingFlags.NonPublic)? - .GetValue(serviceBase) ?? throw new ArgumentNullException($"Unable to find partition information for service: {serviceBase}"); - } - - /// - /// Creates a for the provided service context and partition info. - /// - /// - /// - /// (optional) The name of the listener that is used to communicate with the service - /// - private static ServiceReference CreateServiceReference(ServiceContext context, ServicePartitionInformation info, string listenerName = null) - { - var serviceReference = new ServiceReference - { - ApplicationName = context.CodePackageActivationContext.ApplicationName, - PartitionKind = info.Kind, - ServiceUri = context.ServiceName, - PartitionGuid = context.PartitionId, - ListenerName = listenerName - }; - - if (info is Int64RangePartitionInformation longInfo) - { - serviceReference.PartitionKey = longInfo.LowKey; - } - else if (info is NamedPartitionInformation stringInfo) - { - serviceReference.PartitionName = stringInfo.Name; - } - - return serviceReference; - } - - /// - /// Outputs the provided message to the if it's configured. - /// - /// - /// - protected void LogMessage(string message, [CallerMemberName] string caller = "unknown") - { - _logCallback?.Invoke($"{caller} - {message}"); - } - } - - /// - /// Marks a service method as being capable of receiving messages. - /// Follows convention that method has signature 'Task MethodName(MessageType message)' - /// Polymorphism is supported. - /// - [AttributeUsage(AttributeTargets.Method, Inherited = false)] - public class SubscribeAttribute : Attribute - { - } -} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/TimeoutRetryHelper.cs b/ServiceFabric.PubSubActors/Helpers/TimeoutRetryHelper.cs similarity index 99% rename from ServiceFabric.PubSubActors/TimeoutRetryHelper.cs rename to ServiceFabric.PubSubActors/Helpers/TimeoutRetryHelper.cs index 6d7845a..f9b737a 100644 --- a/ServiceFabric.PubSubActors/TimeoutRetryHelper.cs +++ b/ServiceFabric.PubSubActors/Helpers/TimeoutRetryHelper.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using Microsoft.ServiceFabric.Data; -namespace ServiceFabric.PubSubActors +namespace ServiceFabric.PubSubActors.Helpers { /// /// Provides retry support when using the . diff --git a/ServiceFabric.PubSubActors/IBrokerService.cs b/ServiceFabric.PubSubActors/IBrokerService.cs index 2c87e5e..7820051 100644 --- a/ServiceFabric.PubSubActors/IBrokerService.cs +++ b/ServiceFabric.PubSubActors/IBrokerService.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Microsoft.ServiceFabric.Services.Remoting; +using ServiceFabric.PubSubActors.State; namespace ServiceFabric.PubSubActors { @@ -9,42 +10,24 @@ namespace ServiceFabric.PubSubActors public interface IBrokerService : IService { /// - /// Registers an Actor as a subscriber for messages. + /// Registers a Service or Actor as a subscriber for messages. /// - /// Reference to the actor to register. + /// Reference to the service or actor to register. /// The full type name of the message to subscribe to. - /// Optional routing key to filter messages based on content. 'Key=Value' where Key is a message property path and Value is the value to match with message payload content. - Task RegisterSubscriberAsync(Microsoft.ServiceFabric.Actors.ActorReference actor, string messageTypeName, string routingKey); + Task SubscribeAsync(ReferenceWrapper reference, string messageTypeName); /// - /// Unregisters an Actor as a subscriber for messages. + /// Unregisters a Service or Actor as a subscriber for messages. /// /// The full type name of the message to subscribe to. - /// Reference to the actor to unregister. - /// Publish any remaining messages. - Task UnregisterSubscriberAsync(Microsoft.ServiceFabric.Actors.ActorReference actor, string messageTypeName, bool flushQueue); - - /// - /// Registers a service as a subscriber for messages. - /// - /// The full type name of the message to subscribe to. - /// Reference to the Service to register. - /// Optional routing key to filter messages based on content. 'Key=Value' where Key is a message property path and Value is the value to match with message payload content. - Task RegisterServiceSubscriberAsync(Interfaces.ServiceReference service, string messageTypeName, string routingKey); - - /// - /// Unregisters a service as a subscriber for messages. - /// - /// The full type name of the message to subscribe to. - /// Reference to the Service to unregister. - /// Publish any remaining messages. - Task UnregisterServiceSubscriberAsync(Interfaces.ServiceReference service, string messageTypeName, bool flushQueue); + /// Reference to the service or actor to unregister. + Task UnsubscribeAsync(ReferenceWrapper reference, string messageTypeName); /// /// Takes a published message and forwards it (indirectly) to all Subscribers. /// /// The message to publish /// - Task PublishMessageAsync(Interfaces.MessageWrapper message); + Task PublishMessageAsync(MessageWrapper message); } } diff --git a/ServiceFabric.PubSubActors/PublisherActors/PublisherActorExtensions.cs b/ServiceFabric.PubSubActors/PublisherActors/PublisherActorExtensions.cs deleted file mode 100644 index b6a8dae..0000000 --- a/ServiceFabric.PubSubActors/PublisherActors/PublisherActorExtensions.cs +++ /dev/null @@ -1,153 +0,0 @@ -using Microsoft.ServiceFabric.Actors; -using Microsoft.ServiceFabric.Actors.Client; -using Microsoft.ServiceFabric.Actors.Runtime; -using Microsoft.ServiceFabric.Services.Client; -using Microsoft.ServiceFabric.Services.Remoting.Client; -using ServiceFabric.PubSubActors.Interfaces; -using System; -using System.Fabric; -using System.Fabric.Query; -using System.Threading.Tasks; -using ServiceFabric.PubSubActors.State; - -namespace ServiceFabric.PubSubActors.PublisherActors -{ - /// - /// Common operations of - /// - public static class PublisherActorExtensions - { - /// - /// Publish a message. - /// - /// - /// - /// The name of the SF application that hosts the . If not provided, actor.ApplicationName will be used. - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static async Task PublishMessageAsync(this ActorBase actor, object message, string applicationName = null) - { - if (actor == null) throw new ArgumentNullException(nameof(actor)); - if (message == null) throw new ArgumentNullException(nameof(message)); - - if (string.IsNullOrWhiteSpace(applicationName)) - { - applicationName = actor.ApplicationName; - } - - var brokerActor = GetBrokerActorForMessage(message, applicationName); - var wrapper = message.CreateMessageWrapper(); - await brokerActor.PublishMessageAsync(wrapper); - } - - - - /// - /// Gets the instance for the provided - /// - /// - /// - /// - private static IBrokerActor GetBrokerActorForMessage(object message, string applicationName) - { - ActorId actorId = new ActorId(message.GetType().FullName); - IBrokerActor brokerActor = ActorProxy.Create(actorId, applicationName, nameof(IBrokerActor)); - return brokerActor; - } - - - - - /////broker service code - private static ServicePartitionList _cachedPartitions; - - /// - /// Publish a message. - /// - /// - /// - /// The name of the SF Service of type . - /// - [Obsolete("Use ServiceFabric.PubSubActors.Helpers.PublisherActorHelper for testability")] - public static async Task PublishMessageToBrokerServiceAsync(this ActorBase actor, object message, Uri brokerServiceName = null) - { - if (actor == null) throw new ArgumentNullException(nameof(actor)); - if (message == null) throw new ArgumentNullException(nameof(message)); - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServices.PublisherServiceExtensions.DiscoverBrokerServiceNameAsync(new Uri(actor.ApplicationName)); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - - - var wrapper = message.CreateMessageWrapper(); - var brokerService = await GetBrokerServiceForMessageAsync(message, brokerServiceName); - await brokerService.PublishMessageAsync(wrapper); - } - - /// - /// Resolves the to send the message to, based on message type. - /// - /// The message to publish - /// - /// - public static async Task GetPartitionForMessageAsync(object message, Uri brokerServiceName, IHashingHelper hashingHelper = null) - { - if (message == null) throw new ArgumentNullException(nameof(message)); - if (brokerServiceName == null) throw new ArgumentNullException(nameof(brokerServiceName)); - if (hashingHelper == null) hashingHelper = new HashingHelper(); - - string messageTypeName = message.GetType().FullName; - - if (_cachedPartitions == null) - { - var fabricClient = new FabricClient(); - _cachedPartitions = await fabricClient.QueryManager.GetPartitionListAsync(brokerServiceName); - } - int hashCode; - unchecked - { - hashCode = (int)hashingHelper.HashString(messageTypeName); - } - int index = Math.Abs(hashCode % _cachedPartitions.Count); - var partition = _cachedPartitions[index]; - if (partition.PartitionInformation.Kind != ServicePartitionKind.Int64Range) - { - throw new InvalidOperationException("Sorry, only Int64 Range Partitions are supported."); - } - - var info = (Int64RangePartitionInformation)partition.PartitionInformation; - var resolvedPartition = new ServicePartitionKey(info.LowKey); - - return resolvedPartition; - } - - /// - /// Gets the instance for the provided - /// - /// - /// Uri of BrokerService instance - /// - public static Task GetBrokerServiceForMessageAsync(object message, Uri brokerServiceName) - { - return GetBrokerServiceForMessageAsync(message.GetType().FullName, brokerServiceName); - } - - /// - /// Gets the instance for the provided - /// - /// Full type name of message object. - /// Uri of BrokerService instance - /// - public static async Task GetBrokerServiceForMessageAsync(string messageTypeName, Uri brokerServiceName) - { - - var resolvedPartition = await GetPartitionForMessageAsync(messageTypeName, brokerServiceName); - var brokerService = ServiceProxy.Create(brokerServiceName, resolvedPartition, listenerName: BrokerServiceBase.ListenerName); - return brokerService; - } - } -} diff --git a/ServiceFabric.PubSubActors/PublisherServices/PublisherServiceExtensions.cs b/ServiceFabric.PubSubActors/PublisherServices/PublisherServiceExtensions.cs deleted file mode 100644 index fab02aa..0000000 --- a/ServiceFabric.PubSubActors/PublisherServices/PublisherServiceExtensions.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.ServiceFabric.Actors; -using Microsoft.ServiceFabric.Actors.Client; -using Microsoft.ServiceFabric.Services.Runtime; -using ServiceFabric.PubSubActors.Interfaces; -using ServiceFabric.PubSubActors.PublisherActors; - -namespace ServiceFabric.PubSubActors.PublisherServices -{ - public static class PublisherServiceExtensions - { - /// - /// Publish a message. - /// - /// - /// - /// The name of the SF application that hosts the . If not provided, ServiceInitializationParameters.CodePackageActivationContext.ApplicationName will be used. - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static async Task PublishMessageAsync(this StatelessService service, object message, - string applicationName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (message == null) throw new ArgumentNullException(nameof(message)); - - if (string.IsNullOrWhiteSpace(applicationName)) - { - applicationName = service.Context.CodePackageActivationContext.ApplicationName; - } - - var brokerActor = GetBrokerActorForMessage(applicationName, message); - var wrapper = message.CreateMessageWrapper(); - await brokerActor.PublishMessageAsync(wrapper); - } - - /// - /// Publish a message. - /// - /// - /// - /// The name of the SF application that hosts the . If not provided, ServiceInitializationParameters.CodePackageActivationContext.ApplicationName will be used. - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static async Task PublishMessageAsync(this StatefulServiceBase service, object message, - string applicationName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (message == null) throw new ArgumentNullException(nameof(message)); - - if (string.IsNullOrWhiteSpace(applicationName)) - { - applicationName = service.Context.CodePackageActivationContext.ApplicationName; - } - - var brokerActor = GetBrokerActorForMessage(applicationName, message); - var wrapper = message.CreateMessageWrapper(); - await brokerActor.PublishMessageAsync(wrapper); - } - - /// - /// Gets the instance for the provided - /// - /// - /// - /// - private static IBrokerActor GetBrokerActorForMessage(string applicationName, object message) - { - ActorId actorId = new ActorId(message.GetType().FullName); - IBrokerActor brokerActor = ActorProxy.Create(actorId, applicationName, nameof(IBrokerActor)); - return brokerActor; - } - - - ///////broker service code - - - /// - /// Publish a message. - /// - /// - /// - /// The name of a SF Service of type . - /// - [Obsolete("Use ServiceFabric.PubSubActors.Helpers.PublisherServiceHelper for testability")] - public static async Task PublishMessageToBrokerServiceAsync(this StatelessService service, object message, - Uri brokerServiceName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (message == null) throw new ArgumentNullException(nameof(message)); - if (brokerServiceName == null) - { - brokerServiceName = - await - DiscoverBrokerServiceNameAsync( - new Uri(service.Context.CodePackageActivationContext.ApplicationName)); - if (brokerServiceName == null) - { - throw new InvalidOperationException( - "No brokerServiceName was provided or discovered in the current application."); - } - } - - var brokerService = - await PublisherActorExtensions.GetBrokerServiceForMessageAsync(message, brokerServiceName); - var wrapper = message.CreateMessageWrapper(); - await brokerService.PublishMessageAsync(wrapper); - } - - /// - /// Publish a message. - /// - /// - /// - /// The name of a SF Service of type . - /// - [Obsolete("Use ServiceFabric.PubSubActors.Helpers.PublisherServiceHelper for testability")] - public static async Task PublishMessageToBrokerServiceAsync(this StatefulServiceBase service, object message, - Uri brokerServiceName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (message == null) throw new ArgumentNullException(nameof(message)); - if (brokerServiceName == null) - { - brokerServiceName = - await - DiscoverBrokerServiceNameAsync( - new Uri(service.Context.CodePackageActivationContext.ApplicationName)); - if (brokerServiceName == null) - { - throw new InvalidOperationException( - "No brokerServiceName was provided or discovered in the current application."); - } - } - - var brokerService = - await PublisherActorExtensions.GetBrokerServiceForMessageAsync(message, brokerServiceName); - var wrapper = message.CreateMessageWrapper(); - await brokerService.PublishMessageAsync(wrapper); - } - - /// - /// Attempts to discover the running in this Application. - /// - /// - /// - [Obsolete("Use ServiceFabric.PubSubActors.Helpers.BrokerServiceLocator for testability")] - - public static async Task DiscoverBrokerServiceNameAsync(Uri applicationName) - { - try - { - var fc = new System.Fabric.FabricClient(); - var property = await fc.PropertyManager.GetPropertyAsync(applicationName, nameof(BrokerService)); - if (property == null) return null; - string value = property.GetValue(); - return new Uri(value); - } - // ReSharper disable once EmptyGeneralCatchClause - catch - { - } - return null; - } - } -} diff --git a/ServiceFabric.PubSubActors/RelayBrokerActor.cs b/ServiceFabric.PubSubActors/RelayBrokerActor.cs deleted file mode 100644 index 0345762..0000000 --- a/ServiceFabric.PubSubActors/RelayBrokerActor.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.ServiceFabric.Actors; -using Microsoft.ServiceFabric.Actors.Runtime; -using ServiceFabric.PubSubActors.Interfaces; - -namespace ServiceFabric.PubSubActors -{ - /// - /// Special implementation of that receives and forwards incoming messages. - /// - [StatePersistence(StatePersistence.Persisted)] - [Obsolete("This class will be removed in the next major upgrade. Use the BrokerService instead.")] - public class RelayBrokerActor : BrokerActor, IRelayBrokerActor - { - /// - /// Initializes a new instance of - /// - /// - /// The that will host this actor instance. - /// - /// - /// The for this actor instance. - /// - public RelayBrokerActor(ActorService actorService, ActorId actorId) - : base(actorService, actorId) - { - } - - /// - /// Publishes the received message to all registered subscribers. - /// - /// - /// - public Task ReceiveMessageAsync(MessageWrapper message) - { - message.IsRelayed = true; - return PublishMessageAsync(message); - } - } -} diff --git a/ServiceFabric.PubSubActors/ServiceFabric.PubSubActors.csproj b/ServiceFabric.PubSubActors/ServiceFabric.PubSubActors.csproj index be2f3b3..b456a17 100644 --- a/ServiceFabric.PubSubActors/ServiceFabric.PubSubActors.csproj +++ b/ServiceFabric.PubSubActors/ServiceFabric.PubSubActors.csproj @@ -3,7 +3,7 @@ ServiceFabric.PubSubActors adds pub/sub behaviour to your Reliable Actors and Services in Service Fabric. How to use this package: https://github.com/loekd/ServiceFabric.PubSubActors/blob/master/README.md 2019 ServiceFabric.PubSubActors - 7.6.2 + 8.0.0 Loek Duys net452;net46;netstandard2.0 x64 @@ -36,9 +36,6 @@ true - - - diff --git a/ServiceFabric.PubSubActors/State/ActorReferenceWrapper.cs b/ServiceFabric.PubSubActors/State/ActorReferenceWrapper.cs index 79e711a..8ee070b 100644 --- a/ServiceFabric.PubSubActors/State/ActorReferenceWrapper.cs +++ b/ServiceFabric.PubSubActors/State/ActorReferenceWrapper.cs @@ -1,9 +1,11 @@ using Microsoft.ServiceFabric.Actors; using Newtonsoft.Json.Linq; -using ServiceFabric.PubSubActors.Interfaces; using System; using System.Runtime.Serialization; using System.Threading.Tasks; +using Microsoft.ServiceFabric.Actors; +using ServiceFabric.PubSubActors.Helpers; +using ServiceFabric.PubSubActors.Subscriber; namespace ServiceFabric.PubSubActors.State { @@ -13,11 +15,7 @@ namespace ServiceFabric.PubSubActors.State [DataContract] public class ActorReferenceWrapper : ReferenceWrapper { - - public override string Name - { - get { return $"{ActorReference.ServiceUri}\t{ActorReference.ActorId}"; } - } + public override string Name => $"{ActorReference.ServiceUri}\t{ActorReference.ActorId}"; /// /// Gets the wrapped @@ -73,7 +71,7 @@ public override bool Equals(object obj) } /// - /// Serves as a hash function for a particular type. + /// Serves as a hash function for a particular type. /// /// /// A hash code for the current object. @@ -108,10 +106,10 @@ public override bool Equals(ReferenceWrapper other) /// public override Task PublishAsync(MessageWrapper message) { - if (string.IsNullOrWhiteSpace(RoutingKey) - || ShouldDeliverMessage(message)) + if (string.IsNullOrWhiteSpace(RoutingKey) || ShouldDeliverMessage(message)) { - return MessageWrapperExtensions.PublishAsync(this, message); + var actor = (ISubscriberActor)ActorReference.Bind(typeof(ISubscriberActor)); + return actor.ReceiveMessageAsync(message); } return Task.FromResult(true); diff --git a/ServiceFabric.PubSubActors/State/BrokerActorState.cs b/ServiceFabric.PubSubActors/State/BrokerActorState.cs deleted file mode 100644 index 2f0539d..0000000 --- a/ServiceFabric.PubSubActors/State/BrokerActorState.cs +++ /dev/null @@ -1,27 +0,0 @@ -using ServiceFabric.PubSubActors.Interfaces; -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace ServiceFabric.PubSubActors.State -{ - /// - /// State for . Contains a regular queue and a dead letter queue for every registered listener. - /// - [Obsolete("This class will be removed in the next major upgrade. Use the BrokerService instead.")] - [DataContract] - public sealed class BrokerActorState - { - /// - /// Contains messages that could not be be sent to subscribed listeners. (has a limit) - /// - [DataMember] - public Dictionary> SubscriberDeadLetters { get; set; } - - /// - /// Contains messages to be sent to subscribed listeners. - /// - [DataMember] - public Dictionary> SubscriberMessages { get; set; } - } -} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/State/BrokerServiceState.cs b/ServiceFabric.PubSubActors/State/BrokerServiceState.cs index d5dde51..6f0b061 100644 --- a/ServiceFabric.PubSubActors/State/BrokerServiceState.cs +++ b/ServiceFabric.PubSubActors/State/BrokerServiceState.cs @@ -91,13 +91,10 @@ public class Reference [DataMember] public readonly string QueueName; - [DataMember] public readonly string DeadLetterQueueName; - - public Reference(ReferenceWrapper serviceOrActorReference, string queueName, string deadLetterQueueName) + public Reference(ReferenceWrapper serviceOrActorReference, string queueName) { ServiceOrActorReference = serviceOrActorReference; QueueName = queueName; - DeadLetterQueueName = deadLetterQueueName; } } } diff --git a/ServiceFabric.PubSubActors.Interfaces/MessageWrapper.cs b/ServiceFabric.PubSubActors/State/MessageWrapper.cs similarity index 92% rename from ServiceFabric.PubSubActors.Interfaces/MessageWrapper.cs rename to ServiceFabric.PubSubActors/State/MessageWrapper.cs index 45ca9e7..0a90c51 100644 --- a/ServiceFabric.PubSubActors.Interfaces/MessageWrapper.cs +++ b/ServiceFabric.PubSubActors/State/MessageWrapper.cs @@ -1,11 +1,12 @@ using System; using System.Reflection; using System.Runtime.Serialization; +using ServiceFabric.PubSubActors.Helpers; -namespace ServiceFabric.PubSubActors.Interfaces +namespace ServiceFabric.PubSubActors.State { /// - /// Generic message format. Contains message CLR type (full name) and serialized payload. If you know the Message Type you can deserialize + /// Generic message format. Contains message CLR type (full name) and serialized payload. If you know the Message Type you can deserialize /// the payload into that object. /// [DataContract] @@ -40,7 +41,7 @@ public class MessageWrapper public static class MessageWrapperExtensions { /// - /// Gets or sets the to use when setting the . + /// Gets or sets the to use when setting the . /// Defaults to which uses Json.Net. /// public static IPayloadSerializer PayloadSerializer { get; set; } = new DefaultPayloadSerializer(); @@ -49,7 +50,7 @@ public static class MessageWrapperExtensions /// Convert the provided into a /// /// - /// + /// public static MessageWrapper CreateMessageWrapper(this object message) { var messageType = message.GetType(); @@ -66,7 +67,7 @@ public static MessageWrapper CreateMessageWrapper(this object message) /// Convert the provided into an object of type /// /// - /// + /// public static TResult CreateMessage(this MessageWrapper messageWrapper) { var message = (PayloadSerializer ?? new DefaultPayloadSerializer()).Deserialize(messageWrapper.Payload); @@ -77,7 +78,7 @@ public static TResult CreateMessage(this MessageWrapper messageWrapper) /// Convert the provided into an object of type /// /// - /// + /// public static object CreateMessage(this MessageWrapper messageWrapper) { var type = Type.GetType(messageWrapper.MessageType, false); diff --git a/ServiceFabric.PubSubActors/State/PartitionInfoExtensions.cs b/ServiceFabric.PubSubActors/State/PartitionInfoExtensions.cs deleted file mode 100644 index ed71839..0000000 --- a/ServiceFabric.PubSubActors/State/PartitionInfoExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Fabric; - -namespace ServiceFabric.PubSubActors.State -{ - public static class PartitionInfoExtensions - { - public static Int64RangePartitionInformation AsLongPartitionInfo(this ServicePartitionInformation info) - { - return info as Int64RangePartitionInformation; - } - - public static NamedPartitionInformation AsNamedPartitionInfo(this ServicePartitionInformation info) - { - return info as NamedPartitionInformation; - } - } -} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/State/ReferenceWrapper.cs b/ServiceFabric.PubSubActors/State/ReferenceWrapper.cs index a3f5800..fa56fdf 100644 --- a/ServiceFabric.PubSubActors/State/ReferenceWrapper.cs +++ b/ServiceFabric.PubSubActors/State/ReferenceWrapper.cs @@ -2,7 +2,7 @@ using System.Runtime.Serialization; using System.Threading.Tasks; using Newtonsoft.Json.Linq; -using ServiceFabric.PubSubActors.Interfaces; +using ServiceFabric.PubSubActors.Helpers; namespace ServiceFabric.PubSubActors.State { @@ -79,14 +79,6 @@ public string GetQueueName() { return GetHashCode().ToString(); } - /// - /// Creates a dead-letter queue name to use for this reference. (not message specific) - /// - /// - public string GetDeadLetterQueueName() - { - return $"DeadLetters_{GetQueueName()}"; - } /// /// Determines whether to deliver the message to the subscriber, based on and . @@ -96,12 +88,12 @@ public string GetDeadLetterQueueName() /// public bool ShouldDeliverMessage(MessageWrapper message) { - if (!(Interfaces.MessageWrapperExtensions.PayloadSerializer is DefaultPayloadSerializer)) + if (!(MessageWrapperExtensions.PayloadSerializer is DefaultPayloadSerializer)) return true; if (_routingKeyValue == null) return true; - var token = Interfaces.MessageWrapperExtensions.PayloadSerializer.Deserialize(message.Payload); + var token = MessageWrapperExtensions.PayloadSerializer.Deserialize(message.Payload); string value = (string)token.SelectToken(_routingKeyValue[0]); return string.Equals(_routingKeyValue[1], value, StringComparison.InvariantCultureIgnoreCase); diff --git a/ServiceFabric.PubSubActors.Interfaces/ServiceReference.cs b/ServiceFabric.PubSubActors/State/ServiceReference.cs similarity index 88% rename from ServiceFabric.PubSubActors.Interfaces/ServiceReference.cs rename to ServiceFabric.PubSubActors/State/ServiceReference.cs index 092d2b9..163526c 100644 --- a/ServiceFabric.PubSubActors.Interfaces/ServiceReference.cs +++ b/ServiceFabric.PubSubActors/State/ServiceReference.cs @@ -2,7 +2,7 @@ using System.Fabric; using System.Runtime.Serialization; -namespace ServiceFabric.PubSubActors.Interfaces +namespace ServiceFabric.PubSubActors.State { [DataContract] public class ServiceReference @@ -28,10 +28,6 @@ public class ServiceReference [DataMember(IsRequired = false)] public string PartitionName { get; set; } - [Obsolete("Don't use this member. It's here for backwards compat.", true)] - [DataMember(IsRequired = false)] - public long? PartitionID { get; set; } - [DataMember(IsRequired = false)] public Guid PartitionGuid { get; set; } diff --git a/ServiceFabric.PubSubActors/State/ServiceReferenceWrapper.cs b/ServiceFabric.PubSubActors/State/ServiceReferenceWrapper.cs index a819db9..1e11638 100644 --- a/ServiceFabric.PubSubActors/State/ServiceReferenceWrapper.cs +++ b/ServiceFabric.PubSubActors/State/ServiceReferenceWrapper.cs @@ -1,7 +1,7 @@ using Microsoft.ServiceFabric.Services.Client; using Microsoft.ServiceFabric.Services.Remoting.Client; -using ServiceFabric.PubSubActors.Interfaces; -using ServiceFabric.PubSubActors.SubscriberServices; +using Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client; +using ServiceFabric.PubSubActors.Subscriber; using System; using System.Fabric; using System.Runtime.Serialization; @@ -15,10 +15,10 @@ namespace ServiceFabric.PubSubActors.State [DataContract] public class ServiceReferenceWrapper : ReferenceWrapper { - public override string Name - { - get { return ServiceReference.Description; } - } + private static readonly Lazy ServiceProxyFactoryLazy = + new Lazy(() => new ServiceProxyFactory(c => new FabricTransportServiceRemotingClientFactory())); + + public override string Name => ServiceReference.Description; /// /// Gets the wrapped @@ -70,7 +70,7 @@ public override bool Equals(object obj) } /// - /// Serves as a hash function for a particular type. + /// Serves as a hash function for a particular type. /// /// /// A hash code for the current object. @@ -122,57 +122,28 @@ public override bool Equals(ReferenceWrapper other) /// public override Task PublishAsync(MessageWrapper message) { - if (string.IsNullOrWhiteSpace(RoutingKey) - || ShouldDeliverMessage(message)) + if (string.IsNullOrWhiteSpace(RoutingKey) || ShouldDeliverMessage(message)) { - return MessageWrapperExtensions.PublishAsync(this, message); + ServicePartitionKey partitionKey; + switch (ServiceReference.PartitionKind) + { + case ServicePartitionKind.Singleton: + partitionKey = ServicePartitionKey.Singleton; + break; + case ServicePartitionKind.Int64Range: + partitionKey = new ServicePartitionKey(ServiceReference.PartitionKey); + break; + case ServicePartitionKind.Named: + partitionKey = new ServicePartitionKey(ServiceReference.PartitionName); + break; + default: + throw new ArgumentOutOfRangeException(); + } + var client = ServiceProxyFactoryLazy.Value.CreateServiceProxy(ServiceReference.ServiceUri, partitionKey); + return client.ReceiveMessageAsync(message); } return Task.FromResult(true); } } - - internal static class MessageWrapperExtensions - { - private static readonly Lazy ServiceProxyFactoryLazy = new Lazy(() => new ServiceProxyFactory()); - - /// - /// Attempts to publish the message to a listener. - /// - /// - /// - /// - public static Task PublishAsync(this ServiceReferenceWrapper wrapper, MessageWrapper message) - { - ServicePartitionKey partitionKey; - switch (wrapper.ServiceReference.PartitionKind) - { - case ServicePartitionKind.Singleton: - partitionKey = ServicePartitionKey.Singleton; - break; - case ServicePartitionKind.Int64Range: - partitionKey = new ServicePartitionKey(wrapper.ServiceReference.PartitionKey); - break; - case ServicePartitionKind.Named: - partitionKey = new ServicePartitionKey(wrapper.ServiceReference.PartitionName); - break; - default: - throw new ArgumentOutOfRangeException(); - } - var client = ServiceProxyFactoryLazy.Value.CreateServiceProxy(wrapper.ServiceReference.ServiceUri, partitionKey); - return client.ReceiveMessageAsync(message); - } - - /// - /// Attempts to publish the message to a listener. - /// - /// - /// - /// - public static Task PublishAsync(this ActorReferenceWrapper wrapper, MessageWrapper message) - { - ISubscriberActor actor = (ISubscriberActor)wrapper.ActorReference.Bind(typeof(ISubscriberActor)); - return actor.ReceiveMessageAsync(message); - } - } } \ No newline at end of file diff --git a/ServiceFabric.PubSubActors.Interfaces/ISubscriberActor.cs b/ServiceFabric.PubSubActors/Subscriber/ISubscriberActor.cs similarity index 80% rename from ServiceFabric.PubSubActors.Interfaces/ISubscriberActor.cs rename to ServiceFabric.PubSubActors/Subscriber/ISubscriberActor.cs index bdc5001..cfa9b25 100644 --- a/ServiceFabric.PubSubActors.Interfaces/ISubscriberActor.cs +++ b/ServiceFabric.PubSubActors/Subscriber/ISubscriberActor.cs @@ -1,11 +1,12 @@ using System.Threading.Tasks; using Microsoft.ServiceFabric.Actors; using Microsoft.ServiceFabric.Actors.Runtime; +using ServiceFabric.PubSubActors.State; -namespace ServiceFabric.PubSubActors.Interfaces +namespace ServiceFabric.PubSubActors.Subscriber { /// - /// Defines a common interface for all Subscriber Actors. + /// Defines a common interface for all Subscriber Actors. /// Don't forget to mark implementing classes with /// the attribute like: [ActorService(Name = nameof(ISubscribingActor))] where ISubscribingActor is defined in your own project. /// diff --git a/ServiceFabric.PubSubActors/SubscriberServices/ISubscriberService.cs b/ServiceFabric.PubSubActors/Subscriber/ISubscriberService.cs similarity index 71% rename from ServiceFabric.PubSubActors/SubscriberServices/ISubscriberService.cs rename to ServiceFabric.PubSubActors/Subscriber/ISubscriberService.cs index e228e7c..6423911 100644 --- a/ServiceFabric.PubSubActors/SubscriberServices/ISubscriberService.cs +++ b/ServiceFabric.PubSubActors/Subscriber/ISubscriberService.cs @@ -1,11 +1,11 @@ using System.Threading.Tasks; using Microsoft.ServiceFabric.Services.Remoting; -using ServiceFabric.PubSubActors.Interfaces; +using ServiceFabric.PubSubActors.State; -namespace ServiceFabric.PubSubActors.SubscriberServices +namespace ServiceFabric.PubSubActors.Subscriber { /// - /// Defines a common interface for all Subscriber Services. + /// Defines a common interface for all Subscriber Services. /// public interface ISubscriberService : IService { diff --git a/ServiceFabric.PubSubActors/SubscriberServices/LongRunningTaskSubscriber.cs b/ServiceFabric.PubSubActors/Subscriber/LongRunningTaskSubscriber.cs similarity index 92% rename from ServiceFabric.PubSubActors/SubscriberServices/LongRunningTaskSubscriber.cs rename to ServiceFabric.PubSubActors/Subscriber/LongRunningTaskSubscriber.cs index ade8839..36908d0 100644 --- a/ServiceFabric.PubSubActors/SubscriberServices/LongRunningTaskSubscriber.cs +++ b/ServiceFabric.PubSubActors/Subscriber/LongRunningTaskSubscriber.cs @@ -8,12 +8,13 @@ using Microsoft.ServiceFabric.Data; using Microsoft.ServiceFabric.Data.Collections; using Microsoft.ServiceFabric.Services.Communication.Runtime; -using Microsoft.ServiceFabric.Services.Remoting.Runtime; +using Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime; using Microsoft.ServiceFabric.Services.Runtime; using Newtonsoft.Json; -using ServiceFabric.PubSubActors.Interfaces; +using ServiceFabric.PubSubActors.Helpers; +using ServiceFabric.PubSubActors.State; -namespace ServiceFabric.PubSubActors.SubscriberServices +namespace ServiceFabric.PubSubActors.Subscriber { /// /// Base implementation of a that runs long running tasks without delaying . @@ -61,7 +62,7 @@ protected LongRunningTaskSubscriberService(StatefulServiceContext serviceContext /// protected override IEnumerable CreateServiceReplicaListeners() { - return this.CreateServiceRemotingReplicaListeners(); //remoting listener + yield return new ServiceReplicaListener(context => new FabricTransportServiceRemotingListener(context, this)); //remoting listener } /// @@ -80,7 +81,7 @@ protected override async Task RunAsync(CancellationToken cancellationToken) //peek task description var taskDescriptionMessage = await TimeoutRetryHelper - .ExecuteInTransaction(StateManager, + .ExecuteInTransaction(StateManager, (tran, token, state) => queue.TryPeekAsync(tran, TimeSpan.FromSeconds(4), token), cancellationToken: cancellationToken) .ConfigureAwait(false); if (!taskDescriptionMessage.HasValue) @@ -89,7 +90,7 @@ protected override async Task RunAsync(CancellationToken cancellationToken) } //deserialize task description, create task implementation - var description = this.Deserialize(taskDescriptionMessage.Value); + var description = taskDescriptionMessage.Value.CreateMessage(); var implementation = TaskDescription.ToTaskImplementation(_typeLocator, description); if (implementation == null) { @@ -104,7 +105,7 @@ protected override async Task RunAsync(CancellationToken cancellationToken) await implementation.ExecuteAsync().ConfigureAwait(false); var taskDescriptionMessageDequeued = await TimeoutRetryHelper - .ExecuteInTransaction(StateManager, + .ExecuteInTransaction(StateManager, (tran, token, state) => queue.TryDequeueAsync(tran, TimeSpan.FromSeconds(4), token), cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -147,9 +148,9 @@ protected override async Task RunAsync(CancellationToken cancellationToken) public async Task ReceiveMessageAsync(MessageWrapper message) { //assume that message contains 'TaskDescription' - var description = this.Deserialize(message); + var description = message.CreateMessage(); if (description == null) return; //wrong message - + var queue = await TimeoutRetryHelper.Execute((token, state) => StateManager.GetOrAddAsync>(_queueName)); await TimeoutRetryHelper .ExecuteInTransaction(StateManager, (tran, token, state) => queue.EnqueueAsync(tran, message)) @@ -201,15 +202,15 @@ public interface ITypeLocator } /// - /// Helper class that maps type name to type for . + /// Helper class that maps type name to type for . /// public class TypeLocator : ITypeLocator { private readonly Dictionary _registeredTaskTypes = new Dictionary(); /// - /// Creates a new instance that scans the provided for concrete - /// implementations of type . + /// Creates a new instance that scans the provided for concrete + /// implementations of type . /// /// public TypeLocator(Assembly taskAssembly) @@ -243,7 +244,7 @@ public ITaskImplementation LocateAndCreate(TaskDescription taskDescription) public interface ITaskImplementation { /// - /// Executes the task. + /// Executes the task. /// /// Task ExecuteAsync(); diff --git a/ServiceFabric.PubSubActors/SubscriberServices/StatefulSubscriberServiceBootstrapper.cs b/ServiceFabric.PubSubActors/Subscriber/StatefulSubscriberServiceBootstrapper.cs similarity index 82% rename from ServiceFabric.PubSubActors/SubscriberServices/StatefulSubscriberServiceBootstrapper.cs rename to ServiceFabric.PubSubActors/Subscriber/StatefulSubscriberServiceBootstrapper.cs index fc8a4ce..a4a0639 100644 --- a/ServiceFabric.PubSubActors/SubscriberServices/StatefulSubscriberServiceBootstrapper.cs +++ b/ServiceFabric.PubSubActors/Subscriber/StatefulSubscriberServiceBootstrapper.cs @@ -1,11 +1,11 @@ -using Microsoft.ServiceFabric.Services.Runtime; -using ServiceFabric.PubSubActors.Helpers; -using System; +using System; using System.Fabric; using System.Fabric.Description; using System.Threading.Tasks; +using Microsoft.ServiceFabric.Services.Runtime; +using ServiceFabric.PubSubActors.Helpers; -namespace ServiceFabric.PubSubActors.SubscriberServices +namespace ServiceFabric.PubSubActors.Subscriber { /// /// Factory for Stateful subscriber services, automatically registers subscriptions for messages. @@ -24,7 +24,7 @@ public sealed class StatefulSubscriberServiceBootstrapper private readonly StatefulServiceContext _context; private readonly Func _serviceFactory; private readonly Action _loggingCallback; - private readonly ISubscriberServiceHelper _subscriberServiceHelper; + private readonly IBrokerClient _brokerClient; private readonly FabricClient _fabricClient; private long _filterId; private TService _service; @@ -39,19 +39,19 @@ public sealed class StatefulSubscriberServiceBootstrapper /// /// Service context. /// Builds an instance of - /// Helps with subscriptions. + /// Helps with subscriptions. /// Indicates whether the created service subscription should be removed after the service is deleted. /// Optional logging callback. public StatefulSubscriberServiceBootstrapper(StatefulServiceContext context, Func serviceFactory, - ISubscriberServiceHelper subscriberServiceHelper = null, + IBrokerClient brokerClient = null, bool autoUnsubscribe = false, Action loggingCallback = null) { _context = context ?? throw new ArgumentNullException(nameof(context)); _serviceFactory = serviceFactory ?? throw new ArgumentNullException(nameof(serviceFactory)); _loggingCallback = loggingCallback; - _subscriberServiceHelper = subscriberServiceHelper ?? new SubscriberServiceHelper(); + _brokerClient = brokerClient ?? new BrokerClient(); _fabricClient = new FabricClient(FabricClientRole.User); _fabricClient.ServiceManager.ServiceNotificationFilterMatched += ServiceNotificationFilterMatched; AutoUnsubscribe = autoUnsubscribe; @@ -121,28 +121,27 @@ private async Task RegisterSubscriptions() _loggingCallback?.Invoke($"Registering subscriptions for service '{_context.ServiceName}'."); try { - await _subscriberServiceHelper.SubscribeAsync( - _subscriberServiceHelper.CreateServiceReference(_service), - _subscriberServiceHelper.DiscoverMessageHandlers(_service).Keys) - .ConfigureAwait(false); + foreach (var subscription in _service.DiscoverMessageHandlers()) + { + await _brokerClient.SubscribeAsync(_service, subscription.Key, subscription.Value).ConfigureAwait(false); + } } catch (Exception ex) { - _loggingCallback?.Invoke( - $"Failed to register subscriptions for service '{_context.ServiceName}'. Error: {ex}"); + _loggingCallback?.Invoke($"Failed to register subscriptions for service '{_context.ServiceName}'. Error: {ex}"); } } private async Task UnregisterSubscriptions() { _loggingCallback?.Invoke($"Unregistering subscriptions for deleted service '{_context.ServiceName}'."); - + try { - await _subscriberServiceHelper.SubscribeAsync( - _subscriberServiceHelper.CreateServiceReference(_service), - _subscriberServiceHelper.DiscoverMessageHandlers(_service).Keys) - .ConfigureAwait(false); + foreach (var subscription in _service.DiscoverMessageHandlers()) + { + await _brokerClient.UnsubscribeAsync(_service, subscription.Key).ConfigureAwait(false); + } } catch (Exception ex) { diff --git a/ServiceFabric.PubSubActors/SubscriberServices/StatelessSubscriberServiceBootstrapper.cs b/ServiceFabric.PubSubActors/Subscriber/StatelessSubscriberServiceBootstrapper.cs similarity index 84% rename from ServiceFabric.PubSubActors/SubscriberServices/StatelessSubscriberServiceBootstrapper.cs rename to ServiceFabric.PubSubActors/Subscriber/StatelessSubscriberServiceBootstrapper.cs index 7dde8e0..cbf1323 100644 --- a/ServiceFabric.PubSubActors/SubscriberServices/StatelessSubscriberServiceBootstrapper.cs +++ b/ServiceFabric.PubSubActors/Subscriber/StatelessSubscriberServiceBootstrapper.cs @@ -5,7 +5,7 @@ using Microsoft.ServiceFabric.Services.Runtime; using ServiceFabric.PubSubActors.Helpers; -namespace ServiceFabric.PubSubActors.SubscriberServices +namespace ServiceFabric.PubSubActors.Subscriber { /// /// Factory for Stateful subscriber services, automatically registers subscriptions for messages. @@ -23,7 +23,7 @@ public sealed class StatelessSubscriberServiceBootstrapper { private readonly StatelessServiceContext _context; private readonly Func _serviceFactory; - private readonly ISubscriberServiceHelper _subscriberServiceHelper; + private readonly IBrokerClient _brokerClient; private readonly Action _loggingCallback; private readonly FabricClient _fabricClient; private long _filterId; @@ -44,13 +44,13 @@ public sealed class StatelessSubscriberServiceBootstrapper /// Optional logging callback. public StatelessSubscriberServiceBootstrapper(StatelessServiceContext context, Func serviceFactory, - ISubscriberServiceHelper subscriberServiceHelper = null, + IBrokerClient subscriberServiceHelper = null, bool autoUnsubscribe = false, Action loggingCallback = null) { _context = context ?? throw new ArgumentNullException(nameof(context)); _serviceFactory = serviceFactory ?? throw new ArgumentNullException(nameof(serviceFactory)); - _subscriberServiceHelper = subscriberServiceHelper ?? new SubscriberServiceHelper(); + _brokerClient = subscriberServiceHelper ?? new BrokerClient(); _fabricClient = new FabricClient(FabricClientRole.User); _fabricClient.ServiceManager.ServiceNotificationFilterMatched += ServiceNotificationFilterMatched; _loggingCallback = loggingCallback; @@ -121,15 +121,14 @@ private async Task RegisterSubscriptions() _loggingCallback?.Invoke($"Registering subscriptions for service '{_context.ServiceName}'."); try { - await _subscriberServiceHelper.SubscribeAsync( - _subscriberServiceHelper.CreateServiceReference(_service), - _subscriberServiceHelper.DiscoverMessageHandlers(_service).Keys) - .ConfigureAwait(false); + foreach (var subscription in _service.DiscoverMessageHandlers()) + { + await _brokerClient.SubscribeAsync(_service, subscription.Key, subscription.Value).ConfigureAwait(false); + } } catch (Exception ex) { - _loggingCallback?.Invoke( - $"Failed to register subscriptions for service '{_context.ServiceName}'. Error: {ex}"); + _loggingCallback?.Invoke($"Failed to register subscriptions for service '{_context.ServiceName}'. Error: {ex}"); } } @@ -139,10 +138,10 @@ private async Task UnregisterSubscriptions() try { - await _subscriberServiceHelper.SubscribeAsync( - _subscriberServiceHelper.CreateServiceReference(_service), - _subscriberServiceHelper.DiscoverMessageHandlers(_service).Keys) - .ConfigureAwait(false); + foreach (var subscription in _service.DiscoverMessageHandlers()) + { + await _brokerClient.UnsubscribeAsync(_service, subscription.Key).ConfigureAwait(false); + } } catch (Exception ex) { diff --git a/ServiceFabric.PubSubActors/Subscriber/SubscriberExtensions.cs b/ServiceFabric.PubSubActors/Subscriber/SubscriberExtensions.cs new file mode 100644 index 0000000..8ca6e38 --- /dev/null +++ b/ServiceFabric.PubSubActors/Subscriber/SubscriberExtensions.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace ServiceFabric.PubSubActors.Subscriber +{ + /// + /// Marks a service method as being capable of receiving messages. + /// Follows convention that method has signature 'Task MethodName(MessageType message)' + /// Polymorphism is supported. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + public class SubscribeAttribute : Attribute + { + } + + public static class SubscriberExtensions + { + public static Dictionary> DiscoverMessageHandlers(this ISubscriberService service) + { + return DiscoverHandlers(service); + } + + public static Dictionary> DiscoverMessageHandlers(this ISubscriberActor service) + { + return DiscoverHandlers(service); + } + + private static Dictionary> DiscoverHandlers(object service) + { + var handlers = new Dictionary>(); + var taskType = typeof(Task); + var methods = service.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + foreach (var method in methods) + { + var subscribeAttribute = method.GetCustomAttributes(typeof(SubscribeAttribute), false) + .Cast() + .SingleOrDefault(); + + if (subscribeAttribute == null) continue; + + var parameters = method.GetParameters(); + if (parameters.Length != 1 || !taskType.IsAssignableFrom(method.ReturnType)) continue; + + handlers[parameters[0].ParameterType] = m => (Task) method.Invoke(service, new[] {m}); + } + + return handlers; + } + } +} \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/SubscriberServices/SubscriberStatefulServiceBase.cs b/ServiceFabric.PubSubActors/Subscriber/SubscriberStatefulServiceBase.cs similarity index 53% rename from ServiceFabric.PubSubActors/SubscriberServices/SubscriberStatefulServiceBase.cs rename to ServiceFabric.PubSubActors/Subscriber/SubscriberStatefulServiceBase.cs index ba533b7..ecb436d 100644 --- a/ServiceFabric.PubSubActors/SubscriberServices/SubscriberStatefulServiceBase.cs +++ b/ServiceFabric.PubSubActors/Subscriber/SubscriberStatefulServiceBase.cs @@ -1,40 +1,41 @@ using System; using System.Collections.Generic; using System.Fabric; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.ServiceFabric.Data; using Microsoft.ServiceFabric.Services.Communication.Runtime; -using Microsoft.ServiceFabric.Services.Remoting.Runtime; +using Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime; using Microsoft.ServiceFabric.Services.Runtime; using ServiceFabric.PubSubActors.Helpers; -using ServiceFabric.PubSubActors.Interfaces; +using ServiceFabric.PubSubActors.State; -namespace ServiceFabric.PubSubActors.SubscriberServices +namespace ServiceFabric.PubSubActors.Subscriber { public class SubscriberStatefulServiceBase : StatefulService, ISubscriberService { - private readonly ISubscriberServiceHelper _subscriberServiceHelper; + private readonly IBrokerClient _brokerClient; /// - /// The message types that this service subscribes to and their respective handler methods. + /// When Set, this callback will be used to log messages to. /// - protected Dictionary> Handlers { get; set; } = new Dictionary>(); + protected Action Logger { get; set; } /// /// Set the Listener name so the remote Broker can find this service when there are multiple listeners available. /// - protected string ListenerName { get; set; } + protected string ListenerName { get; set; } = "SubscriberStatefulServiceRemotingListener"; /// /// Creates a new instance using the provided context. /// /// - /// - protected SubscriberStatefulServiceBase(StatefulServiceContext serviceContext, ISubscriberServiceHelper subscriberServiceHelper = null) + /// + protected SubscriberStatefulServiceBase(StatefulServiceContext serviceContext, IBrokerClient brokerClient = null) : base(serviceContext) { - _subscriberServiceHelper = subscriberServiceHelper ?? new SubscriberServiceHelper(new BrokerServiceLocator()); + _brokerClient = brokerClient ?? new BrokerClient(); } /// @@ -42,18 +43,17 @@ protected SubscriberStatefulServiceBase(StatefulServiceContext serviceContext, I /// /// /// - /// + /// protected SubscriberStatefulServiceBase(StatefulServiceContext serviceContext, - IReliableStateManagerReplica2 reliableStateManagerReplica, ISubscriberServiceHelper subscriberServiceHelper = null) + IReliableStateManagerReplica2 reliableStateManagerReplica, IBrokerClient brokerClient = null) : base(serviceContext, reliableStateManagerReplica) { - _subscriberServiceHelper = subscriberServiceHelper ?? new SubscriberServiceHelper(new BrokerServiceLocator()); + _brokerClient = brokerClient ?? new BrokerClient(); } /// protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken) { - Handlers = _subscriberServiceHelper.DiscoverMessageHandlers(this); return Subscribe(); } @@ -64,7 +64,7 @@ protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken /// public virtual Task ReceiveMessageAsync(MessageWrapper messageWrapper) { - return _subscriberServiceHelper.ProccessMessageAsync(messageWrapper, Handlers); + return _brokerClient.ProcessMessageAsync(messageWrapper); } /// @@ -72,16 +72,36 @@ public virtual Task ReceiveMessageAsync(MessageWrapper messageWrapper) /// This method can be overriden to subscribe manually based on custom logic. /// /// - protected virtual Task Subscribe() + protected virtual async Task Subscribe() { - var serviceReference = _subscriberServiceHelper.CreateServiceReference(this, ListenerName); - return _subscriberServiceHelper.SubscribeAsync(serviceReference, Handlers.Keys); + foreach (var handler in this.DiscoverMessageHandlers()) + { + try + { + await _brokerClient.SubscribeAsync(this, handler.Key, handler.Value, ListenerName); + LogMessage($"Registered Service:'{Context.ServiceName}' as Subscriber of {handler.Key}."); + } + catch (Exception ex) + { + LogMessage($"Failed to register Service:'{Context.ServiceName}' as Subscriber of {handler.Key}. Error:'{ex.Message}'."); + } + } } /// protected override IEnumerable CreateServiceReplicaListeners() { - return this.CreateServiceRemotingReplicaListeners(); + yield return new ServiceReplicaListener(context => new FabricTransportServiceRemotingListener(context, this), ListenerName); + } + + /// + /// Outputs the provided message to the if it's configured. + /// + /// + /// + protected void LogMessage(string message, [CallerMemberName] string caller = "unknown") + { + Logger?.Invoke($"{caller} - {message}"); } } } \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/SubscriberServices/SubscriberStatelessServiceBase.cs b/ServiceFabric.PubSubActors/Subscriber/SubscriberStatelessServiceBase.cs similarity index 51% rename from ServiceFabric.PubSubActors/SubscriberServices/SubscriberStatelessServiceBase.cs rename to ServiceFabric.PubSubActors/Subscriber/SubscriberStatelessServiceBase.cs index 04d11c8..9289410 100644 --- a/ServiceFabric.PubSubActors/SubscriberServices/SubscriberStatelessServiceBase.cs +++ b/ServiceFabric.PubSubActors/Subscriber/SubscriberStatelessServiceBase.cs @@ -1,15 +1,16 @@ using System; -using Microsoft.ServiceFabric.Services.Communication.Runtime; -using Microsoft.ServiceFabric.Services.Remoting.Runtime; -using Microsoft.ServiceFabric.Services.Runtime; -using ServiceFabric.PubSubActors.Helpers; -using ServiceFabric.PubSubActors.Interfaces; using System.Collections.Generic; using System.Fabric; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.ServiceFabric.Services.Communication.Runtime; +using Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime; +using Microsoft.ServiceFabric.Services.Runtime; +using ServiceFabric.PubSubActors.Helpers; +using ServiceFabric.PubSubActors.State; -namespace ServiceFabric.PubSubActors.SubscriberServices +namespace ServiceFabric.PubSubActors.Subscriber { /// /// Base class for a that serves as a subscriber of messages from the broker. @@ -17,31 +18,27 @@ namespace ServiceFabric.PubSubActors.SubscriberServices /// public abstract class SubscriberStatelessServiceBase : StatelessService, ISubscriberService { - private readonly ISubscriberServiceHelper _subscriberServiceHelper; + private readonly IBrokerClient _brokerClient; /// - /// The message types that this service subscribes to and their respective handler methods. + /// When Set, this callback will be used to log messages to. /// - protected Dictionary> Handlers { get; set; } = new Dictionary>(); + protected Action Logger { get; set; } /// /// Set the Listener name so the remote Broker can find this service when there are multiple listeners available. /// - protected string ListenerName { get; set; } + protected string ListenerName { get; set; } = "SubscriberStatelessServiceRemotingListener"; - protected SubscriberStatelessServiceBase(StatelessServiceContext serviceContext, ISubscriberServiceHelper subscriberServiceHelper = null) + protected SubscriberStatelessServiceBase(StatelessServiceContext serviceContext, IBrokerClient brokerClient = null) : base(serviceContext) { - _subscriberServiceHelper = subscriberServiceHelper ?? new SubscriberServiceHelper(new BrokerServiceLocator()); + _brokerClient = brokerClient ?? new BrokerClient(); } - /// - /// Subscribes to all message types that have a handler registered using the . - /// - /// + /// protected override Task OnOpenAsync(CancellationToken cancellationToken) { - Handlers = _subscriberServiceHelper.DiscoverMessageHandlers(this); return Subscribe(); } @@ -52,7 +49,7 @@ protected override Task OnOpenAsync(CancellationToken cancellationToken) /// public virtual Task ReceiveMessageAsync(MessageWrapper messageWrapper) { - return _subscriberServiceHelper.ProccessMessageAsync(messageWrapper, Handlers); + return _brokerClient.ProcessMessageAsync(messageWrapper); } /// @@ -60,16 +57,36 @@ public virtual Task ReceiveMessageAsync(MessageWrapper messageWrapper) /// This method can be overriden to subscribe manually based on custom logic. /// /// - protected virtual Task Subscribe() + protected virtual async Task Subscribe() { - var serviceReference = _subscriberServiceHelper.CreateServiceReference(this, ListenerName); - return _subscriberServiceHelper.SubscribeAsync(serviceReference, Handlers.Keys); + foreach (var handler in this.DiscoverMessageHandlers()) + { + try + { + await _brokerClient.SubscribeAsync(this, handler.Key, handler.Value, ListenerName); + LogMessage($"Registered Service:'{Context.ServiceName}' as Subscriber of {handler.Key}."); + } + catch (Exception ex) + { + LogMessage($"Failed to register Service:'{Context.ServiceName}' as Subscriber of {handler.Key}. Error:'{ex.Message}'."); + } + } } /// protected override IEnumerable CreateServiceInstanceListeners() { - return this.CreateServiceRemotingInstanceListeners(); + yield return new ServiceInstanceListener(context => new FabricTransportServiceRemotingListener(context, this), ListenerName); + } + + /// + /// Outputs the provided message to the if it's configured. + /// + /// + /// + protected void LogMessage(string message, [CallerMemberName] string caller = "unknown") + { + Logger?.Invoke($"{caller} - {message}"); } } } \ No newline at end of file diff --git a/ServiceFabric.PubSubActors/SubscriberActors/SubscriberActorExtensions.cs b/ServiceFabric.PubSubActors/SubscriberActors/SubscriberActorExtensions.cs deleted file mode 100644 index 0e1779e..0000000 --- a/ServiceFabric.PubSubActors/SubscriberActors/SubscriberActorExtensions.cs +++ /dev/null @@ -1,157 +0,0 @@ -using Microsoft.ServiceFabric.Actors; -using Microsoft.ServiceFabric.Actors.Client; -using Microsoft.ServiceFabric.Actors.Runtime; -using ServiceFabric.PubSubActors.Interfaces; -using System; -using System.Threading.Tasks; - -namespace ServiceFabric.PubSubActors.SubscriberActors -{ - /// - /// Common operations for Actors to become Subscribers - /// - public static class SubscriberActorExtensions - { - /// - /// Registers this Actor as a subscriber for messages of type using a approach. - /// The relay actor will register itself as subscriber to the broker actor, creating a fan out pattern for scalability. - /// - /// The actor registering itself as a subscriber for messages of type - /// The type of message to register for (each message type has its own instance) - /// The ID of the relay broker to register with. Remember this ID in the caller, if you ever need to unregister. - /// (optional) The ID of the source to use as the source for the - /// Remember this ID in the caller, if you ever need to unregister. - /// If not specified, the default for the message type will be used. - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static async Task RegisterMessageTypeWithRelayBrokerAsync(this ActorBase actor, Type messageType, ActorId relayBrokerActorId, ActorId sourceBrokerActorId) - { - if (actor == null) throw new ArgumentNullException(nameof(actor)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (relayBrokerActorId == null) throw new ArgumentNullException(nameof(relayBrokerActorId)); - - if (sourceBrokerActorId == null) - { - sourceBrokerActorId = new ActorId(messageType.FullName); - } - IRelayBrokerActor relayBrokerActor = ActorProxy.Create(relayBrokerActorId, actor.ApplicationName, nameof(IRelayBrokerActor)); - IBrokerActor brokerActor = ActorProxy.Create(sourceBrokerActorId, actor.ApplicationName, nameof(IBrokerActor)); - - //register relay as subscriber for broker - await brokerActor.RegisterSubscriberAsync(ActorReference.Get(relayBrokerActor)); - //register caller as subscriber for relay broker - await relayBrokerActor.RegisterSubscriberAsync(ActorReference.Get(actor)); - } - - /// - /// Unregisters this Actor as a subscriber for messages of type using a approach. - /// - /// The actor registering itself as a subscriber for messages of type - /// The type of message to unregister for (each message type has its own instance) - /// The ID of the relay broker to unregister with. - /// (optional) The ID of the source that was used as the source for the - /// If not specified, the default for the message type will be used. - /// Publish any remaining messages. - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static async Task UnregisterMessageTypeWithRelayBrokerAsync(this ActorBase actor, Type messageType, ActorId relayBrokerActorId, ActorId sourceBrokerActorId, bool flushQueue) - { - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (actor == null) throw new ArgumentNullException(nameof(actor)); - if (relayBrokerActorId == null) throw new ArgumentNullException(nameof(relayBrokerActorId)); - - if (sourceBrokerActorId == null) - { - sourceBrokerActorId = new ActorId(messageType.FullName); - } - IRelayBrokerActor relayBrokerActor = ActorProxy.Create(relayBrokerActorId, actor.ApplicationName, nameof(IRelayBrokerActor)); - IBrokerActor brokerActor = ActorProxy.Create(sourceBrokerActorId, actor.ApplicationName, nameof(IBrokerActor)); - - //unregister relay as subscriber for broker - await brokerActor.UnregisterSubscriberAsync(ActorReference.Get(relayBrokerActor), flushQueue); - //unregister caller as subscriber for relay broker - await relayBrokerActor.UnregisterSubscriberAsync(ActorReference.Get(actor), flushQueue); - } - - /// - /// Registers this Actor as a subscriber for messages of type . - /// - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static async Task RegisterMessageTypeAsync(this ActorBase actor, Type messageType) - { - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (actor == null) throw new ArgumentNullException(nameof(actor)); - ActorId actorId = new ActorId(messageType.FullName); - IBrokerActor brokerActor = ActorProxy.Create(actorId, actor.ApplicationName, nameof(IBrokerActor)); - await brokerActor.RegisterSubscriberAsync(ActorReference.Get(actor)); - } - - /// - /// Unregisters this Actor as a subscriber for messages of type . - /// - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static async Task UnregisterMessageTypeAsync(this ActorBase actor, Type messageType, bool flushQueue) - { - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (actor == null) throw new ArgumentNullException(nameof(actor)); - ActorId actorId = new ActorId(messageType.FullName); - IBrokerActor brokerActor = ActorProxy.Create(actorId, actor.ApplicationName, nameof(IBrokerActor)); - await brokerActor.UnregisterSubscriberAsync(ActorReference.Get(actor), flushQueue); - } - - /// - /// Registers this Actor as a subscriber for messages of type with the . - /// - /// - [Obsolete("Use ServiceFabric.PubSubActors.Helpers.SubscriberActorHelper for testability")] - public static async Task RegisterMessageTypeWithBrokerServiceAsync(this ActorBase actor, Type messageType, Uri brokerServiceName = null, string routingKey = null) - { - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (actor == null) throw new ArgumentNullException(nameof(actor)); - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServices.PublisherServiceExtensions.DiscoverBrokerServiceNameAsync(new Uri(actor.ApplicationName)); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = await PublisherActors.PublisherActorExtensions.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - await brokerService.RegisterSubscriberAsync(ActorReference.Get(actor), messageType.FullName, routingKey); - } - - /// - /// Unregisters this Actor as a subscriber for messages of type with the . - /// - /// - [Obsolete("Use ServiceFabric.PubSubActors.Helpers.SubscriberActorHelper for testability")] - public static async Task UnregisterMessageTypeWithBrokerServiceAsync(this ActorBase actor, Type messageType, bool flushQueue, Uri brokerServiceName = null) - { - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (actor == null) throw new ArgumentNullException(nameof(actor)); - - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServices.PublisherServiceExtensions.DiscoverBrokerServiceNameAsync(new Uri(actor.ApplicationName)); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = await PublisherActors.PublisherActorExtensions.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - await brokerService.UnregisterSubscriberAsync(ActorReference.Get(actor), messageType.FullName, flushQueue); - } - - /// - /// Deserializes the provided Payload into an intance of type - /// - /// - /// - public static TResult Deserialize(this ActorBase actor, MessageWrapper messageWrapper) - { - return messageWrapper.CreateMessage(); - } - } -} diff --git a/ServiceFabric.PubSubActors/SubscriberServices/SubscriberServiceExtensions.cs b/ServiceFabric.PubSubActors/SubscriberServices/SubscriberServiceExtensions.cs deleted file mode 100644 index bad64ca..0000000 --- a/ServiceFabric.PubSubActors/SubscriberServices/SubscriberServiceExtensions.cs +++ /dev/null @@ -1,396 +0,0 @@ -using Microsoft.ServiceFabric.Actors; -using Microsoft.ServiceFabric.Actors.Client; -using Microsoft.ServiceFabric.Services.Runtime; -using ServiceFabric.PubSubActors.Interfaces; -using System; -using System.Fabric; -using System.Reflection; -using System.Threading.Tasks; - -namespace ServiceFabric.PubSubActors.SubscriberServices -{ - public static class SubscriberServiceExtensions - { - /// - /// Deserializes the provided Payload into an instance of type - /// - /// - /// - public static TResult Deserialize(this StatefulServiceBase service, MessageWrapper messageWrapper) - { - if (messageWrapper == null) throw new ArgumentNullException(nameof(messageWrapper)); - if (string.IsNullOrWhiteSpace(messageWrapper.Payload)) throw new ArgumentNullException(nameof(messageWrapper.Payload)); - - var payload = messageWrapper.CreateMessage(); - return payload; - } - - /// - /// Deserializes the provided Payload into an instance of type - /// - /// - /// - public static TResult Deserialize(this StatelessService service, MessageWrapper messageWrapper) - { - if (messageWrapper == null) throw new ArgumentNullException(nameof(messageWrapper)); - if (string.IsNullOrWhiteSpace(messageWrapper.Payload)) throw new ArgumentNullException(nameof(messageWrapper.Payload)); - - var payload = messageWrapper.CreateMessage(); - return payload; - } - - /// - /// Registers a service as a subscriber for messages of type . - /// - /// The service registering itself as a subscriber for messages of type - /// The type of message to register for (each message type has its own instance) - /// (optional) The name of the listener that is used to communicate with the service - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static Task RegisterMessageTypeAsync(this StatefulServiceBase service, Type messageType, string listenerName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - return RegisterMessageTypeAsync(service.Context, service.GetServicePartition().PartitionInfo, messageType, listenerName); - } - - /// - /// Registers a service as a subscriber for messages of type . - /// - /// The service registering itself as a subscriber for messages of type - /// The type of message to register for (each message type has its own instance) - /// (optional) The name of the listener that is used to communicate with the service - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static Task RegisterMessageTypeAsync(this StatelessService service, Type messageType, string listenerName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - return RegisterMessageTypeAsync(service.Context, service.GetServicePartition().PartitionInfo, messageType, listenerName); - } - - /// - /// Unregisters a service as a subscriber for messages of type . - /// - /// The service unregistering itself as a subscriber for messages of type - /// The type of message to unregister for (each message type has its own instance) - /// Publish any remaining messages. - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static Task UnregisterMessageTypeAsync(this StatelessService service, Type messageType, bool flushQueue) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - return UnregisterMessageTypeAsync(service.Context, service.GetServicePartition().PartitionInfo, messageType, flushQueue); - } - - /// - /// Unregisters a service as a subscriber for messages of type . - /// - /// The service unregistering itself as a subscriber for messages of type - /// The type of message to unregister for (each message type has its own instance) - /// Publish any remaining messages. - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static Task UnregisterMessageTypeAsync(this StatefulServiceBase service, Type messageType, bool flushQueue) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - return UnregisterMessageTypeAsync(service.Context, service.GetServicePartition().PartitionInfo, messageType, flushQueue); - } - - /// - /// Registers a Service as a subscriber for messages of type using a approach. - /// The relay actor will register itself as subscriber to the broker actor, creating a fan out pattern for scalability. - /// - /// The service registering itself as a subscriber for messages of type - /// The type of message to register for (each message type has its own instance) - /// The ID of the relay broker to register with. Remember this ID in the caller, if you ever need to unregister. - /// (optional) The ID of the source to use as the source for the - /// Remember this ID in the caller, if you ever need to unregister. - /// If not specified, the default for the message type will be used. - /// (optional) The name of the listener that is used to communicate with the service - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static Task RegisterMessageTypeWithRelayBrokerAsync(this StatefulServiceBase service, Type messageType, ActorId relayBrokerActorId, ActorId sourceBrokerActorId, string listenerName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (relayBrokerActorId == null) throw new ArgumentNullException(nameof(relayBrokerActorId)); - - return RegisterMessageTypeWithRelayBrokerAsync(service.Context, service.GetServicePartition().PartitionInfo, messageType, relayBrokerActorId, sourceBrokerActorId, listenerName); - } - - /// - /// Registers a Service as a subscriber for messages of type using a approach. - /// The relay actor will register itself as subscriber to the broker actor, creating a fan out pattern for scalability. - /// - /// The service registering itself as a subscriber for messages of type - /// The type of message to register for (each message type has its own instance) - /// The ID of the relay broker to register with. Remember this ID in the caller, if you ever need to unregister. - /// (optional) The ID of the source to use as the source for the - /// Remember this ID in the caller, if you ever need to unregister. - /// If not specified, the default for the message type will be used. - /// (optional) The name of the listener that is used to communicate with the service - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static Task RegisterMessageTypeWithRelayBrokerAsync(this StatelessService service, Type messageType, ActorId relayBrokerActorId, ActorId sourceBrokerActorId, string listenerName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (relayBrokerActorId == null) throw new ArgumentNullException(nameof(relayBrokerActorId)); - - return RegisterMessageTypeWithRelayBrokerAsync(service.Context, service.GetServicePartition().PartitionInfo, messageType, relayBrokerActorId, sourceBrokerActorId, listenerName); - } - - /// - /// Unregisters a Service as a subscriber for messages of type using a approach. - /// - /// The service registering itself as a subscriber for messages of type - /// The type of message to unregister for (each message type has its own instance) - /// The ID of the relay broker to unregister with. - /// (optional) The ID of the source that was used as the source for the - /// If not specified, the default for the message type will be used. - /// Publish any remaining messages. - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static Task UnregisterMessageTypeWithRelayBrokerAsync(this StatefulServiceBase service, Type messageType, ActorId relayBrokerActorId, ActorId sourceBrokerActorId, bool flushQueue) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (relayBrokerActorId == null) throw new ArgumentNullException(nameof(relayBrokerActorId)); - - return UnregisterMessageTypeWithRelayBrokerAsync(service.Context, service.GetServicePartition().PartitionInfo, messageType, relayBrokerActorId, sourceBrokerActorId, flushQueue); - } - - /// - /// Unregisters a Service as a subscriber for messages of type using a approach. - /// - /// The service registering itself as a subscriber for messages of type - /// The type of message to unregister for (each message type has its own instance) - /// The ID of the relay broker to unregister with. - /// (optional) The ID of the source that was used as the source for the - /// If not specified, the default for the message type will be used. - /// Publish any remaining messages. - /// - [Obsolete("This method will be removed in the next major upgrade. Use the BrokerService instead.")] - public static Task UnregisterMessageTypeWithRelayBrokerAsync(this StatelessService service, Type messageType, ActorId relayBrokerActorId, ActorId sourceBrokerActorId, bool flushQueue) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (relayBrokerActorId == null) throw new ArgumentNullException(nameof(relayBrokerActorId)); - - return UnregisterMessageTypeWithRelayBrokerAsync(service.Context, service.GetServicePartition().PartitionInfo, messageType, relayBrokerActorId, sourceBrokerActorId, flushQueue); - } - - /// - /// Registers this Service as a subscriber for messages of type with the . - /// - /// - public static async Task RegisterMessageTypeWithBrokerServiceAsync(this StatelessService service, Type messageType, Uri brokerServiceName = null, string listenerName = null, string routingKey = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServices.PublisherServiceExtensions.DiscoverBrokerServiceNameAsync(new Uri(service.Context.CodePackageActivationContext.ApplicationName)); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = await PublisherActors.PublisherActorExtensions.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - var serviceReference = CreateServiceReference(service.Context, service.GetServicePartition().PartitionInfo, listenerName); - await brokerService.RegisterServiceSubscriberAsync(serviceReference, messageType.FullName, routingKey); - } - - /// - /// Unregisters this Service as a subscriber for messages of type with the . - /// - /// - public static async Task UnregisterMessageTypeWithBrokerServiceAsync(this StatelessService service, Type messageType, bool flushQueue, Uri brokerServiceName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServices.PublisherServiceExtensions.DiscoverBrokerServiceNameAsync(new Uri(service.Context.CodePackageActivationContext.ApplicationName)); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = await PublisherActors.PublisherActorExtensions.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - var serviceReference = CreateServiceReference(service.Context, service.GetServicePartition().PartitionInfo); - await brokerService.UnregisterServiceSubscriberAsync(serviceReference, messageType.FullName, flushQueue); - } - - /// - /// Registers this Actor as a subscriber for messages of type with the . - /// - /// - public static async Task RegisterMessageTypeWithBrokerServiceAsync(this StatefulService service, Type messageType, Uri brokerServiceName = null, string listenerName = null, string routingKey = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServices.PublisherServiceExtensions.DiscoverBrokerServiceNameAsync(new Uri(service.Context.CodePackageActivationContext.ApplicationName)); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = await PublisherActors.PublisherActorExtensions.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - var serviceReference = CreateServiceReference(service.Context, service.GetServicePartition().PartitionInfo, listenerName); - await brokerService.RegisterServiceSubscriberAsync(serviceReference, messageType.FullName, routingKey); - } - - /// - /// Unregisters this Actor as a subscriber for messages of type with the . - /// - /// - public static async Task UnregisterMessageTypeWithBrokerServiceAsync(this StatefulService service, Type messageType, bool flushQueue, Uri brokerServiceName = null) - { - if (service == null) throw new ArgumentNullException(nameof(service)); - if (messageType == null) throw new ArgumentNullException(nameof(messageType)); - if (brokerServiceName == null) - { - brokerServiceName = await PublisherServices.PublisherServiceExtensions.DiscoverBrokerServiceNameAsync(new Uri(service.Context.CodePackageActivationContext.ApplicationName)); - if (brokerServiceName == null) - { - throw new InvalidOperationException("No brokerServiceName was provided or discovered in the current application."); - } - } - var brokerService = await PublisherActors.PublisherActorExtensions.GetBrokerServiceForMessageAsync(messageType.Name, brokerServiceName); - var serviceReference = CreateServiceReference(service.Context, service.GetServicePartition().PartitionInfo); - await brokerService.UnregisterServiceSubscriberAsync(serviceReference, messageType.FullName, flushQueue); - } - - /// - /// Gets the Partition info for the provided StatefulServiceBase instance. - /// - /// - /// - private static IStatefulServicePartition GetServicePartition(this StatefulServiceBase serviceBase) - { - if (serviceBase == null) throw new ArgumentNullException(nameof(serviceBase)); - return (IStatefulServicePartition)serviceBase - .GetType() - .GetProperty("Partition", BindingFlags.Instance | BindingFlags.NonPublic) - .GetValue(serviceBase); - } - - /// - /// Gets the Partition info for the provided StatelessService instance. - /// - /// - /// - private static IStatelessServicePartition GetServicePartition(this StatelessService serviceBase) - { - if (serviceBase == null) throw new ArgumentNullException(nameof(serviceBase)); - return (IStatelessServicePartition)serviceBase - .GetType() - .GetProperty("Partition", BindingFlags.Instance | BindingFlags.NonPublic) - .GetValue(serviceBase); - } - - /// - /// Creates a for the provided service context and partition info. - /// - /// - /// - /// (optional) The name of the listener that is used to communicate with the service - /// - public static ServiceReference CreateServiceReference(ServiceContext context, ServicePartitionInformation info, string listenerName = null) - { - var serviceReference = new ServiceReference - { - ApplicationName = context.CodePackageActivationContext.ApplicationName, - PartitionKind = info.Kind, - ServiceUri = context.ServiceName, - PartitionGuid = context.PartitionId, - ListenerName = listenerName - }; - - if (info is Int64RangePartitionInformation longInfo) - { - serviceReference.PartitionKey = longInfo.LowKey; - } - else if (info is NamedPartitionInformation stringInfo) - { - serviceReference.PartitionName = stringInfo.Name; - } - - return serviceReference; - } - - /// - /// Registers a service as a subscriber for messages of type . - /// - /// - private static async Task RegisterMessageTypeAsync(ServiceContext context, ServicePartitionInformation info, Type messageType, string listenerName) - { - var serviceReference = CreateServiceReference(context, info, listenerName); - ActorId actorId = new ActorId(messageType.FullName); - IBrokerActor brokerActor = ActorProxy.Create(actorId, serviceReference.ApplicationName, nameof(IBrokerActor)); - - await brokerActor.RegisterServiceSubscriberAsync(serviceReference); - } - - /// - /// Unregisters a service as a subscriber for messages of type . - /// - /// - private static async Task UnregisterMessageTypeAsync(ServiceContext context, ServicePartitionInformation info, Type messageType, bool flushQueue) - { - var serviceReference = CreateServiceReference(context, info); - ActorId actorId = new ActorId(messageType.FullName); - IBrokerActor brokerActor = ActorProxy.Create(actorId, serviceReference.ApplicationName, nameof(IBrokerActor)); - - await brokerActor.UnregisterServiceSubscriberAsync(serviceReference, flushQueue); - } - - /// - /// Registers a service as a subscriber for messages of type using a relay broker. - /// - /// - private static async Task RegisterMessageTypeWithRelayBrokerAsync(ServiceContext context, ServicePartitionInformation info, Type messageType, ActorId relayBrokerActorId, ActorId sourceBrokerActorId, string listenerName = null) - { - var serviceReference = CreateServiceReference(context, info, listenerName); - - if (sourceBrokerActorId == null) - { - sourceBrokerActorId = new ActorId(messageType.FullName); - } - IRelayBrokerActor relayBrokerActor = ActorProxy.Create(relayBrokerActorId, serviceReference.ApplicationName, nameof(IRelayBrokerActor)); - IBrokerActor brokerActor = ActorProxy.Create(sourceBrokerActorId, serviceReference.ApplicationName, nameof(IBrokerActor)); - - //register relay as subscriber for broker - await brokerActor.RegisterSubscriberAsync(ActorReference.Get(relayBrokerActor)); - //register caller as subscriber for relay broker - await relayBrokerActor.RegisterServiceSubscriberAsync(serviceReference); - } - - /// - /// Unregisters a service as a subscriber for messages of type using a relay broker. - /// - /// - private static async Task UnregisterMessageTypeWithRelayBrokerAsync(ServiceContext context, ServicePartitionInformation info, Type messageType, ActorId relayBrokerActorId, ActorId sourceBrokerActorId, bool flushQueue) - { - var serviceReference = CreateServiceReference(context, info); - - if (sourceBrokerActorId == null) - { - sourceBrokerActorId = new ActorId(messageType.FullName); - } - IRelayBrokerActor relayBrokerActor = ActorProxy.Create(relayBrokerActorId, serviceReference.ApplicationName, nameof(IRelayBrokerActor)); - IBrokerActor brokerActor = ActorProxy.Create(sourceBrokerActorId, serviceReference.ApplicationName, nameof(IBrokerActor)); - - //register relay as subscriber for broker - await brokerActor.UnregisterSubscriberAsync(ActorReference.Get(relayBrokerActor), flushQueue); - //register caller as subscriber for relay broker - await relayBrokerActor.UnregisterServiceSubscriberAsync(serviceReference, flushQueue); - } - } -}