Remove static instance of ActorRuntime

Contributes to: #416

This change removes a mutable static instance - this is low-hanging
fruit design improvement and helps us make these features more flexible
in the future.
This commit is contained in:
Ryan Nowak 2020-10-07 13:07:28 -07:00
parent a061b726b6
commit cd781248c7
4 changed files with 41 additions and 36 deletions

View File

@ -10,31 +10,35 @@ namespace Dapr.Actors.AspNetCore
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
internal static class EndpointRouteBuilderExtensions internal static class EndpointRouteBuilderExtensions
{ {
public static IEndpointConventionBuilder AddDaprConfigRoute(this IEndpointRouteBuilder endpoints) public static IEndpointConventionBuilder AddDaprConfigRoute(this IEndpointRouteBuilder endpoints)
{ {
var runtime = endpoints.ServiceProvider.GetRequiredService<ActorRuntime>();
return endpoints.MapGet("dapr/config", async context => return endpoints.MapGet("dapr/config", async context =>
{ {
context.Response.ContentType = "application/json"; context.Response.ContentType = "application/json";
await ActorRuntime.Instance.SerializeSettingsAndRegisteredTypes(context.Response.BodyWriter); await runtime.SerializeSettingsAndRegisteredTypes(context.Response.BodyWriter);
}); });
} }
public static IEndpointConventionBuilder AddActorDeactivationRoute(this IEndpointRouteBuilder endpoints) public static IEndpointConventionBuilder AddActorDeactivationRoute(this IEndpointRouteBuilder endpoints)
{ {
var runtime = endpoints.ServiceProvider.GetRequiredService<ActorRuntime>();
return endpoints.MapDelete("actors/{actorTypeName}/{actorId}", async context => return endpoints.MapDelete("actors/{actorTypeName}/{actorId}", async context =>
{ {
var routeValues = context.Request.RouteValues; var routeValues = context.Request.RouteValues;
var actorTypeName = (string)routeValues["actorTypeName"]; var actorTypeName = (string)routeValues["actorTypeName"];
var actorId = (string)routeValues["actorId"]; var actorId = (string)routeValues["actorId"];
await ActorRuntime.DeactivateAsync(actorTypeName, actorId); await runtime.DeactivateAsync(actorTypeName, actorId);
}); });
} }
public static IEndpointConventionBuilder AddActorMethodRoute(this IEndpointRouteBuilder endpoints) public static IEndpointConventionBuilder AddActorMethodRoute(this IEndpointRouteBuilder endpoints)
{ {
var runtime = endpoints.ServiceProvider.GetRequiredService<ActorRuntime>();
return endpoints.MapPut("actors/{actorTypeName}/{actorId}/method/{methodName}", async context => return endpoints.MapPut("actors/{actorTypeName}/{actorId}/method/{methodName}", async context =>
{ {
var routeValues = context.Request.RouteValues; var routeValues = context.Request.RouteValues;
@ -46,7 +50,7 @@ namespace Dapr.Actors.AspNetCore
if (context.Request.Headers.ContainsKey(Constants.RequestHeaderName)) if (context.Request.Headers.ContainsKey(Constants.RequestHeaderName))
{ {
var daprActorheader = context.Request.Headers[Constants.RequestHeaderName]; var daprActorheader = context.Request.Headers[Constants.RequestHeaderName];
var (header, body) = await ActorRuntime.DispatchWithRemotingAsync(actorTypeName, actorId, methodName, daprActorheader, context.Request.Body); var (header, body) = await runtime.DispatchWithRemotingAsync(actorTypeName, actorId, methodName, daprActorheader, context.Request.Body);
// Item 1 is header , Item 2 is body // Item 1 is header , Item 2 is body
if (header != string.Empty) if (header != string.Empty)
@ -62,7 +66,7 @@ namespace Dapr.Actors.AspNetCore
// write exception info in response. // write exception info in response.
try try
{ {
await ActorRuntime.DispatchWithoutRemotingAsync(actorTypeName, actorId, methodName, context.Request.Body, context.Response.Body); await runtime.DispatchWithoutRemotingAsync(actorTypeName, actorId, methodName, context.Request.Body, context.Response.Body);
} }
catch (Exception e) catch (Exception e)
{ {
@ -78,6 +82,7 @@ namespace Dapr.Actors.AspNetCore
public static IEndpointConventionBuilder AddReminderRoute(this IEndpointRouteBuilder endpoints) public static IEndpointConventionBuilder AddReminderRoute(this IEndpointRouteBuilder endpoints)
{ {
var runtime = endpoints.ServiceProvider.GetRequiredService<ActorRuntime>();
return endpoints.MapPut("actors/{actorTypeName}/{actorId}/method/remind/{reminderName}", async context => return endpoints.MapPut("actors/{actorTypeName}/{actorId}/method/remind/{reminderName}", async context =>
{ {
var routeValues = context.Request.RouteValues; var routeValues = context.Request.RouteValues;
@ -86,12 +91,13 @@ namespace Dapr.Actors.AspNetCore
var reminderName = (string)routeValues["reminderName"]; var reminderName = (string)routeValues["reminderName"];
// read dueTime, period and data from Request Body. // read dueTime, period and data from Request Body.
await ActorRuntime.FireReminderAsync(actorTypeName, actorId, reminderName, context.Request.Body); await runtime.FireReminderAsync(actorTypeName, actorId, reminderName, context.Request.Body);
}); });
} }
public static IEndpointConventionBuilder AddTimerRoute(this IEndpointRouteBuilder endpoints) public static IEndpointConventionBuilder AddTimerRoute(this IEndpointRouteBuilder endpoints)
{ {
var runtime = endpoints.ServiceProvider.GetRequiredService<ActorRuntime>();
return endpoints.MapPut("actors/{actorTypeName}/{actorId}/method/timer/{timerName}", async context => return endpoints.MapPut("actors/{actorTypeName}/{actorId}/method/timer/{timerName}", async context =>
{ {
var routeValues = context.Request.RouteValues; var routeValues = context.Request.RouteValues;
@ -100,7 +106,7 @@ namespace Dapr.Actors.AspNetCore
var timerName = (string)routeValues["timerName"]; var timerName = (string)routeValues["timerName"];
// read dueTime, period and data from Request Body. // read dueTime, period and data from Request Body.
await ActorRuntime.FireTimerAsync(actorTypeName, actorId, timerName); await runtime.FireTimerAsync(actorTypeName, actorId, timerName);
}); });
} }
} }

View File

@ -9,6 +9,7 @@ namespace Dapr.Actors.AspNetCore
using Dapr.Actors.Runtime; using Dapr.Actors.Runtime;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
/// <summary> /// <summary>
/// Class containing DaprActor related extension methods for Microsoft.AspNetCore.Hosting.IWebHostBuilder. /// Class containing DaprActor related extension methods for Microsoft.AspNetCore.Hosting.IWebHostBuilder.
@ -36,7 +37,11 @@ namespace Dapr.Actors.AspNetCore
return hostBuilder; return hostBuilder;
} }
configureActorRuntime.Invoke(ActorRuntime.Instance); var runtime = new ActorRuntime();
if (configureActorRuntime != null)
{
configureActorRuntime.Invoke(runtime);
}
// Set flag to prevent double service configuration // Set flag to prevent double service configuration
hostBuilder.UseSetting(SettingName, true.ToString()); hostBuilder.UseSetting(SettingName, true.ToString());
@ -47,6 +52,8 @@ namespace Dapr.Actors.AspNetCore
services.AddRouting(); services.AddRouting();
services.AddHealthChecks(); services.AddHealthChecks();
services.AddSingleton<IStartupFilter>(new DaprActorSetupFilter()); services.AddSingleton<IStartupFilter>(new DaprActorSetupFilter());
services.AddSingleton<ActorRuntime>(runtime);
}); });
return hostBuilder; return hostBuilder;

View File

@ -6,6 +6,7 @@
namespace Dapr.Actors.Runtime namespace Dapr.Actors.Runtime
{ {
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
@ -15,13 +16,8 @@ namespace Dapr.Actors.Runtime
/// <summary> /// <summary>
/// Contains methods to register actor types. Registering the types allows the runtime to create instances of the actor. /// Contains methods to register actor types. Registering the types allows the runtime to create instances of the actor.
/// </summary> /// </summary>
public class ActorRuntime public sealed class ActorRuntime
{ {
/// <summary>
/// Gets ActorRuntime.
/// </summary>
public static readonly ActorRuntime Instance = new ActorRuntime();
private const string TraceType = "ActorRuntime"; private const string TraceType = "ActorRuntime";
// Map of ActorType --> ActorManager. // Map of ActorType --> ActorManager.
@ -29,10 +25,6 @@ namespace Dapr.Actors.Runtime
private ActorSettings actorSettings; private ActorSettings actorSettings;
/// <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() internal ActorRuntime()
{ {
this.actorSettings = new ActorSettings(); this.actorSettings = new ActorSettings();
@ -78,7 +70,7 @@ namespace Dapr.Actors.Runtime
actorSettingsDelegate.Invoke(this.actorSettings); actorSettingsDelegate.Invoke(this.actorSettings);
} }
internal Task SerializeSettingsAndRegisteredTypes(System.Buffers.IBufferWriter<byte> output) internal Task SerializeSettingsAndRegisteredTypes(IBufferWriter<byte> output)
{ {
using Utf8JsonWriter writer = new Utf8JsonWriter(output); using Utf8JsonWriter writer = new Utf8JsonWriter(output);
writer.WriteStartObject(); writer.WriteStartObject();
@ -124,9 +116,9 @@ namespace Dapr.Actors.Runtime
/// <param name="actorTypeName">Actor type name to deactivate the actor for.</param> /// <param name="actorTypeName">Actor type name to deactivate the actor for.</param>
/// <param name="actorId">Actor id for the actor to be deactivated.</param> /// <param name="actorId">Actor id for the actor to be deactivated.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
internal static async Task DeactivateAsync(string actorTypeName, string actorId) internal async Task DeactivateAsync(string actorTypeName, string actorId)
{ {
await Instance.GetActorManager(actorTypeName).DeactivateActor(new ActorId(actorId)); await GetActorManager(actorTypeName).DeactivateActor(new ActorId(actorId));
} }
/// <summary> /// <summary>
@ -134,14 +126,14 @@ namespace Dapr.Actors.Runtime
/// </summary> /// </summary>
/// <param name="actorTypeName">Actor type name to invokde the method for.</param> /// <param name="actorTypeName">Actor type name to invokde the method for.</param>
/// <param name="actorId">Actor id for the actor for which method will be invoked.</param> /// <param name="actorId">Actor id for the actor for which method will be invoked.</param>
/// <param name="actorMethodName">MEthos name on actor type which will be invoked.</param> /// <param name="actorMethodName">Method name on actor type which will be invoked.</param>
/// <param name="daprActorheader">Actor Header.</param> /// <param name="daprActorheader">Actor Header.</param>
/// <param name="data">Payload for the actor method.</param> /// <param name="data">Payload for the actor method.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns> /// <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) internal Task<Tuple<string, byte[]>> DispatchWithRemotingAsync(string actorTypeName, string actorId, string actorMethodName, string daprActorheader, Stream data, CancellationToken cancellationToken = default)
{ {
return Instance.GetActorManager(actorTypeName).DispatchWithRemotingAsync(new ActorId(actorId), actorMethodName, daprActorheader, data, cancellationToken); return GetActorManager(actorTypeName).DispatchWithRemotingAsync(new ActorId(actorId), actorMethodName, daprActorheader, data, cancellationToken);
} }
/// <summary> /// <summary>
@ -149,14 +141,14 @@ namespace Dapr.Actors.Runtime
/// </summary> /// </summary>
/// <param name="actorTypeName">Actor type name to invokde the method for.</param> /// <param name="actorTypeName">Actor type name to invokde the method for.</param>
/// <param name="actorId">Actor id for the actor for which method will be invoked.</param> /// <param name="actorId">Actor id for the actor for which method will be invoked.</param>
/// <param name="actorMethodName">MEthos name on actor type which will be invoked.</param> /// <param name="actorMethodName">Method name on actor type which will be invoked.</param>
/// <param name="requestBodyStream">Payload for the actor method.</param> /// <param name="requestBodyStream">Payload for the actor method.</param>
/// <param name="responseBodyStream">Response for the actor method.</param> /// <param name="responseBodyStream">Response for the actor method.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns> /// <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) internal Task DispatchWithoutRemotingAsync(string actorTypeName, string actorId, string actorMethodName, Stream requestBodyStream, Stream responseBodyStream, CancellationToken cancellationToken = default)
{ {
return Instance.GetActorManager(actorTypeName).DispatchWithoutRemotingAsync(new ActorId(actorId), actorMethodName, requestBodyStream, responseBodyStream, cancellationToken); return GetActorManager(actorTypeName).DispatchWithoutRemotingAsync(new ActorId(actorId), actorMethodName, requestBodyStream, responseBodyStream, cancellationToken);
} }
/// <summary> /// <summary>
@ -168,9 +160,9 @@ namespace Dapr.Actors.Runtime
/// <param name="requestBodyStream">Payload for the actor method.</param> /// <param name="requestBodyStream">Payload for the actor method.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns> /// <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) internal Task FireReminderAsync(string actorTypeName, string actorId, string reminderName, Stream requestBodyStream, CancellationToken cancellationToken = default)
{ {
return Instance.GetActorManager(actorTypeName).FireReminderAsync(new ActorId(actorId), reminderName, requestBodyStream, cancellationToken); return GetActorManager(actorTypeName).FireReminderAsync(new ActorId(actorId), reminderName, requestBodyStream, cancellationToken);
} }
/// <summary> /// <summary>
@ -181,9 +173,9 @@ namespace Dapr.Actors.Runtime
/// <param name="timerName">The name of timer provided during registration.</param> /// <param name="timerName">The name of timer provided during registration.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns> /// <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) internal Task FireTimerAsync(string actorTypeName, string actorId, string timerName, CancellationToken cancellationToken = default)
{ {
return Instance.GetActorManager(actorTypeName).FireTimerAsync(new ActorId(actorId), timerName, cancellationToken); return GetActorManager(actorTypeName).FireTimerAsync(new ActorId(actorId), timerName, cancellationToken);
} }
private ActorManager GetActorManager(string actorTypeName) private ActorManager GetActorManager(string actorTypeName)

View File

@ -7,7 +7,6 @@ namespace Dapr.Actors.Test
{ {
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
@ -54,18 +53,19 @@ namespace Dapr.Actors.Test
// This tests the change that removed the Activate message from Dapr runtime -> app. // This tests the change that removed the Activate message from Dapr runtime -> app.
[Fact] [Fact]
public void NoActivateMessageFromRuntime() public async Task NoActivateMessageFromRuntime()
{ {
var actorType = typeof(MyActor); var actorType = typeof(MyActor);
ActorRuntime.Instance.RegisterActor<MyActor>(); var runtime = new ActorRuntime();
runtime.RegisterActor<MyActor>();
var output = new MemoryStream(); var output = new MemoryStream();
ActorRuntime.DispatchWithoutRemotingAsync("MyActor", "abc", "MyMethod", new MemoryStream(), output).GetAwaiter().GetResult(); await runtime.DispatchWithoutRemotingAsync("MyActor", "abc", "MyMethod", new MemoryStream(), output);
string s = Encoding.UTF8.GetString(output.ToArray()); string s = Encoding.UTF8.GetString(output.ToArray());
Assert.Equal("\"hi\"", s); Assert.Equal("\"hi\"", s);
Assert.Contains(actorType.Name, ActorRuntime.Instance.RegisteredActorTypes, StringComparer.InvariantCulture); Assert.Contains(actorType.Name, runtime.RegisteredActorTypes, StringComparer.InvariantCulture);
Console.WriteLine("done"); Console.WriteLine("done");
} }