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
2 changes: 2 additions & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ const config: UserConfig<DefaultTheme.Config> = {
{text: 'Sql Server', link: '/guide/messaging/transports/sqlserver'},
{text: 'PostgreSQL', link: '/guide/messaging/transports/postgresql'},
{text: 'MySQL', link: '/guide/messaging/transports/mysql'},
{text: 'SQLite', link: '/guide/messaging/transports/sqlite'},
{text: 'MQTT', link: '/guide/messaging/transports/mqtt'},
{text: 'NATS', link: '/guide/messaging/transports/nats'},
{text: 'Kafka', link: '/guide/messaging/transports/kafka'},
Expand Down Expand Up @@ -258,6 +259,7 @@ const config: UserConfig<DefaultTheme.Config> = {
{text: 'Sql Server Integration', link: '/guide/durability/sqlserver'},
{text: 'PostgreSQL Integration', link: '/guide/durability/postgresql'},
{text: 'MySQL Integration', link: '/guide/durability/mysql'},
{text: 'SQLite Integration', link: '/guide/durability/sqlite'},
{text: 'RavenDb Integration', link: '/guide/durability/ravendb'},
{text: 'Entity Framework Core Integration', collapsed: false, link: '/guide/durability/efcore', items: [
{text: 'Transactional Middleware', link: '/guide/durability/efcore/transactional-middleware'},
Expand Down
211 changes: 211 additions & 0 deletions docs/guide/durability/sqlite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# SQLite Integration

::: info
Wolverine can use the SQLite durability options with any mix of Entity Framework Core
as a higher level persistence framework. SQLite is a great choice for smaller applications,
development/testing scenarios, or single-node deployments where you want durable messaging
without the overhead of a separate database server.
:::

Wolverine supports a SQLite backed message persistence strategy and even a SQLite backed messaging transport
option. To get started, add the `WolverineFx.Sqlite` dependency to your application:

```bash
dotnet add package WolverineFx.Sqlite
```

## Message Persistence

To enable SQLite to serve as Wolverine's [transactional inbox and outbox](./), you just need to use the `WolverineOptions.PersistMessagesWithSqlite()`
extension method as shown below in a sample:

<!-- snippet: sample_setup_sqlite_storage -->
<a id='snippet-sample_setup_sqlite_storage'></a>
```cs
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("sqlite");

builder.Host.UseWolverine(opts =>
{
// Setting up SQLite-backed message storage
// This requires a reference to Wolverine.Sqlite
opts.PersistMessagesWithSqlite(connectionString);

// Other Wolverine configuration
});

// This is rebuilding the persistent storage database schema on startup
// and also clearing any persisted envelope state
builder.Host.UseResourceSetupOnStartup();

var app = builder.Build();

// Other ASP.Net Core configuration...

// Using JasperFx opens up command line utilities for managing
// the message storage
return await app.RunJasperFxCommands(args);
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/SqliteTests/DocumentationSamples.cs#L15-L41' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_setup_sqlite_storage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

### Connection String Examples

SQLite supports both file-based and in-memory databases:

<!-- snippet: sample_sqlite_connection_string_examples -->
<a id='snippet-sample_sqlite_connection_string_examples'></a>
```cs
// File-based database (recommended for production)
opts.PersistMessagesWithSqlite("Data Source=wolverine.db");

// Shared in-memory database (useful for testing)
opts.PersistMessagesWithSqlite("Data Source=wolverine;Mode=Memory;Cache=Shared");
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/SqliteTests/DocumentationSamples.cs#L49-L57' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_sqlite_connection_string_examples' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

::: warning
When using in-memory databases with `Mode=Memory;Cache=Shared`, the database is destroyed when the last connection
closes. Wolverine internally manages a keep-alive connection to prevent premature destruction, but this mode is
best suited for testing scenarios rather than production deployments.
:::

## SQLite Messaging Transport

The `WolverineFx.Sqlite` Nuget also contains a simple messaging transport that was mostly meant to be usable for teams
who want asynchronous queueing without introducing more specialized infrastructure. To enable this transport in your code,
use this option which *also* activates SQLite backed message persistence:

<!-- snippet: sample_using_sqlite_transport -->
<a id='snippet-sample_using_sqlite_transport'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
var connectionString = builder.Configuration.GetConnectionString("sqlite");
opts.UseSqlitePersistenceAndTransport(
connectionString,

// This argument is the database schema name for the envelope storage.
// In SQLite, the default is 'main' which refers to the primary database.
"main",

// This schema name is for the actual SQLite queue tables.
// Default is 'wolverine_queues'
transportSchema:"wolverine_queues")

// Tell Wolverine to build out all necessary queue or scheduled message
// tables on demand as needed
.AutoProvision()

// Optional that may be helpful in testing, but probably bad
// in production!
.AutoPurgeOnStartup();

// Use this extension method to create subscriber rules
opts.PublishAllMessages().ToSqliteQueue("outbound");

// Use this to set up queue listeners
opts.ListenToSqliteQueue("inbound")

// Optionally specify how many messages to
// fetch into the listener at any one time
.MaximumMessagesToReceive(50);
});

using var host = builder.Build();
await host.StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/SqliteTests/DocumentationSamples.cs#L63-L102' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_sqlite_transport' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

The SQLite transport is strictly queue-based at this point. The queues are configured as durable by default, meaning
that they are utilizing the transactional inbox and outbox. The SQLite queues can also be buffered:

<!-- snippet: sample_setting_sqlite_queue_to_buffered -->
<a id='snippet-sample_setting_sqlite_queue_to_buffered'></a>
```cs
opts.ListenToSqliteQueue("sender").BufferedInMemory();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/SqliteTests/DocumentationSamples.cs#L113-L117' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_setting_sqlite_queue_to_buffered' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Using this option just means that the SQLite queues can be used for both sending or receiving with no integration
with the transactional inbox or outbox. This is a little more performant, but less safe as messages could be
lost if held in memory when the application shuts down unexpectedly.

### Polling
Wolverine has a number of internal polling operations, and any SQLite queues will be polled on a configured interval.
The default polling interval is set in the `DurabilitySettings` class and can be configured at runtime as below:

```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
// Health check message queue/dequeue
opts.Durability.HealthCheckPollingTime = TimeSpan.FromSeconds(10);

// Node reassigment checks
opts.Durability.NodeReassignmentPollingTime = TimeSpan.FromSeconds(5);

// User queue poll frequency
opts.Durability.ScheduledJobPollingTime = TimeSpan.FromSeconds(5);
}
```

::: info Control queue
Wolverine has an internal control queue (`dbcontrol`) used for internal operations.
This queue is hardcoded to poll every second and should not be changed to ensure the stability of the application.
:::


## Lightweight Saga Usage

See the details on [Lightweight Saga Storage](/guide/durability/sagas.html#lightweight-saga-storage) for more information.

SQLite saga storage uses a `TEXT` column (JSON serialized) for saga state and supports optimistic concurrency with version tracking.

## SQLite-Specific Considerations

### Advisory Locks

SQLite does not have native advisory locks like PostgreSQL. Wolverine uses a table-based locking mechanism
(`wolverine_locks` table) to emulate advisory locks for distributed locking. Locks are acquired by inserting
rows and released by deleting them.

### Data Types

The SQLite persistence uses the following data type mappings:

| Purpose | SQLite Type |
|---------|-------------|
| Message body | `BLOB` |
| Saga state | `TEXT` (JSON) |
| Timestamps | `TEXT` (stored as `datetime('now')` UTC format) |
| GUIDs | `TEXT` |
| IDs (auto-increment) | `INTEGER` |

### Schema Names

SQLite only supports the `main` schema name at this time. Unlike PostgreSQL or SQL Server, SQLite does not have
a traditional schema system, so custom schema names are not currently supported. The `schemaName` parameter on
`PersistMessagesWithSqlite()` and `UseSqlitePersistenceAndTransport()` is provided for forward compatibility —
a future release will support non-"main" schemas via SQLite's
[ATTACH DATABASE](https://www.sqlite.org/lang_attach.html) mechanism.

### Multi-Tenancy

Multi-tenancy through separate databases per tenant is not yet supported for the SQLite persistence. This feature
is planned for an upcoming release. For multi-tenant scenarios today, consider using the
[PostgreSQL integration](/guide/durability/postgresql#multi-tenancy) instead.

### Concurrency

SQLite uses file-level locking, which means only one writer can access the database at a time. For applications
with high write throughput, consider using PostgreSQL or SQL Server instead. However, for moderate workloads and
single-node deployments, SQLite performs well and eliminates the need for external database infrastructure.

### Compatibility

The SQLite persistence is compatible with any platform supported by [Microsoft.Data.Sqlite](https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/). The implementation uses the Weasel.Sqlite library for schema management.
3 changes: 3 additions & 0 deletions docs/guide/messaging/transports/sqlite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SQLite Transport

See the [SQLite Transport](/guide/durability/sqlite#sqlite-messaging-transport) documentation in the [SQLite Integration](/guide/durability/sqlite) topic.
120 changes: 120 additions & 0 deletions src/Persistence/SqliteTests/DocumentationSamples.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using JasperFx;
using JasperFx.Resources;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Wolverine;
using Wolverine.Sqlite;

namespace SqliteTests;

public class DocumentationSamples
{
public static async Task<int> SetupSqliteStorage(string[] args)
{
#region sample_setup_sqlite_storage

var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("sqlite");

builder.Host.UseWolverine(opts =>
{
// Setting up SQLite-backed message storage
// This requires a reference to Wolverine.Sqlite
opts.PersistMessagesWithSqlite(connectionString);

// Other Wolverine configuration
});

// This is rebuilding the persistent storage database schema on startup
// and also clearing any persisted envelope state
builder.Host.UseResourceSetupOnStartup();

var app = builder.Build();

// Other ASP.Net Core configuration...

// Using JasperFx opens up command line utilities for managing
// the message storage
return await app.RunJasperFxCommands(args);

#endregion
}

public static void SqliteConnectionStringExamples()
{
using var host = Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
#region sample_sqlite_connection_string_examples

// File-based database (recommended for production)
opts.PersistMessagesWithSqlite("Data Source=wolverine.db");

// Shared in-memory database (useful for testing)
opts.PersistMessagesWithSqlite("Data Source=wolverine;Mode=Memory;Cache=Shared");

#endregion
}).Build();
}

public static async Task UsingSqliteTransport()
{
#region sample_using_sqlite_transport

var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
var connectionString = builder.Configuration.GetConnectionString("sqlite");
opts.UseSqlitePersistenceAndTransport(
connectionString,

// This argument is the database schema name for the envelope storage.
// In SQLite, the default is 'main' which refers to the primary database.
"main",

// This schema name is for the actual SQLite queue tables.
// Default is 'wolverine_queues'
transportSchema:"wolverine_queues")

// Tell Wolverine to build out all necessary queue or scheduled message
// tables on demand as needed
.AutoProvision()

// Optional that may be helpful in testing, but probably bad
// in production!
.AutoPurgeOnStartup();

// Use this extension method to create subscriber rules
opts.PublishAllMessages().ToSqliteQueue("outbound");

// Use this to set up queue listeners
opts.ListenToSqliteQueue("inbound")

// Optionally specify how many messages to
// fetch into the listener at any one time
.MaximumMessagesToReceive(50);
});

using var host = builder.Build();
await host.StartAsync();

#endregion
}

public static void SetSqliteQueueToBuffered()
{
using var host = Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
opts.UseSqlitePersistenceAndTransport("Data Source=wolverine;Mode=Memory;Cache=Shared")
.AutoProvision().AutoPurgeOnStartup().DisableInboxAndOutboxOnAll();

#region sample_setting_sqlite_queue_to_buffered

opts.ListenToSqliteQueue("sender").BufferedInMemory();

#endregion
}).Build();
}
}
Loading