Skip to content
Merged
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
6 changes: 6 additions & 0 deletions Akka.Persistence.Redis.sln
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.Redis.Benchmark.DockerTests", "src\benchmarks\Akka.Persistence.Redis.Benchmark.DockerTests\Akka.Persistence.Redis.Benchmark.DockerTests.csproj", "{BFBB3933-E8FC-4574-8D60-E6FA6BEABAE9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.Redis.Hosting", "src\Akka.Persistence.Redis.Hosting\Akka.Persistence.Redis.Hosting.csproj", "{80BD65FD-3AB6-4AF1-B0CD-573C9D50DB1E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build-system", "build-system", "{D118A7A7-A89D-450D-8218-E2AE580DEF5D}"
ProjectSection(SolutionItems) = preProject
build-system\azure-pipeline.template.yaml = build-system\azure-pipeline.template.yaml
Expand Down Expand Up @@ -70,6 +72,10 @@ Global
{BFBB3933-E8FC-4574-8D60-E6FA6BEABAE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFBB3933-E8FC-4574-8D60-E6FA6BEABAE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFBB3933-E8FC-4574-8D60-E6FA6BEABAE9}.Release|Any CPU.Build.0 = Release|Any CPU
{80BD65FD-3AB6-4AF1-B0CD-573C9D50DB1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{80BD65FD-3AB6-4AF1-B0CD-573C9D50DB1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80BD65FD-3AB6-4AF1-B0CD-573C9D50DB1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{80BD65FD-3AB6-4AF1-B0CD-573C9D50DB1E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(NetStandardVersion)</TargetFramework>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Akka.Persistence.Hosting" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Akka.Persistence.Redis\Akka.Persistence.Redis.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
using System;
using System.Text;
using Akka.Actor;
using Akka.Configuration;
using Akka.Hosting;
using Akka.Persistence.Hosting;

#nullable enable
namespace Akka.Persistence.Redis.Hosting;

public static class AkkaPersistenceRedisHostingExtensions
{
/// <summary>
/// Adds Akka.Persistence.Redis support to this <see cref="ActorSystem"/>.
/// </summary>
/// <param name="builder">
/// The builder instance being configured.
/// </param>
/// <param name="configurationString">
/// Connection string as described here: https://stackexchange.github.io/StackExchange.Redis/Configuration#basic-configuration-strings.
/// </param>
/// <param name="mode">
/// <para>
/// Determines which settings should be added by this method call.
/// </para>
/// <i>Default</i>: <see cref="PersistenceMode.Both"/>
/// </param>
/// <param name="autoInitialize">
/// <para>
/// Should the redis store table be initialized automatically.
/// </para>
/// <i>Default</i>: <c>false</c>
/// </param>
/// <param name="journalBuilder">
/// <para>
/// An <see cref="Action"/> used to configure an <see cref="AkkaPersistenceJournalBuilder"/> instance.
/// </para>
/// <i>Default</i>: <c>null</c>
/// </param>
/// <param name="pluginIdentifier">
/// <para>
/// The configuration identifier for the plugins
/// </para>
/// <i>Default</i>: <c>"redis"</c>
/// </param>
/// <param name="isDefaultPlugin">
/// <para>
/// A <c>bool</c> flag to set the plugin as the default persistence plugin for the <see cref="ActorSystem"/>
/// </para>
/// <b>Default</b>: <c>true</c>
/// </param>
/// <returns>
/// The same <see cref="AkkaConfigurationBuilder"/> instance originally passed in.
/// </returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <see cref="journalBuilder"/> is set and <see cref="mode"/> is set to
/// <see cref="PersistenceMode.SnapshotStore"/>
/// </exception>
public static AkkaConfigurationBuilder WithRedisPersistence(
this AkkaConfigurationBuilder builder,
string configurationString,
PersistenceMode mode = PersistenceMode.Both,
bool autoInitialize = true,
Action<AkkaPersistenceJournalBuilder>? journalBuilder = null,
string pluginIdentifier = "redis",
bool isDefaultPlugin = true)
{
if (mode == PersistenceMode.SnapshotStore && journalBuilder is { })
throw new Exception(
$"{nameof(journalBuilder)} can only be set when {nameof(mode)} is set to either {PersistenceMode.Both} or {PersistenceMode.Journal}");

var journalOpt = new RedisJournalOptions(isDefaultPlugin, pluginIdentifier)
{
ConfigurationString = configurationString,
AutoInitialize = autoInitialize,
};

var adapters = new AkkaPersistenceJournalBuilder(journalOpt.Identifier, builder);
journalBuilder?.Invoke(adapters);
journalOpt.Adapters = adapters;

var snapshotOpt = new RedisSnapshotOptions(isDefaultPlugin, pluginIdentifier)
{
ConfigurationString = configurationString,
AutoInitialize = autoInitialize,
};

return mode switch
{
PersistenceMode.Journal => builder.WithRedisPersistence(journalOpt, null),
PersistenceMode.SnapshotStore => builder.WithRedisPersistence(null, snapshotOpt),
PersistenceMode.Both => builder.WithRedisPersistence(journalOpt, snapshotOpt),
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Invalid PersistenceMode defined.")
};
}

/// <summary>
/// Adds Akka.Persistence.Redis support to this <see cref="ActorSystem"/>. At least one of the
/// configurator delegate needs to be populated else this method will throw an exception.
/// </summary>
/// <param name="builder">
/// The builder instance being configured.
/// </param>
/// <param name="journalOptionConfigurator">
/// <para>
/// An <see cref="Action{T}"/> that modifies an instance of <see cref="RedisJournalOptions"/>,
/// used to configure the journal plugin
/// </para>
/// <i>Default</i>: <c>null</c>
/// </param>
/// <param name="snapshotOptionConfigurator">
/// <para>
/// An <see cref="Action{T}"/> that modifies an instance of <see cref="RedisSnapshotOptions"/>,
/// used to configure the snapshot store plugin
/// </para>
/// <i>Default</i>: <c>null</c>
/// </param>
/// <param name="isDefaultPlugin">
/// <para>
/// A <c>bool</c> flag to set the plugin as the default persistence plugin for the <see cref="ActorSystem"/>
/// </para>
/// <b>Default</b>: <c>true</c>
/// </param>
/// <returns>
/// The same <see cref="AkkaConfigurationBuilder"/> instance originally passed in.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown when both <paramref name="journalOptionConfigurator"/> and <paramref name="snapshotOptionConfigurator"/> are null.
/// </exception>
public static AkkaConfigurationBuilder WithRedisPersistence(
this AkkaConfigurationBuilder builder,
Action<RedisJournalOptions>? journalOptionConfigurator = null,
Action<RedisSnapshotOptions>? snapshotOptionConfigurator = null,
bool isDefaultPlugin = true)
{
if (journalOptionConfigurator is null && snapshotOptionConfigurator is null)
throw new ArgumentException(
$"{nameof(journalOptionConfigurator)} and {nameof(snapshotOptionConfigurator)} could not both be null");

RedisJournalOptions? journalOptions = null;
if (journalOptionConfigurator is { })
{
journalOptions = new RedisJournalOptions(isDefaultPlugin);
journalOptionConfigurator(journalOptions);
}

RedisSnapshotOptions? snapshotOptions = null;
if (snapshotOptionConfigurator is { })
{
snapshotOptions = new RedisSnapshotOptions(isDefaultPlugin);
snapshotOptionConfigurator(snapshotOptions);
}

return builder.WithRedisPersistence(journalOptions, snapshotOptions);
}

/// <summary>
/// Adds Akka.Persistence.Redis support to this <see cref="ActorSystem"/>. At least one of the options
/// have to be populated else this method will throw an exception.
/// </summary>
/// <param name="builder">
/// The builder instance being configured.
/// </param>
/// <param name="journalOptions">
/// <para>
/// An instance of <see cref="RedisJournalOptions"/>, used to configure the journal plugin
/// </para>
/// <i>Default</i>: <c>null</c>
/// </param>
/// <param name="snapshotOptions">
/// <para>
/// An instance of <see cref="RedisSnapshotOptions"/>, used to configure the snapshot store plugin
/// </para>
/// <i>Default</i>: <c>null</c>
/// </param>
/// <returns>
/// The same <see cref="AkkaConfigurationBuilder"/> instance originally passed in.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown when both <paramref name="journalOptions"/> and <paramref name="snapshotOptions"/> are null.
/// </exception>
public static AkkaConfigurationBuilder WithRedisPersistence(
this AkkaConfigurationBuilder builder,
RedisJournalOptions? journalOptions = null,
RedisSnapshotOptions? snapshotOptions = null)
{
if (journalOptions is null && snapshotOptions is null)
throw new ArgumentException($"{nameof(journalOptions)} and {nameof(snapshotOptions)} could not both be null");

return (journalOptions, snapshotOptions) switch
{
(null, null) =>
throw new ArgumentException(
$"{nameof(journalOptions)} and {nameof(snapshotOptions)} could not both be null"),

(_, null) =>
builder
.AddHocon(journalOptions.ToConfig(), HoconAddMode.Prepend)
.AddHocon(journalOptions.DefaultConfig, HoconAddMode.Append)
.AddHocon(RedisPersistence.DefaultConfig(), HoconAddMode.Append),

(null, _) =>
builder
.AddHocon(snapshotOptions.ToConfig(), HoconAddMode.Prepend)
.AddHocon(snapshotOptions.DefaultConfig, HoconAddMode.Append),

(_, _) =>
builder
.AddHocon(journalOptions.ToConfig(), HoconAddMode.Prepend)
.AddHocon(snapshotOptions.ToConfig(), HoconAddMode.Prepend)
.AddHocon(journalOptions.DefaultConfig, HoconAddMode.Append)
.AddHocon(snapshotOptions.DefaultConfig, HoconAddMode.Append)
.AddHocon(RedisPersistence.DefaultConfig(), HoconAddMode.Append),
};
}
}
87 changes: 87 additions & 0 deletions src/Akka.Persistence.Redis.Hosting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Akka.Persistence.Redis.Hosting

Akka.Hosting extension methods to add Akka.Persistence.Redis to an ActorSystem

# Akka.Persistence.Redis Extension Methods

## WithRedisPersistence() Method

```csharp
public static AkkaConfigurationBuilder WithRedisPersistence(
this AkkaConfigurationBuilder builder,
string configurationString,
PersistenceMode mode = PersistenceMode.Both,
bool autoInitialize = true,
Action<AkkaPersistenceJournalBuilder>? journalBuilder = null,
string pluginIdentifier = "Redis",
bool isDefaultPlugin = true);
```

```csharp
public static AkkaConfigurationBuilder WithRedisPersistence(
this AkkaConfigurationBuilder builder,
Action<RedisJournalOptions>? journalOptionConfigurator = null,
Action<RedisSnapshotOptions>? snapshotOptionConfigurator = null,
bool isDefaultPlugin = true)
```

```csharp
public static AkkaConfigurationBuilder WithRedisPersistence(
this AkkaConfigurationBuilder builder,
RedisJournalOptions? journalOptions = null,
RedisSnapshotOptions? snapshotOptions = null)
```

### Parameters

* `configurationString` __string__
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another example of the configuration vs connection string


Connection string used for database access. Connection string as described here: https://stackexchange.github.io/StackExchange.Redis/Configuration#basic-configuration-strings.

* `mode` __PersistenceMode__

Determines which settings should be added by this method call. __Default__: `PersistenceMode.Both`

* `PersistenceMode.Journal`: Only add the journal settings
* `PersistenceMode.SnapshotStore`: Only add the snapshot store settings
* `PersistenceMode.Both`: Add both journal and snapshot store settings

* `autoInitialize` __bool__

Should the Redis store collection be initialized automatically. __Default__: `false`

* `journalBuilder` __Action\<AkkaPersistenceJournalBuilder\>__

An Action delegate used to configure an `AkkaPersistenceJournalBuilder` instance. Used to configure [Event Adapters](https://getakka.net/articles/persistence/event-adapters.html)

* `journalConfigurator` __Action\<RedisJournalOptions\>__

An Action delegate to configure a `RedisJournalOptions` instance.

* `snapshotConfigurator` __Action\<RedisSnapshotOptions\>__

An Action delegate to configure a `RedisSnapshotOptions` instance.

* `journalOptions` __RedisJournalOptions__

An `RedisJournalOptions` instance to configure the Redis journal store.

* `snapshotOptions` __RedisSnapshotOptions__

An `RedisSnapshotOptions` instance to configure the Redis snapshot store.

## Example

```csharp
using var host = new HostBuilder()
.ConfigureServices((context, services) =>
{
services.AddAkka("redisDemo", (builder, provider) =>
{
builder
.WithRedisPersistence("your-redis-connection-string");
});
}).Build();

await host.RunAsync();
```
Loading