Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Facility to provide interface while registering actors #1350

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ public void ConfigureServices(IServiceCollection services)
{
// Register actor types and configure actor settings
options.Actors.RegisterActor<MyActor>();

// Register MyActor to a specific interface
options.Actors.RegisterActor<IMyActor, MyActor>();

// Configure default settings
options.ActorIdleTimeout = TimeSpan.FromMinutes(10);
Expand All @@ -150,6 +153,12 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<BankService>();
}
```
> [!IMPORTANT]
> When registering actors, note the return type requirements for the methods inside the interfaces:
> * Pattern 1: `options.Actors.RegisterActor<MyActor>()`
> * In this case, all interfaces implemented by `MyActor` must have methods that return only `Task` or `Task<T>`. This applies to every method in all interfaces `MyActor` implements.
> * Pattern 2: `options.Actors.RegisterActor<IMyActor, MyActor>()`
> * Here, only the `IMyActor` interface is required to have methods that return `Task` or `Task<T>`. Other interfaces `MyActor` are not subject to this restriction.
Comment on lines +156 to +161
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we force the use of IActor interface we can add a Diagnostics to block the compilation when the derived class contains methods that doesn't return Task<T>. Similar to what is done in the actors' source generator for the CancellationToken (example)

I usually don't like to find out particularities from documentation, if possible I prefer my editor to guide me.

What do you think about it?


### Configuring JSON options

Expand Down Expand Up @@ -241,4 +250,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

## Next steps

Try the [Running and using virtual actors example]({{< ref dotnet-actors-howto.md >}}).
Try the [Running and using virtual actors example]({{< ref dotnet-actors-howto.md >}}).
13 changes: 12 additions & 1 deletion src/Dapr.Actors/Resources/SR.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 66 additions & 6 deletions src/Dapr.Actors/Runtime/ActorRegistrationCollection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -51,7 +51,7 @@ public void RegisterActor<TActor>(Action<ActorRegistration> configure = null)
public void RegisterActor<TActor>(ActorRuntimeOptions typeOptions, Action<ActorRegistration> configure = null)
where TActor : Actor
{
RegisterActor<TActor>(null, typeOptions, configure);
RegisterActor(typeof(TActor), default, typeOptions, configure);
}

/// <summary>
Expand All @@ -64,21 +64,81 @@ public void RegisterActor<TActor>(ActorRuntimeOptions typeOptions, Action<ActorR
public void RegisterActor<TActor>(string actorTypeName, Action<ActorRegistration> configure = null)
where TActor : Actor
{
RegisterActor<TActor>(actorTypeName, null, configure);
RegisterActor(typeof(TActor), actorTypeName, null, configure);
}

/// <summary>
/// Registers an actor type in the collection.
/// </summary>
/// <typeparam name="TActorInterface">Type of actor interface.</typeparam>
/// <typeparam name="TActor">Type of actor.</typeparam>
/// <param name="configure">An optional delegate used to configure the actor registration.</param>
public void RegisterActor<TActorInterface, TActor>(Action<ActorRegistration> configure = null)
where TActorInterface : IActor
where TActor : Actor, TActorInterface
{
RegisterActor<TActorInterface, TActor>(actorTypeName: null, configure);
}

/// <summary>
/// Registers an actor type in the collection.
/// </summary>
/// <typeparam name="TActorInterface">Type of actor interface.</typeparam>
/// <typeparam name="TActor">Type of actor.</typeparam>
/// <param name="typeOptions">An optional <see cref="ActorRuntimeOptions"/> that defines values for this type alone.</param>
/// <param name="configure">An optional delegate used to configure the actor registration.</param>
public void RegisterActor<TActorInterface, TActor>(ActorRuntimeOptions typeOptions, Action<ActorRegistration> configure = null)
where TActorInterface : IActor
where TActor : Actor, TActorInterface
{
RegisterActor(typeof(TActorInterface), typeof(TActor), null, typeOptions, configure);
}

/// <summary>
/// Registers an actor type in the collection.
/// </summary>
/// <typeparam name="TActorInterface">Type of actor interface.</typeparam>
/// <typeparam name="TActor">Type of actor.</typeparam>
/// <param name="actorTypeName">The name of the actor type represented by the actor.</param>
/// <param name="configure">An optional delegate used to configure the actor registration.</param>
/// <remarks>The value of <paramref name="actorTypeName"/> will have precedence over the default actor type name derived from the actor implementation type or any type name set via <see cref="ActorAttribute"/>.</remarks>
public void RegisterActor<TActorInterface, TActor>(string actorTypeName, Action<ActorRegistration> configure = null)
where TActorInterface : IActor
where TActor : Actor, TActorInterface
{
RegisterActor(typeof(TActorInterface), typeof(TActor), actorTypeName, null, configure);
}

/// <summary>
/// Registers an actor type in the collection.
/// </summary>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorTypeName">The name of the actor type represented by the actor.</param>
/// <param name="typeOptions">An optional <see cref="ActorRuntimeOptions"/> that defines values for this type alone.</param>
/// <param name="configure">An optional delegate used to configure the actor registration.</param>
/// <remarks>The value of <paramref name="actorTypeName"/> will have precedence over the default actor type name derived from the actor implementation type or any type name set via <see cref="ActorAttribute"/>.</remarks>
public void RegisterActor<TActor>(string actorTypeName, ActorRuntimeOptions typeOptions, Action<ActorRegistration> configure = null)
where TActor : Actor
public void RegisterActor(Type actorType, string actorTypeName, ActorRuntimeOptions typeOptions, Action<ActorRegistration> configure = null)
{
RegisterActorInternal(default, actorType, actorTypeName, typeOptions, configure);
}

/// <summary>
/// Registers an actor type in the collection.
/// </summary>
/// <param name="actorInterfaceType">Type of actor interface.</param>
/// <param name="actorType">Type of actor.</param>
/// <param name="actorTypeName">The name of the actor type represented by the actor.</param>
/// <param name="typeOptions">An optional <see cref="ActorRuntimeOptions"/> that defines values for this type alone.</param>
/// <param name="configure">An optional delegate used to configure the actor registration.</param>
/// <remarks>The value of <paramref name="actorTypeName"/> will have precedence over the default actor type name derived from the actor implementation type or any type name set via <see cref="ActorAttribute"/>.</remarks>
public void RegisterActor(Type actorInterfaceType, Type actorType, string actorTypeName, ActorRuntimeOptions typeOptions, Action<ActorRegistration> configure = null)
{
RegisterActorInternal(actorInterfaceType, actorType, actorTypeName, typeOptions, configure);
}

private void RegisterActorInternal(Type actorInterfaceType, Type actorType, string actorTypeName, ActorRuntimeOptions typeOptions, Action<ActorRegistration> configure = null)
{
var actorTypeInfo = ActorTypeInformation.Get(typeof(TActor), actorTypeName);
var actorTypeInfo = ActorTypeInformation.Get(actorInterfaceType, actorType, actorTypeName);
var registration = new ActorRegistration(actorTypeInfo, typeOptions);
configure?.Invoke(registration);
this.Add(registration);
Expand Down
2 changes: 1 addition & 1 deletion src/Dapr.Actors/Runtime/ActorTypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
38 changes: 36 additions & 2 deletions src/Dapr.Actors/Runtime/ActorTypeInformation.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -120,6 +120,40 @@ public static ActorTypeInformation Get(Type actorType)
/// <remarks>The value of <paramref name="actorTypeName"/> will have precedence over the default actor type name derived from the actor implementation type or any type name set via <see cref="ActorAttribute"/>.</remarks>
public static ActorTypeInformation Get(Type actorType, string actorTypeName)
{
return GetInternal(default, actorType, actorTypeName);
}

/// <summary>
/// Creates an <see cref="ActorTypeInformation"/> from actorType.
/// </summary>
/// <param name="actorInterfaceType">The type of interface implementing the actor to create ActorTypeInformation for.</param>
/// <param name="actorType">The type of class implementing the actor to create ActorTypeInformation for.</param>
/// <param name="actorTypeName">The name of the actor type represented by the actor.</param>
/// <returns><see cref="ActorTypeInformation"/> created from actorType.</returns>
/// <exception cref="System.ArgumentException">
/// <para>When <see cref="System.Type.BaseType"/> for actorType is not of type <see cref="Actor"/>.</para>
/// <para>When actorType does not implement an interface deriving from <see cref="IActor"/>
/// and is not marked as abstract.</para>
/// </exception>
/// <remarks>The value of <paramref name="actorTypeName"/> will have precedence over the default actor type name derived from the actor implementation type or any type name set via <see cref="ActorAttribute"/>.</remarks>
public static ActorTypeInformation Get(Type actorInterfaceType, Type actorType, string actorTypeName)
{
return GetInternal(actorInterfaceType, actorType, actorTypeName);
}

private static ActorTypeInformation GetInternal(Type actorInterfaceType, Type actorType, string actorTypeName)
{
if (actorInterfaceType != default && !actorInterfaceType.IsActorInterface())
{
throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
SR.ErrorNotAnActorInterface,
actorInterfaceType.FullName,
typeof(Actor).FullName),
"actorInterfaceType");
}

if (!actorType.IsActor())
{
throw new ArgumentException(
Expand All @@ -132,7 +166,7 @@ public static ActorTypeInformation Get(Type actorType, string actorTypeName)
}

// get all actor interfaces
var actorInterfaces = actorType.GetActorInterfaces();
var actorInterfaces = actorInterfaceType != default ? new Type[] { actorInterfaceType } : actorType.GetActorInterfaces();

// ensure that the if the actor type is not abstract it implements at least one actor interface
if ((actorInterfaces.Length == 0) && (!actorType.GetTypeInfo().IsAbstract))
Expand Down
75 changes: 74 additions & 1 deletion test/Dapr.Actors.AspNetCore.Test/ActorHostingTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -11,8 +11,10 @@
// limitations under the License.
// ------------------------------------------------------------------------

using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Dapr.Actors.Client;
using Dapr.Actors.Runtime;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -88,6 +90,50 @@ public void CanAccessProxyFactoryWithCustomJsonOptions()
Assert.Same(jsonOptions, factory.DefaultOptions.JsonSerializerOptions);
}

[Fact]
public void CanRegisterActorsToSpecificInterface()
{
var services = new ServiceCollection();
services.AddLogging();
services.AddOptions();
services.AddActors(options =>
{
options.Actors.RegisterActor<IMyActor, InternalMyActor>();
});

var runtime = services.BuildServiceProvider().GetRequiredService<ActorRuntime>();

Assert.Collection(
runtime.RegisteredActors.Select(r => r.Type.ActorTypeName).OrderBy(t => t),
t => Assert.Equal(ActorTypeInformation.Get(typeof(IMyActor), typeof(InternalMyActor), actorTypeName: null).ActorTypeName, t));

Assert.Collection(
runtime.RegisteredActors.Select(r => r.Type.InterfaceTypes.First()).OrderBy(t => t),
t => Assert.Equal(ActorTypeInformation.Get(typeof(IMyActor), typeof(InternalMyActor), actorTypeName: null).InterfaceTypes.First(), t));

Assert.True(runtime.RegisteredActors.First().Type.InterfaceTypes.Count() == 1);
}

[Fact]
public void RegisterActorThrowsArgumentExceptionWhenAnyInterfaceInTheChainIsNotIActor()
{
var services = new ServiceCollection();
services.AddLogging();
services.AddOptions();
services.AddActors(options =>
{
Assert.Throws<ArgumentException>(() => options.Actors.RegisterActor<INonActor1, InternalMyActor>());
});
}

private interface INonActor
{
}

private interface INonActor1 : INonActor, IActor
{
}

private interface ITestActor : IActor
{
}
Expand All @@ -107,5 +153,32 @@ public TestActor2(ActorHost host)
{
}
}

public interface IMyActor : IActor
{
Task SomeMethod();
}

public interface IInternalMyActor : IMyActor
{
void SomeInternalMethod();
}

public class InternalMyActor : Actor, IInternalMyActor, INonActor1
{
public InternalMyActor(ActorHost host)
: base(host)
{
}

public void SomeInternalMethod()
{
}

public Task SomeMethod()
{
return Task.CompletedTask;
}
}
}
}