Allow explicit actor type (#165)

* Refactored ActorRuntime to allow testing.

* Add test for inferred actor type.

* Add RegisterActor() overload.

* Consolidate RegisterActor() implementations.

* Refactor to make type still intrinsic to actor implementation.

* Update docs.

* Revert error codes change.

* Updates per PR feedback.

* Add warning to ActorRuntime constructor.
This commit is contained in:
Phillip Hoff 2019-11-25 09:30:18 -08:00 committed by Aman Bhardwaj
parent 7875ef3da9
commit 916d552279
8 changed files with 197 additions and 22 deletions

View File

@ -257,6 +257,18 @@ namespace MyActorService
}
```
#### Using an explicit actor type name
By default, the "type" of the actor as seen by clients is derived from the name of the actor implementation class. If desired, you can specify an explicit type name by attaching an `ActorAttribute` attribute to the actor implementation class.
```csharp
[Actor(TypeName = "MyCustomActorTypeName")]
internal class MyActor : Actor, IMyActor
{
// ...
}
```
### Register Actor to Dapr Runtime
Register `MyActor` actor type to actor runtime and set the localhost port (`https://localhost:3000`) which Dapr Runtime can call Actor through.

View File

@ -123,7 +123,7 @@ namespace Dapr.Actors.AspNetCore
writer.WritePropertyName("entities");
writer.WriteStartArray();
foreach (var actorType in ActorRuntime.RegisteredActorTypes)
foreach (var actorType in ActorRuntime.Instance.RegisteredActorTypes)
{
writer.WriteStringValue(actorType);
}

View File

@ -0,0 +1,24 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
namespace Dapr.Actors.Runtime
{
using System;
/// <summary>
/// Contains optional properties related to an actor implementation.
/// </summary>
/// <remarks>Intended to be attached to actor implementation types (i.e.those derived from <see cref="Actor" />).</remarks>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class ActorAttribute : Attribute
{
/// <summary>
/// Gets or sets the name of the actor type represented by the actor.
/// </summary>
/// <value>The <see cref="string"/> name of the actor type represented by the actor.</value>
/// <remarks>If set, this value will override the default actor type name derived from the actor implementation type.</remarks>
public string TypeName { get; set; }
}
}

View File

