-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Simplify silo/services configuration and streamstone config
- Loading branch information
Showing
12 changed files
with
203 additions
and
136 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
using System; | ||
using System.Buffers; | ||
using System.Collections.Generic; | ||
using Orleans; | ||
using Orleans.Core; | ||
using Orleans.Runtime; | ||
|
||
interface IActorStateFactory : IPersistentStateFactory { } | ||
|
||
class ActorStateFactory(IPersistentStateFactory factory) : IActorStateFactory | ||
{ | ||
public IPersistentState<TState> Create<TState>(IGrainContext context, IPersistentStateConfiguration config) | ||
{ | ||
var state = factory.Create<TState>(context, config); | ||
// We're super conservative here, only replacing if all conditions are met. | ||
// We check for a state with no parameterless constructor up-front, then go from there. | ||
if (typeof(TState).GetConstructor(Type.EmptyTypes) is null && | ||
// We only know how the state bridge works | ||
state is StateStorageBridge<TState> bridge && | ||
// We only support a ctor that receives the grain id as a string | ||
context.GrainId.Key.ToString() is object id && | ||
typeof(TState).GetConstructor(new[] { typeof(string) }) is not null && | ||
// Even so, we might fail activation | ||
Activator.CreateInstance(typeof(TState), id) is TState actor) | ||
{ | ||
// In this case, we can force custom creation of the state object. | ||
// Internally, this causes the StateStorageBridge<T>._grainState to not be | ||
// forcedly constructed via Activator.CreateInstance with a parameterless constructor, | ||
// and instead our "rehydrated" type is used instead. | ||
// NOTE: this causes the OnStart on the PersistentState<TState> class to skip invoking | ||
// ReadStateAsync, as it assumes rehydration makes that unnecessary. | ||
// However, the JournaledGrain base class overrides the OnActivateAsync method and | ||
// forces a sync, so we're good since our generated grain does that too. | ||
bridge.OnRehydrate(new ActivationContext(new GrainState<TState>(actor))); | ||
} | ||
|
||
return state; | ||
} | ||
|
||
class ActivationContext(object actor) : IRehydrationContext | ||
{ | ||
public IEnumerable<string> Keys => throw new NotImplementedException(); | ||
public bool TryGetBytes(string key, out ReadOnlySequence<byte> value) => throw new NotImplementedException(); | ||
public bool TryGetValue<T>(string key, out T? value) | ||
{ | ||
if (actor is T typed) | ||
{ | ||
value = typed; | ||
return true; | ||
} | ||
|
||
value = default; | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,96 +1,53 @@ | ||
using System; | ||
using System.Buffers; | ||
using System.Collections.Generic; | ||
using System.ComponentModel; | ||
using System.Linq; | ||
using Devlooped.CloudActors; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Orleans; | ||
using Orleans.Core; | ||
using Orleans.Hosting; | ||
using Orleans.Runtime; | ||
|
||
[EditorBrowsable(EditorBrowsableState.Never)] | ||
public static class CloudActorsExtensions | ||
namespace Devlooped.CloudActors | ||
{ | ||
/// <summary> | ||
/// Adds the <see cref="IActorBus"/> service and custom actor activation logic. | ||
/// </summary> | ||
public static IServiceCollection AddCloudActors(this IServiceCollection services) | ||
[EditorBrowsable(EditorBrowsableState.Never)] | ||
public static partial class CloudActorsExtensions | ||
{ | ||
services.TryAddSingleton<IActorBus>(sp => new OrleansActorBus(sp.GetRequiredService<IGrainFactory>())); | ||
|
||
// Attempt to replace the OOB persistence so we don't require a parameterless constructor and always | ||
// have actors initialized with a specific id from the grain. | ||
if (services.FirstOrDefault(d => d.ServiceType == typeof(IPersistentStateFactory)) is { } descriptor) | ||
/// <summary> | ||
/// Adds the <see cref="IActorBus"/> service and actor activation logic. | ||
/// </summary> | ||
/// <remarks> | ||
/// It's not necessary to invoke this method if you already invoked <c>AddCloudActors</c> | ||
/// on the <see cref="ISiloBuilder"/> when configuring Orleans. | ||
/// </remarks> | ||
public static IServiceCollection AddCloudActors(this IServiceCollection services) | ||
{ | ||
services.Remove(descriptor); | ||
services.Replace(ServiceDescriptor.Describe( | ||
typeof(IPersistentStateFactory), | ||
s => new ActorActivatorFactory((IPersistentStateFactory)CreateInstance(s, descriptor)), | ||
descriptor.Lifetime | ||
)); | ||
} | ||
return services; | ||
} | ||
|
||
static object CreateInstance(IServiceProvider services, ServiceDescriptor descriptor) | ||
{ | ||
if (descriptor.ImplementationInstance is not null) | ||
return descriptor.ImplementationInstance; | ||
|
||
if (descriptor.ImplementationFactory is not null) | ||
return descriptor.ImplementationFactory(services); | ||
|
||
return ActivatorUtilities.GetServiceOrCreateInstance(services, descriptor.ImplementationType!); | ||
} | ||
|
||
class ActorActivatorFactory(IPersistentStateFactory factory) : IPersistentStateFactory | ||
{ | ||
public IPersistentState<TState> Create<TState>(IGrainContext context, IPersistentStateConfiguration config) | ||
{ | ||
var state = factory.Create<TState>(context, config); | ||
// We're super conservative here, only replacing if all conditions are met. | ||
// We check for a state with no parameterless constructor up-front, then go from there. | ||
if (typeof(TState).GetConstructor(Type.EmptyTypes) is null && | ||
// We only know how the state bridge works | ||
state is StateStorageBridge<TState> bridge && | ||
// We only support a ctor that receives the grain id as a string | ||
context.GrainId.Key.ToString() is object id && | ||
typeof(TState).GetConstructor(new[] { typeof(string) }) is not null && | ||
// Even so, we might fail activation | ||
Activator.CreateInstance(typeof(TState), id) is TState actor) | ||
services.TryAddSingleton<IActorBus>(sp => new OrleansActorBus(sp.GetRequiredService<IGrainFactory>())); | ||
|
||
// Attempt to replace the OOB persistence so we don't require a parameterless constructor and always | ||
// have actors initialized with a specific id from the grain. | ||
if (services.FirstOrDefault(d => d.ServiceType == typeof(IActorStateFactory)) is null && | ||
services.FirstOrDefault(d => d.ServiceType == typeof(IPersistentStateFactory)) is { } descriptor) | ||
{ | ||
// In this case, we can force custom creation of the state object. | ||
// Internally, this causes the StateStorageBridge<T>._grainState to not be | ||
// forcedly constructed via Activator.CreateInstance with a parameterless constructor, | ||
// and instead our "rehydrated" type is used instead. | ||
// NOTE: this causes the OnStart on the PersistentState<TState> class to skip invoking | ||
// ReadStateAsync, as it assumes rehydration makes that unnecessary. | ||
// However, the JournaledGrain base class overrides the OnActivateAsync method and | ||
// forces a sync, so we're good since our generated grain does that too. | ||
bridge.OnRehydrate(new ActivationContext(new GrainState<TState>(actor))); | ||
services.Remove(descriptor); | ||
services.Replace(ServiceDescriptor.Describe( | ||
typeof(IPersistentStateFactory), | ||
s => new ActorStateFactory((IPersistentStateFactory)CreateInstance(s, descriptor)), | ||
descriptor.Lifetime | ||
)); | ||
} | ||
|
||
return state; | ||
return services; | ||
} | ||
|
||
class ActivationContext(object actor) : IRehydrationContext | ||
static object CreateInstance(IServiceProvider services, ServiceDescriptor descriptor) | ||
{ | ||
public IEnumerable<string> Keys => throw new NotImplementedException(); | ||
public bool TryGetBytes(string key, out ReadOnlySequence<byte> value) => throw new NotImplementedException(); | ||
public bool TryGetValue<T>(string key, out T? value) | ||
{ | ||
if (actor is T typed) | ||
{ | ||
value = typed; | ||
return true; | ||
} | ||
if (descriptor.ImplementationInstance is not null) | ||
return descriptor.ImplementationInstance; | ||
|
||
value = default; | ||
return false; | ||
} | ||
if (descriptor.ImplementationFactory is not null) | ||
return descriptor.ImplementationFactory(services); | ||
|
||
return ActivatorUtilities.GetServiceOrCreateInstance(services, descriptor.ImplementationType!); | ||
} | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace Devlooped.CloudActors; | ||
|
||
public class StreamstoneOptions | ||
{ | ||
static readonly JsonSerializerOptions options = new() | ||
{ | ||
AllowTrailingCommas = true, | ||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, | ||
PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate, | ||
Converters = { new JsonStringEnumConverter() }, | ||
}; | ||
|
||
/// <summary> | ||
/// Default options to use when creating a <see cref="StreamstoneStorage"/> instance. | ||
/// </summary> | ||
public static StreamstoneOptions Default { get; } = new(); | ||
|
||
/// <summary> | ||
/// When true, will automatically create a snapshot of the state every <see cref="SnapshotThreshold"/> events. | ||
/// In order to include properties with private setters in the snapshot, the type must be annotated with | ||
/// [JsonInclude]. | ||
/// </summary> | ||
public bool AutoSnapshot { get; set; } = true; | ||
|
||
/// <summary> | ||
/// The settings to use when serializing and deserializing events and snapshot | ||
/// if <see cref="AutoSnapshot"/> is true. | ||
/// </summary> | ||
public JsonSerializerOptions JsonOptions { get; set; } = options; | ||
} |
Oops, something went wrong.