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
5 changes: 4 additions & 1 deletion Akka.Reminders.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
<File Path="README.md" />
</Folder>
<Folder Name="/src/">
<Project Path="src/Akka.Reminders.PostgreSql/Akka.Reminders.PostgreSql.csproj" />
<Project Path="src/Akka.Reminders.Sql/Akka.Reminders.Sql.csproj" />
<Project Path="src/Akka.Reminders.SqlServer/Akka.Reminders.SqlServer.csproj" />
<Project Path="src/Akka.Reminders.Sqlite/Akka.Reminders.Sqlite.csproj" />
</Folder>
<Project Path="src/Akka.Reminders.Tests/Akka.Reminders.Tests.csproj" />
<Project Path="src/Akka.Reminders.Benchmarks/Akka.Reminders.Benchmarks.csproj" />
<Project Path="src\Akka.Reminders\Akka.Reminders.csproj" />
</Solution>
</Solution>
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<PackageVersion Include="Akka.Cluster.Hosting" Version="$(AkkaHostingVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.4" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.8" />
<PackageVersion Include="Npgsql" Version="8.0.8" />
</ItemGroup>
<!-- Test dependencies -->
Expand All @@ -29,4 +30,4 @@
<ItemGroup>
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.103" />
</ItemGroup>
</Project>
</Project>
90 changes: 67 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Akka.Reminders provides a reliable way to schedule reminders (time-delayed messa
## Features

- ✅ **Single and recurring reminders** - Schedule one-time or repeating time-based messages
- ✅ **SQL storage backends** - Production-ready SQL Server and PostgreSQL support
- ✅ **SQL storage backends** - Production-ready SQL Server, PostgreSQL, and SQLite support
- ✅ **Automatic retries** - Failed deliveries retry with exponential backoff
- ✅ **Cluster singleton** - Reminder scheduler with automatic failover
- ✅ **Akka.Hosting integration** - First-class configuration API
Expand All @@ -30,6 +30,7 @@ Akka.Reminders provides a reliable way to schedule reminders (time-delayed messa
- [Configuration](#configuration)
- [SQL Server Storage](#sql-server-storage)
- [PostgreSQL Storage](#postgresql-storage)
- [SQLite Storage](#sqlite-storage)
- [In-Memory Storage](#in-memory-storage)
- [Reminder Settings](#reminder-settings)
- [Usage Examples](#usage-examples)
Expand All @@ -47,21 +48,34 @@ dotnet add package Aaron.Akka.Reminders

**SQL Server Storage:**
```bash
dotnet add package Aaron.Akka.Reminders.Sql
dotnet add package Aaron.Akka.Reminders.SqlServer
```

**PostgreSQL Storage:**
```bash
dotnet add package Aaron.Akka.Reminders.PostgreSql
```

**SQLite Storage:**
```bash
dotnet add package Aaron.Akka.Reminders.Sqlite
```

**Legacy Compatibility Package (all providers):**
```bash
dotnet add package Aaron.Akka.Reminders.Sql
```

## Supported Storage Backends

| Storage Backend | Package | Auto-Initialize | Documentation |
|----------------|---------|-----------------|---------------|
| In-Memory | `Akka.Reminders` | N/A | Built-in (development/testing only) |
| SQL Server | `Akka.Reminders.Sql` | Yes | [SQL Server Schema](src/Akka.Reminders.Sql/Scripts/SqlServer-Create.sql) |
| PostgreSQL | `Akka.Reminders.Sql` | Yes | [PostgreSQL Schema](src/Akka.Reminders.Sql/Scripts/PostgreSql-Create.sql) |
| In-Memory | `Aaron.Akka.Reminders` | N/A | Built-in (development/testing only) |
| SQL Server | `Aaron.Akka.Reminders.SqlServer` | Yes | [SQL Server Schema](src/Akka.Reminders.SqlServer/Scripts/SqlServer-Create.sql) |
| PostgreSQL | `Aaron.Akka.Reminders.PostgreSql` | Yes | [PostgreSQL Schema](src/Akka.Reminders.PostgreSql/Scripts/PostgreSql-Create.sql) |
| SQLite | `Aaron.Akka.Reminders.Sqlite` | Yes | [SQLite Schema](src/Akka.Reminders.Sqlite/Scripts/Sqlite-Create.sql) |

> **Compatibility:** `Aaron.Akka.Reminders.Sql` remains available and fully functional as a compatibility package, but new projects should prefer provider-specific packages (`SqlServer`, `PostgreSql`, or `Sqlite`).

> **Note:** For production deployments, you can manually create database schemas using the provided SQL scripts instead of using auto-initialization.

Expand Down Expand Up @@ -95,6 +109,7 @@ builder.Services.AddAkka("MySystem", (configBuilder, provider) =>
```csharp
using Akka.Hosting;
using Akka.Reminders;
using Akka.Reminders.SqlServer.Hosting;

var builder = WebApplication.CreateBuilder(args);

Expand Down Expand Up @@ -166,6 +181,9 @@ public class MyEntityActor : ReceiveActor

### SQL Server Storage

`using Akka.Reminders.SqlServer.Hosting;`
`using Akka.Reminders.SqlServer.Configuration;`

The `WithSqlServerStorage` extension method configures SQL Server as the reminder storage backend:

```csharp
Expand All @@ -179,29 +197,29 @@ The `WithSqlServerStorage` extension method configures SQL Server as the reminde

**Parameters:**
- `connectionString`: SQL Server connection string
- `schemaName`: Database schema name (default: "dbo")
- `tableName`: Table name for reminders (default: "reminders")
- `schemaName`: Database schema name (default: "reminders")
- `tableName`: Table name for reminders (default: "scheduled_reminders")
- `autoInitialize`: Auto-create schema/table if missing (default: true)

**Advanced Configuration:**

```csharp
.WithReminders("reminder-host", reminders => reminders
.WithSqlServerStorage(settings =>
.WithSqlServerStorage(new SqlServerReminderStorageSettings
{
settings.ConnectionString = "Server=localhost;...";
settings.SchemaName = "custom_schema";
settings.TableName = "my_reminders";
settings.CommandTimeout = TimeSpan.FromSeconds(60);
settings.AutoInitialize = false; // Manual schema management
ConnectionString = "Server=localhost;...",
SchemaName = "custom_schema",
TableName = "my_reminders",
CommandTimeout = TimeSpan.FromSeconds(60),
AutoInitialize = false // Manual schema management
}))
```

**Manual Schema Setup:**

For production environments, you may prefer to manually create the database schema. Use the provided SQL script:

📄 [SQL Server Schema Script](src/Akka.Reminders.Sql/Scripts/SqlServer-Create.sql)
📄 [SQL Server Schema Script](src/Akka.Reminders.SqlServer/Scripts/SqlServer-Create.sql)

```sql
-- Run this script against your database
Expand All @@ -210,6 +228,9 @@ For production environments, you may prefer to manually create the database sche

### PostgreSQL Storage

`using Akka.Reminders.PostgreSql.Hosting;`
`using Akka.Reminders.PostgreSql.Configuration;`

The `WithPostgreSqlStorage` extension method configures PostgreSQL as the reminder storage backend:

```csharp
Expand All @@ -223,35 +244,58 @@ The `WithPostgreSqlStorage` extension method configures PostgreSQL as the remind

**Parameters:**
- `connectionString`: PostgreSQL connection string
- `schemaName`: Database schema name (default: "public")
- `tableName`: Table name for reminders (default: "reminders")
- `schemaName`: Database schema name (default: "reminders")
- `tableName`: Table name for reminders (default: "scheduled_reminders")
- `autoInitialize`: Auto-create schema/table if missing (default: true)

**Advanced Configuration:**

```csharp
.WithReminders("reminder-host", reminders => reminders
.WithPostgreSqlStorage(settings =>
.WithPostgreSqlStorage(new PostgreSqlReminderStorageSettings
{
settings.ConnectionString = "Host=localhost;...";
settings.SchemaName = "custom_schema";
settings.TableName = "my_reminders";
settings.CommandTimeout = TimeSpan.FromSeconds(60);
settings.AutoInitialize = false; // Manual schema management
ConnectionString = "Host=localhost;...",
SchemaName = "custom_schema",
TableName = "my_reminders",
CommandTimeout = TimeSpan.FromSeconds(60),
AutoInitialize = false // Manual schema management
}))
```

**Manual Schema Setup:**

For production environments, you may prefer to manually create the database schema. Use the provided SQL script:

📄 [PostgreSQL Schema Script](src/Akka.Reminders.Sql/Scripts/PostgreSql-Create.sql)
📄 [PostgreSQL Schema Script](src/Akka.Reminders.PostgreSql/Scripts/PostgreSql-Create.sql)

```sql
-- Run this script against your database
-- Creates schema, table, and indexes
```

### SQLite Storage

`using Akka.Reminders.Sqlite.Hosting;`

The `WithSqliteStorage` extension method configures SQLite as the reminder storage backend:

```csharp
.WithReminders("reminder-host", reminders => reminders
.WithSqliteStorage(
connectionString: "Data Source=reminders.db;Mode=ReadWriteCreate;Cache=Shared",
tableName: "akka_reminders",
autoInitialize: true))
```

**Parameters:**
- `connectionString`: SQLite connection string
- `tableName`: Table name for reminders (default: "scheduled_reminders")
- `autoInitialize`: Auto-create table if missing (default: true)

**Manual Schema Setup:**

📄 [SQLite Schema Script](src/Akka.Reminders.Sqlite/Scripts/Sqlite-Create.sql)

### In-Memory Storage

For development and testing, use the built-in in-memory storage:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<ItemGroup>
<ProjectReference Include="..\Akka.Reminders\Akka.Reminders.csproj" />
<ProjectReference Include="..\Akka.Reminders.Sql\Akka.Reminders.Sql.csproj" />
<ProjectReference Include="..\Akka.Reminders.PostgreSql\Akka.Reminders.PostgreSql.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 5 additions & 5 deletions src/Akka.Reminders.Benchmarks/SqlReminderBenchmarkBase.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Akka.Actor;
using Akka.Reminders.Sql;
using Akka.Reminders.Sql.Configuration;
using Akka.Reminders.PostgreSql;
using Akka.Reminders.PostgreSql.Configuration;
using BenchmarkDotNet.Attributes;
using Npgsql;
using NpgsqlTypes;
Expand All @@ -17,15 +17,15 @@ public abstract class SqlReminderBenchmarkBase
"Host=localhost;Port=5432;Database=reminders_bench;Username=postgres;Password=postgres";

private ActorSystem? _system;
protected SqlReminderStorage Storage { get; private set; } = null!;
protected PostgreSqlReminderStorage Storage { get; private set; } = null!;

[GlobalSetup]
public async Task GlobalSetup()
{
_system = ActorSystem.Create("benchmark-system");

var settings = SqlReminderStorageSettings.CreatePostgreSql(ConnectionString);
Storage = new SqlReminderStorage(settings, _system);
var settings = PostgreSqlReminderStorageSettings.Create(ConnectionString);
Storage = new PostgreSqlReminderStorage(settings, _system);

// Force table creation via auto-initialize
await Storage.GetRemindersOverviewAsync(DateTimeOffset.UtcNow);
Expand Down
25 changes: 25 additions & 0 deletions src/Akka.Reminders.PostgreSql/Akka.Reminders.PostgreSql.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>Aaron.Akka.Reminders.PostgreSql</PackageId>
<Description>PostgreSQL storage implementation for Akka.Reminders.</Description>
<PackageTags>akka;akkadotnet;akka.reminders;sql;postgresql;</PackageTags>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

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

<ItemGroup>
<PackageReference Include="Npgsql" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Scripts\PostgreSql-Create.sql" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace Akka.Reminders.PostgreSql.Configuration;

/// <summary>
/// Configuration settings for PostgreSQL reminder storage.
/// </summary>
public sealed record PostgreSqlReminderStorageSettings
{
/// <summary>
/// The PostgreSQL connection string.
/// </summary>
public required string ConnectionString { get; init; }

/// <summary>
/// The schema name for the reminders table.
/// Default: "reminders"
/// </summary>
public string SchemaName { get; init; } = "reminders";

/// <summary>
/// The table name for storing reminders.
/// Default: "scheduled_reminders"
/// </summary>
public string TableName { get; init; } = "scheduled_reminders";

/// <summary>
/// Whether to automatically create the schema and table if they don't exist.
/// Default: true
/// </summary>
public bool AutoInitialize { get; init; } = true;

/// <summary>
/// The timeout for database operations.
/// Default: 30 seconds
/// </summary>
public TimeSpan CommandTimeout { get; init; } = TimeSpan.FromSeconds(30);

/// <summary>
/// Creates settings for PostgreSQL.
/// </summary>
public static PostgreSqlReminderStorageSettings Create(
string connectionString,
string? schemaName = null,
string? tableName = null,
bool? autoInitialize = null)
{
return new PostgreSqlReminderStorageSettings
{
ConnectionString = connectionString,
SchemaName = schemaName ?? "reminders",
TableName = tableName ?? "scheduled_reminders",
AutoInitialize = autoInitialize ?? true
};
}

/// <summary>
/// Validates the settings and throws if invalid.
/// </summary>
public void Validate()
{
if (string.IsNullOrWhiteSpace(ConnectionString))
throw new ArgumentException("ConnectionString cannot be null or empty.", nameof(ConnectionString));

if (string.IsNullOrWhiteSpace(SchemaName))
throw new ArgumentException("SchemaName cannot be null or empty.", nameof(SchemaName));

if (string.IsNullOrWhiteSpace(TableName))
throw new ArgumentException("TableName cannot be null or empty.", nameof(TableName));

if (CommandTimeout <= TimeSpan.Zero)
throw new ArgumentException("CommandTimeout must be positive.", nameof(CommandTimeout));
}
}
Loading