@ -24,16 +24,20 @@ namespace Dapr.Actors.Runtime
private const string TraceType = "ActorRuntime";
// Map of ActorType --> ActorManager.
private static Dictionary<string, ActorManager> actorManagers = new Dictionary<string, ActorManager>();
private readonly Dictionary<string, ActorManager> actorManagers = new Dictionary<string, ActorManager>();
private ActorRuntime()
/// <remarks>
/// WARNING: This type is expected to be accessed via the <see cref="Instance" /> singleton instance.
/// This constructor is exposed only for unit testing purposes.
/// </remarks>
internal ActorRuntime()
{
}
/// <summary>
/// Gets actor type names registered with the runtime.
/// </summary>
public static IEnumerable<string> RegisteredActorTypes => actorManagers.Keys;
public IEnumerable<string> RegisteredActorTypes => this.actorManagers.Keys;
internal static IDaprInteractor DaprInteractor => new DaprHttpInteractor();
@ -45,7 +49,6 @@ namespace Dapr.Actors.Runtime
public void RegisterActor<TActor>(Func<ActorTypeInformation, ActorService> actorServiceFactory = null)
where TActor : Actor
{
var actorTypeName = typeof(TActor).Name;
var actorTypeInfo = ActorTypeInformation.Get(typeof(TActor));
ActorService actorService;
@ -59,7 +62,7 @@ namespace Dapr.Actors.Runtime
}
// Create ActorManagers, override existing entry if registered again.
actorManagers[actorTypeName] = new ActorManager(actorService);
this.actorManagers[actorTypeInfo.ActorTypeName] = new ActorManager(actorService);
}
/// <summary>
@ -70,7 +73,7 @@ namespace Dapr.Actors.Runtime
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
internal static async Task ActivateAsync(string actorTypeName, string actorId)
{
await GetActorManager(actorTypeName).ActivateActor(new ActorId(actorId));
await Instance.GetActorManager(actorTypeName).ActivateActor(new ActorId(actorId));
}
/// <summary>
@ -81,7 +84,7 @@ namespace Dapr.Actors.Runtime
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
internal static async Task DeactivateAsync(string actorTypeName, string actorId)
{
await GetActorManager(actorTypeName).DeactivateActor(new ActorId(actorId));
await Instance.GetActorManager(actorTypeName).DeactivateActor(new ActorId(actorId));
}
/// <summary>
@ -96,7 +99,7 @@ namespace Dapr.Actors.Runtime
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
internal static Task<Tuple<string, byte[]>> DispatchWithRemotingAsync(string actorTypeName, string actorId, string actorMethodName, string daprActorheader, Stream data, CancellationToken cancellationToken = default)
{
return GetActorManager(actorTypeName).DispatchWithRemotingAsync(new ActorId(actorId), actorMethodName, daprActorheader, data, cancellationToken);
return Instance.GetActorManager(actorTypeName).DispatchWithRemotingAsync(new ActorId(actorId), actorMethodName, daprActorheader, data, cancellationToken);
}
/// <summary>
@ -111,7 +114,7 @@ namespace Dapr.Actors.Runtime
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
internal static Task DispatchWithoutRemotingAsync(string actorTypeName, string actorId, string actorMethodName, Stream requestBodyStream, Stream responseBodyStream, CancellationToken cancellationToken = default)
{
return GetActorManager(actorTypeName).DispatchWithoutRemotingAsync(new ActorId(actorId), actorMethodName, requestBodyStream, responseBodyStream, cancellationToken);
return Instance.GetActorManager(actorTypeName).DispatchWithoutRemotingAsync(new ActorId(actorId), actorMethodName, requestBodyStream, responseBodyStream, cancellationToken);
}
/// <summary>
@ -125,7 +128,7 @@ namespace Dapr.Actors.Runtime
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
internal static Task FireReminderAsync(string actorTypeName, string actorId, string reminderName, Stream requestBodyStream, CancellationToken cancellationToken = default)
{
return GetActorManager(actorTypeName).FireReminderAsync(new ActorId(actorId), reminderName, requestBodyStream, cancellationToken);
return Instance.GetActorManager(actorTypeName).FireReminderAsync(new ActorId(actorId), reminderName, requestBodyStream, cancellationToken);
}
/// <summary>
@ -138,12 +141,12 @@ namespace Dapr.Actors.Runtime
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
internal static Task FireTimerAsync(string actorTypeName, string actorId, string timerName, CancellationToken cancellationToken = default)
{
return GetActorManager(actorTypeName).FireTimerAsync(new ActorId(actorId), timerName, cancellationToken);
return Instance.GetActorManager(actorTypeName).FireTimerAsync(new ActorId(actorId), timerName, cancellationToken);
}
private static ActorManager GetActorManager(string actorTypeName)
private ActorManager GetActorManager(string actorTypeName)
{
if (!actorManagers.TryGetValue(actorTypeName, out var actorManager))
if (!this.actorManagers.TryGetValue(actorTypeName, out var actorManager))
{
var errorMsg = $"Actor type {actorTypeName} is not registerd with Actor runtime.";
ActorTrace.Instance.WriteError(TraceType, errorMsg);

View File

@ -15,13 +15,13 @@ namespace Dapr.Actors.Runtime
internal sealed class ActorStateManager : IActorStateManager
{
private readonly Actor actor;
private readonly string actorType;
private readonly string actorTypeName;
private readonly Dictionary<string, StateMetadata> stateChangeTracker;
internal ActorStateManager(Actor actor)
{
this.actor = actor;
this.actorType = actor.GetType().Name;
this.actorTypeName = actor.ActorService.ActorTypeInfo.ActorTypeName;
this.stateChangeTracker = new Dictionary<string, StateMetadata>();
}
@ -51,7 +51,7 @@ namespace Dapr.Actors.Runtime
return false;
}
if (await this.actor.ActorService.StateProvider.ContainsStateAsync(this.actorType, this.actor.Id.ToString(), stateName, cancellationToken))
if (await this.actor.ActorService.StateProvider.ContainsStateAsync(this.actorTypeName, this.actor.Id.ToString(), stateName, cancellationToken))
{
return false;
}
@ -112,7 +112,7 @@ namespace Dapr.Actors.Runtime
stateMetadata.ChangeKind = StateChangeKind.Update;
}
}
else if (await this.actor.ActorService.StateProvider.ContainsStateAsync(this.actorType, this.actor.Id.ToString(), stateName, cancellationToken))
else if (await this.actor.ActorService.StateProvider.ContainsStateAsync(this.actorTypeName, this.actor.Id.ToString(), stateName, cancellationToken))
{
this.stateChangeTracker.Add(stateName, StateMetadata.Create(value, StateChangeKind.Update));
}
@ -151,7 +151,7 @@ namespace Dapr.Actors.Runtime
return true;
}
if (await this.actor.ActorService.StateProvider.ContainsStateAsync(this.actorType, this.actor.Id.ToString(), stateName, cancellationToken))
if (await this.actor.ActorService.StateProvider.ContainsStateAsync(this.actorTypeName, this.actor.Id.ToString(), stateName, cancellationToken))
{
this.stateChangeTracker.Add(stateName, StateMetadata.CreateForRemove());
return true;
@ -172,7 +172,7 @@ namespace Dapr.Actors.Runtime
return stateMetadata.ChangeKind != StateChangeKind.Remove;
}
if (await this.actor.ActorService.StateProvider.ContainsStateAsync(this.actorType, this.actor.Id.ToString(), stateName, cancellationToken))
if (await this.actor.ActorService.StateProvider.ContainsStateAsync(this.actorTypeName, this.actor.Id.ToString(), stateName, cancellationToken))
{
return true;
}
@ -297,7 +297,7 @@ namespace Dapr.Actors.Runtime
if (stateChangeList.Count > 0)
{
await this.actor.ActorService.StateProvider.SaveStateAsync(this.actorType, this.actor.Id.ToString(), stateChangeList.AsReadOnly(), cancellationToken);
await this.actor.ActorService.StateProvider.SaveStateAsync(this.actorTypeName, this.actor.Id.ToString(), stateChangeList.AsReadOnly(), cancellationToken);
}
// Remove the states from tracker whcih were marked for removal.
@ -321,7 +321,7 @@ namespace Dapr.Actors.Runtime
private Task<ConditionalValue<T>> TryGetStateFromStateProviderAsync<T>(string stateName, CancellationToken cancellationToken)
{
return this.actor.ActorService.StateProvider.TryLoadStateAsync<T>(this.actorType, this.actor.Id.ToString(), stateName, cancellationToken);
return this.actor.ActorService.StateProvider.TryLoadStateAsync<T>(this.actorTypeName, this.actor.Id.ToString(), stateName, cancellationToken);
}
private sealed class StateMetadata

