diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 2637be7fb..bc45bee58 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -189,6 +189,7 @@ const config: UserConfig = { {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'}, @@ -258,6 +259,7 @@ const config: UserConfig = { {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'}, diff --git a/docs/guide/durability/sqlite.md b/docs/guide/durability/sqlite.md new file mode 100644 index 000000000..086b4f8e7 --- /dev/null +++ b/docs/guide/durability/sqlite.md @@ -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: + + + +```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); +``` +snippet source | anchor + + +### Connection String Examples + +SQLite supports both file-based and in-memory databases: + + + +```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"); +``` +snippet source | anchor + + +::: 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: + + + +```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(); +``` +snippet source | anchor + + +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: + + + +```cs +opts.ListenToSqliteQueue("sender").BufferedInMemory(); +``` +snippet source | anchor + + +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. diff --git a/docs/guide/messaging/transports/sqlite.md b/docs/guide/messaging/transports/sqlite.md new file mode 100644 index 000000000..bebbffc8a --- /dev/null +++ b/docs/guide/messaging/transports/sqlite.md @@ -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. diff --git a/src/Persistence/SqliteTests/DocumentationSamples.cs b/src/Persistence/SqliteTests/DocumentationSamples.cs new file mode 100644 index 000000000..282c6eb65 --- /dev/null +++ b/src/Persistence/SqliteTests/DocumentationSamples.cs @@ -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 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(); + } +}