View File

@ -23,6 +23,13 @@ namespace Dapr.Actors.Runtime
{
}
/// <summary>
/// Gets the name of the actor type represented by the actor.
/// </summary>
/// <value>The <see cref="string"/> name of the actor type represented by the actor.</value>
/// <remarks>Defaults to the name of the class implementing the actor. Can be overridden using the <see cref="Dapr.Actors.Runtime.ActorAttribute.TypeName" /> property.</remarks>
public string ActorTypeName { get; private set; }
/// <summary>
/// Gets the type of the class implementing the actor.
/// </summary>
@ -112,8 +119,13 @@ namespace Dapr.Actors.Runtime
"actorType");
}
var actorAttribute = actorType.GetCustomAttribute<ActorAttribute>();
string actorTypeName = actorAttribute?.TypeName ?? actorType.Name;
return new ActorTypeInformation()
{
ActorTypeName = actorTypeName,
InterfaceTypes = actorInterfaces,
ImplementationType = actorType,
IsAbstract = actorType.GetTypeInfo().IsAbstract,

View File

@ -0,0 +1,66 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
namespace Dapr.Actors.Test
{
using System;
using Dapr.Actors;
using Dapr.Actors.Runtime;
using Xunit;
public sealed class ActorRuntimeTests
{
private const string RenamedActorTypeName = "MyRenamedActor";
private interface ITestActor : IActor
{
}
[Fact]
public void TestInferredActorType()
{
var actorType = typeof(TestActor);
var actorRuntime = new ActorRuntime();
Assert.Empty(actorRuntime.RegisteredActorTypes);
actorRuntime.RegisterActor<TestActor>();
Assert.Contains(actorType.Name, actorRuntime.RegisteredActorTypes, StringComparer.InvariantCulture);
}
[Fact]
public void TestExplicitActorType()
{
var actorType = typeof(RenamedActor);
var actorRuntime = new ActorRuntime();
Assert.NotEqual(RenamedActorTypeName, actorType.Name);
Assert.Empty(actorRuntime.RegisteredActorTypes);
actorRuntime.RegisterActor<RenamedActor>();
Assert.Contains(RenamedActorTypeName, actorRuntime.RegisteredActorTypes, StringComparer.InvariantCulture);
}
private sealed class TestActor : Actor, ITestActor
{
public TestActor(ActorService actorService, ActorId actorId)
: base(actorService, actorId)
{
}
}
[Actor(TypeName = RenamedActorTypeName)]
private sealed class RenamedActor : Actor, ITestActor
{
public RenamedActor(ActorService actorService, ActorId actorId)
: base(actorService, actorId)
{
}
}
}
}

View File

@ -0,0 +1,58 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
namespace Dapr.Actors.Test
{
using Dapr.Actors;
using Dapr.Actors.Runtime;
using Xunit;
public sealed class ActorTypeInformationTests
{
private const string RenamedActorTypeName = "MyRenamedActor";
private interface ITestActor : IActor
{
}
[Fact]
public void TestInferredActorType()
{
var actorType = typeof(TestActor);
var actorTypeInformation = ActorTypeInformation.Get(actorType);
Assert.Equal(actorType.Name, actorTypeInformation.ActorTypeName);
}
[Fact]
public void TestExplicitActorType()
{
var actorType = typeof(RenamedActor);
Assert.NotEqual(RenamedActorTypeName, actorType.Name);
var actorTypeInformation = ActorTypeInformation.Get(actorType);
Assert.Equal(RenamedActorTypeName, actorTypeInformation.ActorTypeName);
}
private sealed class TestActor : Actor, ITestActor
{
public TestActor(ActorService actorService, ActorId actorId)
: base(actorService, actorId)
{
}
}
[Actor(TypeName = RenamedActorTypeName)]
private sealed class RenamedActor : Actor, ITestActor
{
public RenamedActor(ActorService actorService, ActorId actorId)
: base(actorService, actorId)
{
}
}
}
}