From 0296e9c3fe9131453e99b774f4e49d210544a935 Mon Sep 17 00:00:00 2001 From: "codebelt-aicia[bot]" Date: Tue, 30 Jun 2026 22:56:03 +0000 Subject: [PATCH 1/8] V5.0.9/service update --- .nuget/Savvyio.App/PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../Savvyio.Commands/PackageReleaseNotes.txt | 6 ++++++ .nuget/Savvyio.Core/PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .nuget/Savvyio.Domain/PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../Savvyio.Messaging/PackageReleaseNotes.txt | 6 ++++++ .../Savvyio.Queries/PackageReleaseNotes.txt | 6 ++++++ CHANGELOG.md | 4 ++++ Directory.Packages.props | 20 +++++++++---------- 37 files changed, 224 insertions(+), 10 deletions(-) diff --git a/.nuget/Savvyio.App/PackageReleaseNotes.txt b/.nuget/Savvyio.App/PackageReleaseNotes.txt index 3625d6b..dedd780 100644 --- a/.nuget/Savvyio.App/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.App/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Commands.Messaging/PackageReleaseNotes.txt b/.nuget/Savvyio.Commands.Messaging/PackageReleaseNotes.txt index da4b859..c0a47ce 100644 --- a/.nuget/Savvyio.Commands.Messaging/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Commands.Messaging/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Commands/PackageReleaseNotes.txt b/.nuget/Savvyio.Commands/PackageReleaseNotes.txt index 8d31926..cbf0b51 100644 --- a/.nuget/Savvyio.Commands/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Commands/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Core/PackageReleaseNotes.txt b/.nuget/Savvyio.Core/PackageReleaseNotes.txt index 4604bb3..2318ce7 100644 --- a/.nuget/Savvyio.Core/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Core/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Domain.EventSourcing/PackageReleaseNotes.txt b/.nuget/Savvyio.Domain.EventSourcing/PackageReleaseNotes.txt index 32d51be..86e2b46 100644 --- a/.nuget/Savvyio.Domain.EventSourcing/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Domain.EventSourcing/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Domain/PackageReleaseNotes.txt b/.nuget/Savvyio.Domain/PackageReleaseNotes.txt index d93c6d2..ac683a8 100644 --- a/.nuget/Savvyio.Domain/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Domain/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.EventDriven.Messaging/PackageReleaseNotes.txt b/.nuget/Savvyio.EventDriven.Messaging/PackageReleaseNotes.txt index b58e220..480e5a8 100644 --- a/.nuget/Savvyio.EventDriven.Messaging/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.EventDriven.Messaging/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.EventDriven/PackageReleaseNotes.txt b/.nuget/Savvyio.EventDriven/PackageReleaseNotes.txt index b4ba79d..b5c5a7c 100644 --- a/.nuget/Savvyio.EventDriven/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.EventDriven/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.Dapper/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.Dapper/PackageReleaseNotes.txt index 8835c23..0612dde 100644 --- a/.nuget/Savvyio.Extensions.Dapper/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.Dapper/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DapperExtensions/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DapperExtensions/PackageReleaseNotes.txt index a5fd9aa..3fa6c31 100644 --- a/.nuget/Savvyio.Extensions.DapperExtensions/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DapperExtensions/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.Dapper/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.Dapper/PackageReleaseNotes.txt index 35e53bf..136c839 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.Dapper/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.Dapper/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.DapperExtensions/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.DapperExtensions/PackageReleaseNotes.txt index 934c47b..20e9b0b 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.DapperExtensions/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.DapperExtensions/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.Domain/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.Domain/PackageReleaseNotes.txt index d08fa15..76814b2 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.Domain/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.Domain/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt index ddf2c75..07ae9e6 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain/PackageReleaseNotes.txt index 2cf0e34..7f71dc7 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore/PackageReleaseNotes.txt index 8f9e0c6..c2addbd 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.NATS/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.NATS/PackageReleaseNotes.txt index bc7eae6..fe79f84 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.NATS/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.NATS/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json/PackageReleaseNotes.txt index dcfea22..f7ff529 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.QueueStorage/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.QueueStorage/PackageReleaseNotes.txt index 0435c4a..3944cf3 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.QueueStorage/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.QueueStorage/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.RabbitMQ/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.RabbitMQ/PackageReleaseNotes.txt index bc6f7b3..3e50867 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.RabbitMQ/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.RabbitMQ/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.SimpleQueueService/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.SimpleQueueService/PackageReleaseNotes.txt index ac6e379..392d3ac 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.SimpleQueueService/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.SimpleQueueService/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.Text.Json/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.Text.Json/PackageReleaseNotes.txt index f2095d6..c9071f1 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.Text.Json/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.Text.Json/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection/PackageReleaseNotes.txt index 97e9f91..cc12a26 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.Dispatchers/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.Dispatchers/PackageReleaseNotes.txt index d1427df..f6748ed 100644 --- a/.nuget/Savvyio.Extensions.Dispatchers/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.Dispatchers/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt index 344be1b..fcf5490 100644 --- a/.nuget/Savvyio.Extensions.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.EFCore.Domain/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.EFCore.Domain/PackageReleaseNotes.txt index a904844..3d9c069 100644 --- a/.nuget/Savvyio.Extensions.EFCore.Domain/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.EFCore.Domain/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.EFCore/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.EFCore/PackageReleaseNotes.txt index 6604062..4c4b29c 100644 --- a/.nuget/Savvyio.Extensions.EFCore/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.EFCore/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.NATS/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.NATS/PackageReleaseNotes.txt index 2b7fcd8..c63adf6 100644 --- a/.nuget/Savvyio.Extensions.NATS/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.NATS/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.Newtonsoft.Json/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.Newtonsoft.Json/PackageReleaseNotes.txt index ee6410e..9752d9a 100644 --- a/.nuget/Savvyio.Extensions.Newtonsoft.Json/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.Newtonsoft.Json/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.QueueStorage/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.QueueStorage/PackageReleaseNotes.txt index 820720a..b8a631a 100644 --- a/.nuget/Savvyio.Extensions.QueueStorage/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.QueueStorage/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.RabbitMQ/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.RabbitMQ/PackageReleaseNotes.txt index 94545d6..815ff8f 100644 --- a/.nuget/Savvyio.Extensions.RabbitMQ/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.RabbitMQ/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.SimpleQueueService/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.SimpleQueueService/PackageReleaseNotes.txt index ae103ce..d66e30b 100644 --- a/.nuget/Savvyio.Extensions.SimpleQueueService/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.SimpleQueueService/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.Text.Json/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.Text.Json/PackageReleaseNotes.txt index b427b46..2b78a72 100644 --- a/.nuget/Savvyio.Extensions.Text.Json/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.Text.Json/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Messaging/PackageReleaseNotes.txt b/.nuget/Savvyio.Messaging/PackageReleaseNotes.txt index 9874555..e2809f6 100644 --- a/.nuget/Savvyio.Messaging/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Messaging/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Queries/PackageReleaseNotes.txt b/.nuget/Savvyio.Queries/PackageReleaseNotes.txt index b2b1d6a..c8be857 100644 --- a/.nuget/Savvyio.Queries/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Queries/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.9 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.8 Availability: .NET 10 and .NET 9 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5256e53..a9a93c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder. +## [5.0.9] - 2026-06-30 + +This is a service update that focuses on package dependencies. + ## [5.0.8] - 2026-06-06 This is a service update that focuses on package dependencies. diff --git a/Directory.Packages.props b/Directory.Packages.props index 4a89e53..255f623 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,16 +9,16 @@ - - - - - - - - - - + + + + + + + + + + From ed8ae58773bde8e926a01c20099373482bf1c227 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Wed, 1 Jul 2026 21:47:32 +0200 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=94=A7=20restructure=20docfx=20build?= =?UTF-8?q?=20system=20for=20namespace=20and=20type=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separate namespace overwrite files (\.docfx/api/namespaces/**/*.md\) and type overwrite files (\.docfx/api/types/**/*.md\) in docfx.json build.overwrite config. Exclude both subdirectories from build.content to prevent treating overwrite Markdown as conceptual content. Update NGINX version to 1.31.2 for docs publishing container. --- .docfx/Dockerfile.docfx | 2 +- .docfx/docfx.json | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.docfx/Dockerfile.docfx b/.docfx/Dockerfile.docfx index 1719a33..2229c73 100644 --- a/.docfx/Dockerfile.docfx +++ b/.docfx/Dockerfile.docfx @@ -1,4 +1,4 @@ -ARG NGINX_VERSION=1.31.0-alpine +ARG NGINX_VERSION=1.31.2-alpine FROM --platform=$BUILDPLATFORM nginx:${NGINX_VERSION} AS base RUN rm -rf /usr/share/nginx/html/* diff --git a/.docfx/docfx.json b/.docfx/docfx.json index 1280593..eb90b55 100644 --- a/.docfx/docfx.json +++ b/.docfx/docfx.json @@ -73,11 +73,12 @@ { "files": [ "api/**/*.yml", - "api/**/*.md", "toc.yml", "*.md" ], "exclude": [ + "api/namespaces/**", + "api/types/**", "bin/**", "obj/**" ] @@ -115,7 +116,16 @@ "overwrite": [ { "files": [ - "api/namespaces/**.md" + "api/namespaces/**/*.md" + ], + "exclude": [ + "obj/**", + "wwwroot/**" + ] + }, + { + "files": [ + "api/types/**/*.md" ], "exclude": [ "obj/**", From 66fb259e2f7dfe3a54160cbc869dda114d03ae6a Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Wed, 1 Jul 2026 21:47:39 +0200 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=93=9D=20expand=20api=20namespace=20d?= =?UTF-8?q?ocumentation=20with=20examples=20and=20entry=20points?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive namespace overview pages for all public API namespaces with clear usage examples, entry points, and Extension Members tables that guide developers to key factories and registration methods. Improves discoverability and adoption of core CQRS, DDD, and command/query patterns. --- .../namespaces/Savvyio.Commands.Messaging.md | 15 +++++++++++++++ .docfx/api/namespaces/Savvyio.Commands.md | 10 +++++++++- .docfx/api/namespaces/Savvyio.Data.md | 4 +++- .docfx/api/namespaces/Savvyio.Diagnostics.md | 9 +++++++++ .docfx/api/namespaces/Savvyio.Dispatchers.md | 4 +++- .../namespaces/Savvyio.Domain.EventSourcing.md | 15 +++++++++++++++ .docfx/api/namespaces/Savvyio.Domain.md | 12 +++++++++++- ...Driven.Messaging.CloudEvents.Cryptography.md | 16 ++++++++++++++++ ...Savvyio.EventDriven.Messaging.CloudEvents.md | 15 +++++++++++++++ .../namespaces/Savvyio.EventDriven.Messaging.md | 15 +++++++++++++++ .docfx/api/namespaces/Savvyio.EventDriven.md | 11 ++++++++++- .../api/namespaces/Savvyio.Extensions.Dapper.md | 9 +++++++++ .../Savvyio.Extensions.DapperExtensions.md | 9 +++++++++ ...yio.Extensions.DependencyInjection.Dapper.md | 15 +++++++++++++++ ...ions.DependencyInjection.DapperExtensions.md | 15 +++++++++++++++ ...vvyio.Extensions.DependencyInjection.Data.md | 15 +++++++++++++++ ....DependencyInjection.Domain.EventSourcing.md | 15 +++++++++++++++ ...yio.Extensions.DependencyInjection.Domain.md | 15 +++++++++++++++ ...encyInjection.EFCore.Domain.EventSourcing.md | 15 +++++++++++++++ ...ensions.DependencyInjection.EFCore.Domain.md | 15 +++++++++++++++ ...yio.Extensions.DependencyInjection.EFCore.md | 15 +++++++++++++++ ....Extensions.DependencyInjection.Messaging.md | 15 +++++++++++++++ ...ensions.DependencyInjection.NATS.Commands.md | 9 +++++++++ ...ions.DependencyInjection.NATS.EventDriven.md | 9 +++++++++ ...vvyio.Extensions.DependencyInjection.NATS.md | 15 +++++++++++++++ ...sions.DependencyInjection.Newtonsoft.Json.md | 15 +++++++++++++++ ...DependencyInjection.QueueStorage.Commands.md | 9 +++++++++ ...endencyInjection.QueueStorage.EventDriven.md | 9 +++++++++ ...tensions.DependencyInjection.QueueStorage.md | 15 +++++++++++++++ ...ons.DependencyInjection.RabbitMQ.Commands.md | 9 +++++++++ ....DependencyInjection.RabbitMQ.EventDriven.md | 9 +++++++++ ...o.Extensions.DependencyInjection.RabbitMQ.md | 15 +++++++++++++++ ...encyInjection.SimpleQueueService.Commands.md | 9 +++++++++ ...yInjection.SimpleQueueService.EventDriven.md | 9 +++++++++ ...ns.DependencyInjection.SimpleQueueService.md | 15 +++++++++++++++ ....Extensions.DependencyInjection.Text.Json.md | 15 +++++++++++++++ .../Savvyio.Extensions.DependencyInjection.md | 16 ++++++++++++++++ ...io.Extensions.EFCore.Domain.EventSourcing.md | 17 +++++++++++++++++ .../Savvyio.Extensions.EFCore.Domain.md | 15 +++++++++++++++ .../api/namespaces/Savvyio.Extensions.EFCore.md | 9 +++++++++ .../Savvyio.Extensions.NATS.Commands.md | 9 +++++++++ .../Savvyio.Extensions.NATS.EventDriven.md | 9 +++++++++ .../api/namespaces/Savvyio.Extensions.NATS.md | 9 +++++++++ ...yio.Extensions.Newtonsoft.Json.Converters.md | 9 +++++++++ .../Savvyio.Extensions.Newtonsoft.Json.md | 16 ++++++++++++++++ .../Savvyio.Extensions.QueueStorage.Commands.md | 9 +++++++++ ...vvyio.Extensions.QueueStorage.EventDriven.md | 9 +++++++++ .../Savvyio.Extensions.QueueStorage.md | 9 +++++++++ .../Savvyio.Extensions.RabbitMQ.Commands.md | 9 +++++++++ .../Savvyio.Extensions.RabbitMQ.EventDriven.md | 9 +++++++++ .../namespaces/Savvyio.Extensions.RabbitMQ.md | 9 +++++++++ ...io.Extensions.SimpleQueueService.Commands.md | 9 +++++++++ ...Extensions.SimpleQueueService.EventDriven.md | 15 +++++++++++++++ .../Savvyio.Extensions.SimpleQueueService.md | 15 +++++++++++++++ .../Savvyio.Extensions.Text.Json.Converters.md | 9 +++++++++ .../namespaces/Savvyio.Extensions.Text.Json.md | 16 ++++++++++++++++ .docfx/api/namespaces/Savvyio.Extensions.md | 15 +++++++++++++++ .docfx/api/namespaces/Savvyio.Handlers.md | 10 ++++++---- .../Savvyio.Messaging.Cryptography.md | 16 ++++++++++++++++ .docfx/api/namespaces/Savvyio.Messaging.md | 10 +++++++++- .docfx/api/namespaces/Savvyio.Queries.md | 10 +++++++++- .docfx/api/namespaces/Savvyio.Reflection.md | 4 +++- .docfx/api/namespaces/Savvyio.md | 11 +++++++---- 63 files changed, 728 insertions(+), 16 deletions(-) create mode 100644 .docfx/api/namespaces/Savvyio.Commands.Messaging.md create mode 100644 .docfx/api/namespaces/Savvyio.Diagnostics.md create mode 100644 .docfx/api/namespaces/Savvyio.Domain.EventSourcing.md create mode 100644 .docfx/api/namespaces/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.md create mode 100644 .docfx/api/namespaces/Savvyio.EventDriven.Messaging.CloudEvents.md create mode 100644 .docfx/api/namespaces/Savvyio.EventDriven.Messaging.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.Dapper.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DapperExtensions.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Dapper.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.DapperExtensions.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Data.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Domain.EventSourcing.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Domain.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.Domain.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Messaging.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.Commands.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.Commands.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Text.Json.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.EFCore.Domain.EventSourcing.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.EFCore.Domain.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.EFCore.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.NATS.Commands.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.NATS.EventDriven.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.NATS.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.Newtonsoft.Json.Converters.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.Newtonsoft.Json.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.QueueStorage.Commands.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.QueueStorage.EventDriven.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.QueueStorage.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.Commands.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.EventDriven.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.Commands.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.EventDriven.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.Text.Json.Converters.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.Text.Json.md create mode 100644 .docfx/api/namespaces/Savvyio.Extensions.md create mode 100644 .docfx/api/namespaces/Savvyio.Messaging.Cryptography.md diff --git a/.docfx/api/namespaces/Savvyio.Commands.Messaging.md b/.docfx/api/namespaces/Savvyio.Commands.Messaging.md new file mode 100644 index 0000000..a3b9ad3 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Commands.Messaging.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Commands.Messaging +summary: *content +--- +Unlike in-process command dispatch through `CommandDispatcher`, commands sent to external services need a transport envelope. `CommandExtensions.ToMessage` wraps any `ICommand` in an `IMessage` envelope, and `InMemoryCommandQueue` replays that envelope in unit tests without a real broker. + +Start with `CommandExtensions.ToMessage`, hand the `IMessage` to a queue from one of the transport extension packages (NATS, RabbitMQ, Azure Queue Storage, Amazon SQS), and swap in `InMemoryCommandQueue` when you need to test the dispatch pipeline without infrastructure. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|T|⬇️|`ToMessage`| diff --git a/.docfx/api/namespaces/Savvyio.Commands.md b/.docfx/api/namespaces/Savvyio.Commands.md index 9c6f43f..cbbd07a 100644 --- a/.docfx/api/namespaces/Savvyio.Commands.md +++ b/.docfx/api/namespaces/Savvyio.Commands.md @@ -2,6 +2,14 @@ uid: Savvyio.Commands summary: *content --- -The `Savvyio.Commands` namespace holds all the abstractions and core types related to commands (C in Cqrs). +Use the `Savvyio.Commands` namespace to model and dispatch write-side operations in a CQRS application. A command represents intent to change state — implement `Command` as your base class when you want a concrete, serializable command payload, then register a `CommandHandler` to process it. + +Start with `Command` for your command payload classes. Register a handler that extends `CommandHandler`, and use `SavvyioOptions.AddCommandDispatcher` and `SavvyioOptions.AddCommandHandler` to wire them into the DI container. Route commands through `CommandDispatcher` or use the higher-level `Mediator` from `Savvyio.Extensions`. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|SavvyioOptions|⬇️|`AddCommandHandler`, `AddCommandDispatcher`| diff --git a/.docfx/api/namespaces/Savvyio.Data.md b/.docfx/api/namespaces/Savvyio.Data.md index 206b377..86ebf2e 100644 --- a/.docfx/api/namespaces/Savvyio.Data.md +++ b/.docfx/api/namespaces/Savvyio.Data.md @@ -2,6 +2,8 @@ uid: Savvyio.Data summary: *content --- -The `Savvyio.Data` namespace holds all the abstractions and core types related to data. +Use the `Savvyio.Data` namespace to define infrastructure-agnostic data access contracts. The interfaces here describe what your repositories and data stores must be able to do — read, write, delete, search, and persist — without tying your domain model to a specific database or ORM. + +Start with `IPersistentDataStore` for a full-lifecycle data store, or compose narrower contracts: `IReadableDataStore`, `IWritableDataStore`, `IDeletableDataStore`, and `ISearchableDataStore`. Implementations for Entity Framework Core and Dapper are available in their respective extension packages. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Diagnostics.md b/.docfx/api/namespaces/Savvyio.Diagnostics.md new file mode 100644 index 0000000..efd94fd --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Diagnostics.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Diagnostics +summary: *content +--- +Use the `Savvyio.Diagnostics` namespace to add health monitoring to a Savvy I/O application. `IHealthCheckProvider` and `IAsyncHealthCheckProvider` define synchronous and asynchronous health-check contracts that infrastructure components can implement to report their operational status. + +Start with `IAsyncHealthCheckProvider` when the health check involves I/O — for example, verifying that a database connection is available or that a message broker is reachable. Use `IHealthCheckProvider` for lightweight, synchronous checks. Both interfaces integrate with standard health check pipelines. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Dispatchers.md b/.docfx/api/namespaces/Savvyio.Dispatchers.md index af17cd6..2b85c15 100644 --- a/.docfx/api/namespaces/Savvyio.Dispatchers.md +++ b/.docfx/api/namespaces/Savvyio.Dispatchers.md @@ -2,6 +2,8 @@ uid: Savvyio.Dispatchers summary: *content --- -The `Savvyio.Dispatchers` namespace holds all the abstractions and core types related to dispatchers. +Use the `Savvyio.Dispatchers` namespace to route commands, queries, and events to their registered handlers. The dispatcher layer decouples the caller from the handler — the caller sends a request to the dispatcher and the framework locates the correct handler. + +Start with `FireForgetDispatcher` for commands and domain events (no return value expected) and `RequestReplyDispatcher` for queries that return a result. Both extend the base `Dispatcher` and use a `ServiceLocator` to resolve handlers from the DI container. For a unified entry point that routes all request types, use `Mediator` from the `Savvyio.Extensions` namespace. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Domain.EventSourcing.md b/.docfx/api/namespaces/Savvyio.Domain.EventSourcing.md new file mode 100644 index 0000000..cbc9768 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Domain.EventSourcing.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Domain.EventSourcing +summary: *content +--- +Instead of storing current aggregate state, event sourcing records every state-changing event and reconstructs the aggregate by replaying them. The `Savvyio.Domain.EventSourcing` namespace provides the base types that make this possible. + +Start with `TracedAggregateRoot` as the base class for aggregates whose history must be persisted. Each state change produces a `TracedDomainEvent` that carries the aggregate ID, version, member type, and the delta. Use the extension methods on `ITracedDomainEvent` to read and write aggregate version metadata. Persistence is provided by `Savvyio.Extensions.EFCore.Domain.EventSourcing`; DI registration is in `Savvyio.Extensions.DependencyInjection.Domain.EventSourcing`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|T|⬇️|`SetAggregateVersion`, `GetAggregateVersion`, `GetMemberType`| diff --git a/.docfx/api/namespaces/Savvyio.Domain.md b/.docfx/api/namespaces/Savvyio.Domain.md index 252670c..bb560ab 100644 --- a/.docfx/api/namespaces/Savvyio.Domain.md +++ b/.docfx/api/namespaces/Savvyio.Domain.md @@ -2,6 +2,16 @@ uid: Savvyio.Domain summary: *content --- -The `Savvyio.Domain` namespace holds all the abstractions and core types related to DDD. +Use the `Savvyio.Domain` namespace to model the core business domain using Domain-Driven Design: aggregate roots with encapsulated domain events, value objects with structural equality, entities with identity, and single-value objects for type-safe primitives. + +Start with `AggregateRoot` when your domain concept has a lifecycle and raises events. Use `Entity` for objects with identity that are governed by an aggregate, `ValueObject` for immutable equality by value, and `SingleValueObject` for primitives like identifiers or money amounts. Register a `DomainEventHandler` and configure dispatching with `SavvyioOptions.AddDomainEventDispatcher` and `AddDomainEventHandler`. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|T|⬇️|`GetEventId`, `GetTimestamp`| +|IDomainEventDispatcher|⬇️|`RaiseMany`, `RaiseManyAsync`| +|SavvyioOptions|⬇️|`AddDomainEventHandler`, `AddDomainEventDispatcher`| diff --git a/.docfx/api/namespaces/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.md b/.docfx/api/namespaces/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.md new file mode 100644 index 0000000..5ad460a --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.md @@ -0,0 +1,16 @@ +--- +uid: Savvyio.EventDriven.Messaging.CloudEvents.Cryptography +summary: *content +--- +Receivers of CloudEvents need cryptographic proof that the event has not been altered in transit. The `Savvyio.EventDriven.Messaging.CloudEvents.Cryptography` namespace provides signing and verification for `ICloudEvent` through `SignedCloudEvent`. + +Start with `CloudEventExtensions.SignCloudEvent` to produce a `SignedCloudEvent` with an attached signature. On the consumer side, call `SignedCloudEventExtensions.CheckCloudEventSignature` to verify authenticity before processing. Use this namespace alongside `Savvyio.EventDriven.Messaging.CloudEvents`; for plain message signing without CloudEvents, see `Savvyio.Messaging.Cryptography`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|ICloudEvent|⬇️|`SignCloudEvent`| +|ISignedCloudEvent|⬇️|`CheckCloudEventSignature`| diff --git a/.docfx/api/namespaces/Savvyio.EventDriven.Messaging.CloudEvents.md b/.docfx/api/namespaces/Savvyio.EventDriven.Messaging.CloudEvents.md new file mode 100644 index 0000000..63ed73f --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.EventDriven.Messaging.CloudEvents.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.EventDriven.Messaging.CloudEvents +summary: *content +--- +[CloudEvents](https://cloudevents.io/) is a CNCF standard for describing event data in a portable way. The `Savvyio.EventDriven.Messaging.CloudEvents` namespace adapts `IMessage` envelopes to this format, enabling interoperability with CloudEvents-aware brokers and consumers. + +Start with `MessageExtensions.ToCloudEvent` to convert an `IMessage` into a `CloudEvent`. Use this namespace when the receiving service expects the CloudEvents schema rather than the native Savvy I/O message format. To add a cryptographic signature to the cloud event, see `Savvyio.EventDriven.Messaging.CloudEvents.Cryptography`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IMessage|⬇️|`ToCloudEvent`| diff --git a/.docfx/api/namespaces/Savvyio.EventDriven.Messaging.md b/.docfx/api/namespaces/Savvyio.EventDriven.Messaging.md new file mode 100644 index 0000000..5893fc4 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.EventDriven.Messaging.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.EventDriven.Messaging +summary: *content +--- +Publishers wrap an integration event in an `IMessage` envelope using `IntegrationEventExtensions.ToMessage` before routing it to a broker. Subscribers unwrap the payload and dispatch the domain event. Start with `IntegrationEventExtensions.ToMessage` to produce the envelope from any `IIntegrationEvent`. + +Pass the `IMessage` to an event bus from one of the transport extension packages (NATS, RabbitMQ, Azure Queue Storage, Amazon SNS/SQS). `InMemoryEventBus` stands in during unit tests for any broker. Brokerless in-process dispatch goes through `IntegrationEventDispatcher` from `Savvyio.EventDriven`, which does not use a message envelope. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|T|⬇️|`ToMessage`| diff --git a/.docfx/api/namespaces/Savvyio.EventDriven.md b/.docfx/api/namespaces/Savvyio.EventDriven.md index 160067f..6f8a6a2 100644 --- a/.docfx/api/namespaces/Savvyio.EventDriven.md +++ b/.docfx/api/namespaces/Savvyio.EventDriven.md @@ -2,6 +2,15 @@ uid: Savvyio.EventDriven summary: *content --- -The `Savvyio.EventDriven` namespace holds all the abstractions and core types related to integration events. +Use the `Savvyio.EventDriven` namespace to define and dispatch integration events — messages that cross service boundaries and enable eventual consistency in a distributed system. An integration event announces that something has happened in one service so that other services can react. + +Start with `IntegrationEvent` as the base class for your cross-service event payloads. Register an `IntegrationEventHandler` and connect it to the DI container with `SavvyioOptions.AddIntegrationEventDispatcher` and `AddIntegrationEventHandler`. Use the extension methods on `IIntegrationEvent` to read the event ID, timestamp, and member type from the event's metadata. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|T|⬇️|`GetEventId`, `GetTimestamp`, `GetMemberType`| +|SavvyioOptions|⬇️|`AddIntegrationEventHandler`, `AddIntegrationEventDispatcher`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.Dapper.md b/.docfx/api/namespaces/Savvyio.Extensions.Dapper.md new file mode 100644 index 0000000..4782b15 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.Dapper.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.Dapper +summary: *content +--- +Use the `Savvyio.Extensions.Dapper` namespace to access data using Dapper — a lightweight micro-ORM that executes raw SQL and maps results to your domain objects. It provides `DapperDataSource` as the connection factory and `DapperDataStore` as the base class for Dapper-backed read and write data stores. + +Start with `DapperDataSource` to wrap a database connection factory that implements `IDapperDataSource`. Extend `DapperDataStore` to write your repository logic using Dapper's `Execute`, `Query`, and `QueryAsync` methods. Configure query options through `DapperQueryOptions`. Register with `Savvyio.Extensions.DependencyInjection.Dapper`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DapperExtensions.md b/.docfx/api/namespaces/Savvyio.Extensions.DapperExtensions.md new file mode 100644 index 0000000..a2012b3 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DapperExtensions.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.DapperExtensions +summary: *content +--- +DapperExtensions adds automatic CRUD mapping on top of Dapper. The `Savvyio.Extensions.DapperExtensions` namespace provides `DapperExtensionsDataStore` as the base class and `DapperExtensionsQueryOptions` for configuring query execution. + +Start with `DapperExtensionsDataStore` when you want auto-mapped CRUD without writing SQL for each operation. Configure query behavior through `DapperExtensionsQueryOptions`. Register with `Savvyio.Extensions.DependencyInjection.DapperExtensions`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Dapper.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Dapper.md new file mode 100644 index 0000000..a2ed133 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Dapper.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Dapper +summary: *content +--- +Dapper persistence needs both a connection factory and a data store. The `Savvyio.Extensions.DependencyInjection.Dapper` namespace provides `AddDapperDataSource` for the connection factory and `AddDapperDataStore` for each data store, combining both concerns in one namespace. + +Start with `AddDapperDataSource` to configure the connection and register `IDapperDataSource`. Then add `AddDapperDataStore` for each data store your application needs. Choose this namespace when you want lightweight, handwritten SQL via Dapper without an ORM; for convention-based automatic CRUD instead, prefer `Savvyio.Extensions.DependencyInjection.DapperExtensions`, and for EF Core, use `Savvyio.Extensions.DependencyInjection.EFCore`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddDapperDataSource`, `AddDapperDataSource`, `AddDapperDataSource`, `AddDapperDataStore`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.DapperExtensions.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.DapperExtensions.md new file mode 100644 index 0000000..756c621 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.DapperExtensions.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.DapperExtensions +summary: *content +--- +DapperExtensions adds automatic CRUD on top of Dapper. The `Savvyio.Extensions.DependencyInjection.DapperExtensions` namespace provides `AddDapperExtensionsDataStore` and `AddDapperExtensionsDataStore` to register the `DapperExtensionsDataStore` implementation from `Savvyio.Extensions.DapperExtensions`. + +Start with `AddDapperExtensionsDataStore` to bind a data store interface to its DapperExtensions implementation. Choose this namespace when you want automatic CRUD without writing SQL statements — DapperExtensions infers INSERT/UPDATE/DELETE/SELECT from class mapping conventions. For handwritten SQL, use `Savvyio.Extensions.DependencyInjection.Dapper` instead. Combine with `Savvyio.Extensions.DependencyInjection.Dapper` for the shared connection factory. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddDapperExtensionsDataStore`, `AddDapperExtensionsDataStore`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Data.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Data.md new file mode 100644 index 0000000..85ce472 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Data.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Data +summary: *content +--- +Registering data stores without tying to a technology keeps the domain model portable. The `Savvyio.Extensions.DependencyInjection.Data` namespace provides `AddDataStore` and `AddDataStore` for this purpose. + +Start with `AddDataStore` to bind an `IDataStore` interface to its implementation. Choose this namespace when your domain layer depends only on the abstract `IDataStore` interface family and you want the concrete implementation resolved by DI without referencing a specific ORM or data-access library. For technology-specific registrations that also configure a connection or context, see `Savvyio.Extensions.DependencyInjection.EFCore`, `Savvyio.Extensions.DependencyInjection.Dapper`, or `Savvyio.Extensions.DependencyInjection.DapperExtensions`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddDataStore`, `AddDataStore`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Domain.EventSourcing.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Domain.EventSourcing.md new file mode 100644 index 0000000..710fc4f --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Domain.EventSourcing.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Domain.EventSourcing +summary: *content +--- +Event-sourced aggregates require a specialized repository that stores and replays traced domain events rather than current state. The `Savvyio.Extensions.DependencyInjection.Domain.EventSourcing` namespace provides `AddTracedAggregateRepository` and the `ITracedAggregateRepository` marker interface. + +Start with `AddTracedAggregateRepository` to bind an `ITracedAggregateRepository` to its implementation. For the EF Core–backed implementation, use `Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddTracedAggregateRepository`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Domain.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Domain.md new file mode 100644 index 0000000..8ef01d1 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Domain.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Domain +summary: *content +--- +Registering DDD aggregate repositories in the DI container is done through this namespace. It provides `AddAggregateRepository`, `AddRepository`, and `AddUnitOfWork` for binding aggregate and read-model repository contracts to their implementations. + +Start with `AddAggregateRepository` to register the primary aggregate repository. Use `AddUnitOfWork` when your domain layer requires a unit-of-work coordinator. For event-sourced aggregates, add `Savvyio.Extensions.DependencyInjection.Domain.EventSourcing`. For EF Core–backed implementations, prefer `Savvyio.Extensions.DependencyInjection.EFCore.Domain`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddAggregateRepository`, `AddRepository`, `AddUnitOfWork`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.md new file mode 100644 index 0000000..31c2b53 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing +summary: *content +--- +Event sourcing with EF Core requires a dedicated repository that writes traced domain events. The `Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing` namespace provides `AddEfCoreTracedAggregateRepository` and `AddEfCoreTracedAggregateRepository` to register this repository. + +Start with `AddEfCoreTracedAggregateRepository` to bind `ITracedAggregateRepository` to `EfCoreTracedAggregateRepository`. Choose this namespace when your domain uses event sourcing and stores aggregate history as a sequence of immutable traced events in a relational database; for standard non-event-sourced aggregates with EF Core, use `Savvyio.Extensions.DependencyInjection.EFCore.Domain` instead. Pair with `Savvyio.Extensions.EFCore.Domain.EventSourcing` which provides the `ModelBuilder` extension to create the event-store schema in `OnModelCreating`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddEfCoreTracedAggregateRepository`, `AddEfCoreTracedAggregateRepository`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.Domain.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.Domain.md new file mode 100644 index 0000000..32bf322 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.Domain.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.Domain +summary: *content +--- +Aggregate roots require their own context boundary. The `Savvyio.Extensions.DependencyInjection.EFCore.Domain` namespace provides `AddEfCoreAggregateDataSource` and `AddEfCoreAggregateRepository` to register EF Core persistence for aggregate roots. + +Start with `AddEfCoreAggregateDataSource` to configure the `DbContext` as a domain data source. Then add `AddEfCoreAggregateRepository` for each aggregate root type. For event-sourced aggregates, use `Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddEfCoreAggregateDataSource`, `AddEfCoreAggregateDataSource`, `AddEfCoreAggregateRepository`, `AddEfCoreAggregateRepository`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.md new file mode 100644 index 0000000..8fee867 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.EFCore.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore +summary: *content +--- +One DI call to configure both the `DbContext` and the Savvy I/O data layer is the goal of this namespace. `AddEfCoreDataSource`, `AddEfCoreDataStore`, and `AddEfCoreRepository` wire up Entity Framework Core persistence without boilerplate. + +Start with `AddEfCoreDataSource` to register the `DbContext` and the `IEfCoreDataSource`. Then add `AddEfCoreRepository` or `AddEfCoreDataStore` for each repository or data store. For domain aggregates with explicit boundaries, prefer `Savvyio.Extensions.DependencyInjection.EFCore.Domain`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddEfCoreDataSource`, `AddEfCoreDataSource`, `AddEfCoreDataStore`, `AddEfCoreDataStore`, `AddEfCoreRepository`, `AddEfCoreRepository`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Messaging.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Messaging.md new file mode 100644 index 0000000..867f50f --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Messaging.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Messaging +summary: *content +--- +Messaging infrastructure belongs behind an abstraction. The `Savvyio.Extensions.DependencyInjection.Messaging` namespace provides `AddMessageQueue` and `AddMessageBus` to bind the abstract messaging interfaces to concrete implementations without tying the domain layer to a specific broker technology. + +Start with `AddMessageBus` to register an event bus, or `AddMessageQueue` to register a command queue. For broker-specific registration helpers (NATS, RabbitMQ, Azure Queue Storage, Amazon SQS), use the corresponding `Savvyio.Extensions.DependencyInjection.*` namespace. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddMessageQueue`, `AddMessageBus`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.Commands.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.Commands.md new file mode 100644 index 0000000..e81d837 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.Commands.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.NATS.Commands +summary: *content +--- +The concrete NATS command-queue types used during DI registration live in this namespace. `NatsCommandQueue` is the registered implementation and `NatsCommandQueueOptions` carries the NATS subject, connection, and serialization settings. + +Start with `NatsCommandQueue` when you need to inspect or extend the registered implementation type. Configure its options through `Savvyio.Extensions.DependencyInjection.NATS.AddNatsCommandQueue` rather than instantiating these types directly. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.md new file mode 100644 index 0000000..f6fc3fd --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.NATS.EventDriven +summary: *content +--- +The concrete NATS event-bus types used during DI registration live in this namespace. `NatsEventBus` is the registered implementation and `NatsEventBusOptions` carries the NATS subject, connection, and serialization settings. + +Start with `NatsEventBus` when you need to inspect or extend the registered implementation type. Configure its options through `Savvyio.Extensions.DependencyInjection.NATS.AddNatsEventBus` rather than instantiating these types directly. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.md new file mode 100644 index 0000000..356fdfa --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.NATS.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.NATS +summary: *content +--- +NATS messaging for Savvy I/O is registered in one call per channel. The `Savvyio.Extensions.DependencyInjection.NATS` namespace provides `AddNatsCommandQueue` and `AddNatsEventBus` to configure the NATS connection and register the corresponding service. + +Start with `AddNatsCommandQueue` to register a NATS command queue, and `AddNatsEventBus` for an event bus. Choose this namespace when your application uses NATS as the message broker for low-latency command and event delivery; for RabbitMQ, Azure Queue Storage, or Amazon SQS, see the corresponding `Savvyio.Extensions.DependencyInjection.*` namespace. The concrete implementation types and their options are in `Savvyio.Extensions.DependencyInjection.NATS.Commands` and `Savvyio.Extensions.DependencyInjection.NATS.EventDriven`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddNatsCommandQueue`, `AddNatsCommandQueue`, `AddNatsEventBus`, `AddNatsEventBus`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json.md new file mode 100644 index 0000000..1483cb0 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Newtonsoft.Json +summary: *content +--- +Serializing commands and events with Newtonsoft.Json requires a single registration call. The `Savvyio.Extensions.DependencyInjection.Newtonsoft.Json` namespace provides `AddNewtonsoftJsonMarshaller` to register `NewtonsoftJsonMarshaller` as the `IMarshaller` used throughout the dispatch pipeline. + +Start with `AddNewtonsoftJsonMarshaller` to configure the serializer settings and converters in one step. To use System.Text.Json instead, replace this with `Savvyio.Extensions.DependencyInjection.Text.Json`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddNewtonsoftJsonMarshaller`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.Commands.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.Commands.md new file mode 100644 index 0000000..b36f0f1 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.Commands.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.QueueStorage.Commands +summary: *content +--- +The concrete Azure Queue Storage command-queue type used during DI registration lives in this namespace. `AzureCommandQueue` enqueues serialized commands to an Azure Storage queue. + +Start with `AzureCommandQueue` when you need to inspect or extend the registered implementation type. Configure it through `Savvyio.Extensions.DependencyInjection.QueueStorage.AddAzureCommandQueue` rather than instantiating it directly. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.md new file mode 100644 index 0000000..7bd29b8 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven +summary: *content +--- +The concrete Azure Queue Storage event-bus type and its options used during DI registration live in this namespace. `AzureEventBus` publishes and receives integration events via Azure Queue Storage. `AzureEventBusOptions` carries the queue name and serialization settings. + +Start with `AzureEventBus` when you need to inspect or extend the registered implementation type. Configure it through `Savvyio.Extensions.DependencyInjection.QueueStorage.AddAzureEventBus` rather than instantiating it directly. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.md new file mode 100644 index 0000000..c1bff78 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.QueueStorage.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.QueueStorage +summary: *content +--- +Azure Queue Storage messaging for Savvy I/O is registered in one call per channel. The `Savvyio.Extensions.DependencyInjection.QueueStorage` namespace provides `AddAzureCommandQueue` and `AddAzureEventBus` to configure the Azure Storage connection and register the corresponding service. + +Start with `AddAzureCommandQueue` to register an Azure command queue, and `AddAzureEventBus` for an event bus. Choose this namespace when your application targets Azure and you want cost-effective, serverless message delivery through Azure Queue Storage; for higher-throughput or broker-based messaging, consider `Savvyio.Extensions.DependencyInjection.RabbitMQ` or `Savvyio.Extensions.DependencyInjection.NATS`. The concrete types are in `Savvyio.Extensions.DependencyInjection.QueueStorage.Commands` and `Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddAzureCommandQueue`, `AddAzureCommandQueue`, `AddAzureEventBus`, `AddAzureEventBus`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.md new file mode 100644 index 0000000..e644ddf --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands +summary: *content +--- +The concrete RabbitMQ command-queue type and its options used during DI registration live in this namespace. `RabbitMqCommandQueue` sends serialized commands to a RabbitMQ exchange. `RabbitMqCommandQueueOptions` carries the exchange, routing key, and connection settings. + +Start with `RabbitMqCommandQueue` when you need to inspect or extend the registered implementation. Configure its options through `Savvyio.Extensions.DependencyInjection.RabbitMQ.AddRabbitMqCommandQueue` rather than instantiating it directly. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.md new file mode 100644 index 0000000..e6971d6 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven +summary: *content +--- +The concrete RabbitMQ event-bus type and its options used during DI registration live in this namespace. `RabbitMqEventBus` publishes and receives integration events via RabbitMQ exchanges. `RabbitMqEventBusOptions` carries the exchange, routing key, and connection settings. + +Start with `RabbitMqEventBus` when you need to inspect or extend the registered implementation. Configure its options through `Savvyio.Extensions.DependencyInjection.RabbitMQ.AddRabbitMqEventBus` rather than instantiating it directly. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.md new file mode 100644 index 0000000..658dae7 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.RabbitMQ.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.RabbitMQ +summary: *content +--- +RabbitMQ messaging for Savvy I/O is registered in one call per channel. The `Savvyio.Extensions.DependencyInjection.RabbitMQ` namespace provides `AddRabbitMqCommandQueue` and `AddRabbitMqEventBus` to configure the RabbitMQ connection, exchange, and queue settings and register the corresponding service. + +Start with `AddRabbitMqCommandQueue` to register a RabbitMQ command queue, and `AddRabbitMqEventBus` for an event bus. Choose this namespace when your application uses RabbitMQ as the broker for durable, broker-managed message delivery with flexible exchange routing; for lighter-weight or cloud-native alternatives, see `Savvyio.Extensions.DependencyInjection.NATS`, `Savvyio.Extensions.DependencyInjection.QueueStorage`, or `Savvyio.Extensions.DependencyInjection.SimpleQueueService`. The concrete types are in `Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands` and `Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddRabbitMqCommandQueue`, `AddRabbitMqCommandQueue`, `AddRabbitMqEventBus`, `AddRabbitMqEventBus`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.md new file mode 100644 index 0000000..9cc13b6 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands +summary: *content +--- +The concrete Amazon SQS command-queue type and its options used during DI registration live in this namespace. `AmazonCommandQueue` sends serialized commands to an SQS queue. `AmazonCommandQueueOptions` carries the queue URL, AWS credentials, and serialization settings. + +Start with `AmazonCommandQueue` when you need to inspect or extend the registered implementation. Configure its options through `Savvyio.Extensions.DependencyInjection.SimpleQueueService.AddAmazonCommandQueue` rather than instantiating it directly. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.md new file mode 100644 index 0000000..87bb074 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven +summary: *content +--- +The concrete Amazon SNS/SQS event-bus type and its options used during DI registration live in this namespace. `AmazonEventBus` publishes to SNS and receives via SQS subscriptions. `AmazonEventBusOptions` carries the topic ARN, queue URL, AWS credentials, and serialization settings. + +Start with `AmazonEventBus` when you need to inspect or extend the registered implementation. Configure its options through `Savvyio.Extensions.DependencyInjection.SimpleQueueService.AddAmazonEventBus` rather than instantiating it directly. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.md new file mode 100644 index 0000000..742c05a --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.SimpleQueueService.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.SimpleQueueService +summary: *content +--- +Amazon SQS/SNS messaging for Savvy I/O is registered in one call per channel. The `Savvyio.Extensions.DependencyInjection.SimpleQueueService` namespace provides `AddAmazonCommandQueue` and `AddAmazonEventBus` to configure the AWS credentials, region, and resource names and register the corresponding service. + +Start with `AddAmazonCommandQueue` to register an SQS command queue, and `AddAmazonEventBus` for an SNS/SQS event bus. Choose this namespace when your application runs on AWS and requires SQS-based command queuing or SNS/SQS fan-out for integration events; for Azure, use `Savvyio.Extensions.DependencyInjection.QueueStorage`, and for self-hosted brokers, consider `Savvyio.Extensions.DependencyInjection.RabbitMQ` or `Savvyio.Extensions.DependencyInjection.NATS`. The concrete types are in `Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands` and `Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddAmazonCommandQueue`, `AddAmazonCommandQueue`, `AddAmazonEventBus`, `AddAmazonEventBus`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Text.Json.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Text.Json.md new file mode 100644 index 0000000..474b84f --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.Text.Json.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Text.Json +summary: *content +--- +Serializing commands and events with System.Text.Json requires a single registration call. The `Savvyio.Extensions.DependencyInjection.Text.Json` namespace provides `AddJsonMarshaller` to register `JsonMarshaller` as the `IMarshaller` used throughout the dispatch pipeline. + +Start with `AddJsonMarshaller` to configure the `JsonSerializerOptions` and converters in one step. To use Newtonsoft.Json instead, replace this with `Savvyio.Extensions.DependencyInjection.Newtonsoft.Json`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddJsonMarshaller`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.md b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.md new file mode 100644 index 0000000..f374e5f --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.DependencyInjection.md @@ -0,0 +1,16 @@ +--- +uid: Savvyio.Extensions.DependencyInjection +summary: *content +--- +At host startup, `AddSavvyIO` creates the registration graph that ties together handlers, dispatchers, marshallers, data sources, and the service locator — all in one fluent call. Use this namespace in every Savvy I/O application that runs under Microsoft's DI container. + +`AddHandlerServicesDescriptor` enables handler auto-discovery; `AddDataSource` registers your data access layer; `AddMarshaller` sets the serialization strategy; `AddServiceLocator` exposes `IServiceLocator`. After startup, call `IServiceProvider.WriteHandlerDiscoveriesToLog` to audit which handlers were resolved. Start with `AddSavvyIO` and compose the sub-namespaces — `Savvyio.Extensions.DependencyInjection.EFCore`, `Savvyio.Extensions.DependencyInjection.NATS`, and their siblings — to add broker and persistence-specific registrations. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddSavvyIO`, `AddConfiguredOptions`, `AddMarshaller`, `AddDataSource`, `AddServiceLocator`, `AddHandlerServicesDescriptor`| +|IServiceProvider|⬇️|`WriteHandlerDiscoveriesToLog`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.EFCore.Domain.EventSourcing.md b/.docfx/api/namespaces/Savvyio.Extensions.EFCore.Domain.EventSourcing.md new file mode 100644 index 0000000..88da94a --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.EFCore.Domain.EventSourcing.md @@ -0,0 +1,17 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain.EventSourcing +summary: *content +--- +EF Core–backed event sourcing requires both a schema setup and a repository that writes individual event rows. The `Savvyio.Extensions.EFCore.Domain.EventSourcing` namespace provides all three: the EF Core entity, the repository, and the `ModelBuilder` extension that creates the event-store table. + +Start with `ModelBuilderExtensions.AddEventSourcing` in `OnModelCreating` to create the event table. Then use `EfCoreTracedAggregateRepository` as the aggregate repository. Use `EfCoreTracedAggregateEntityExtensions.ToTracedDomainEvent` to hydrate domain events from stored rows, and `TracedDomainEventExtensions.ToByteArray` to serialize a traced event for storage. Register with `Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|EfCoreTracedAggregateEntity|⬇️|`ToTracedDomainEvent`| +|ITracedDomainEvent|⬇️|`ToByteArray`| +|ModelBuilder|⬇️|`AddEventSourcing`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.EFCore.Domain.md b/.docfx/api/namespaces/Savvyio.Extensions.EFCore.Domain.md new file mode 100644 index 0000000..b7a4312 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.EFCore.Domain.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain +summary: *content +--- +Aggregate roots require their own persistence boundary in DDD. The `Savvyio.Extensions.EFCore.Domain` namespace provides `EfCoreAggregateRepository` and `EfCoreAggregateDataSource` for EF Core–backed aggregate persistence, and `DomainEventDispatcherExtensions` to dispatch accumulated domain events after the aggregate is saved. + +Start with `EfCoreAggregateRepository` as the base class for aggregate root repositories. Extend it and inject `IDomainEventDispatcher`, then call `RaiseManyAsync` after saving to dispatch accumulated domain events. Register with `Savvyio.Extensions.DependencyInjection.EFCore.Domain`. For event-sourced aggregates, use `Savvyio.Extensions.EFCore.Domain.EventSourcing`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IDomainEventDispatcher|⬇️|`RaiseMany`, `RaiseManyAsync`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.EFCore.md b/.docfx/api/namespaces/Savvyio.Extensions.EFCore.md new file mode 100644 index 0000000..c1b2ffd --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.EFCore.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.EFCore +summary: *content +--- +Use the `Savvyio.Extensions.EFCore` namespace for the core Entity Framework Core integration types. It provides `EfCoreDbContext`, `EfCoreDataSource`, `EfCoreDataStore`, and `EfCoreRepository` — the building blocks for infrastructure-layer persistence that connects EF Core to the Savvy I/O data access abstractions in `Savvyio.Data`. + +Start with `EfCoreDataSource` as the base class when you want a typed `DbContext` factory that implements `IEfCoreDataSource`. Extend `EfCoreDataStore` for general data store operations, or `EfCoreRepository` for aggregate root–scoped repositories. Register these with `Savvyio.Extensions.DependencyInjection.EFCore`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.NATS.Commands.md b/.docfx/api/namespaces/Savvyio.Extensions.NATS.Commands.md new file mode 100644 index 0000000..90c7bad --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.NATS.Commands.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.NATS.Commands +summary: *content +--- +Savvy I/O commands delivered via NATS are handled by this namespace. `NatsCommandQueue` implements `ICommandQueue` by publishing serialized commands to a NATS subject and consuming them via a subscription. `NatsCommandQueueOptions` configures the subject, connection, and serialization. + +Start with `NatsCommandQueue` as the implementation class for NATS command delivery. Choose this namespace when you need the concrete NATS command-queue type to extend, test, or configure directly; for DI-based registration without touching the implementation type, use `Savvyio.Extensions.DependencyInjection.NATS.AddNatsCommandQueue`. For the corresponding event bus, see `Savvyio.Extensions.NATS.EventDriven`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.NATS.EventDriven.md b/.docfx/api/namespaces/Savvyio.Extensions.NATS.EventDriven.md new file mode 100644 index 0000000..9e07efa --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.NATS.EventDriven.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.NATS.EventDriven +summary: *content +--- +Savvy I/O integration events published via NATS are handled by this namespace. `NatsEventBus` implements `IEventBus` by publishing serialized integration events to a NATS subject and subscribing to receive them. `NatsEventBusOptions` configures the subject, connection, and serialization. + +Start with `NatsEventBus` as the implementation class for NATS event delivery. Choose this namespace when you need the concrete NATS event-bus type to extend, test, or configure directly; for DI-based registration, use `Savvyio.Extensions.DependencyInjection.NATS.AddNatsEventBus`. For the corresponding command queue, see `Savvyio.Extensions.NATS.Commands`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.NATS.md b/.docfx/api/namespaces/Savvyio.Extensions.NATS.md new file mode 100644 index 0000000..91058c0 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.NATS.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.NATS +summary: *content +--- +Use the `Savvyio.Extensions.NATS` namespace for the core NATS messaging types. It provides `NatsMessage` as the NATS-specific message envelope and `NatsMessageOptions` for configuring subject, connection, and serialization settings shared by both the command queue and event bus. + +These base types are used by `Savvyio.Extensions.NATS.Commands` and `Savvyio.Extensions.NATS.EventDriven`. Registering with the DI container is handled by `Savvyio.Extensions.DependencyInjection.NATS`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.Newtonsoft.Json.Converters.md b/.docfx/api/namespaces/Savvyio.Extensions.Newtonsoft.Json.Converters.md new file mode 100644 index 0000000..0264e07 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.Newtonsoft.Json.Converters.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.Newtonsoft.Json.Converters +summary: *content +--- +`AggregateRootConverter`, `MessageConverter`, `RequestConverter`, `SingleValueObjectConverter`, and `ValueObjectConverter` — these five concrete `JsonConverter` types handle Savvy I/O domain-model serialization for Newtonsoft.Json. Each targets one base-type contract and can be composed with third-party converters or subclassed when custom serialization behavior is needed. + +Start with `MessageConverter` because `IMessage` is the outermost envelope for command and event payloads. Add the remaining converters as the domain model requires. For most applications, the extension methods in `Savvyio.Extensions.Newtonsoft.Json` register the needed subset in fewer lines; reference individual converter types here only when you need to subclass or override one. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.Newtonsoft.Json.md b/.docfx/api/namespaces/Savvyio.Extensions.Newtonsoft.Json.md new file mode 100644 index 0000000..4b5a5ff --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.Newtonsoft.Json.md @@ -0,0 +1,16 @@ +--- +uid: Savvyio.Extensions.Newtonsoft.Json +summary: *content +--- +Round-tripping Savvy I/O domain types — `IMessage`, aggregate roots, value objects, and requests — through Newtonsoft.Json requires converters that understand each type's custom serialization contract. This namespace contains `NewtonsoftJsonMarshaller`, `JsonConverterExtensions`, and `JsonSerializerExtensions` for that purpose. + +`JsonConverterExtensions` is the registration API: `AddMessageConverter()` and `AddMetadataDictionaryConverter()` cover messaging types, while `AddAggregateRootConverter`, `AddValueObjectConverter`, `AddSingleValueObjectConverter`, and `AddRequestConverter` cover domain-model types. `JsonSerializerExtensions.ResolvePropertyKeyByConvention` and `ResolveDictionaryKeyByConvention` align key naming with the Savvy I/O conventions. Prefer this namespace when the application already depends on Newtonsoft.Json or requires features not available in System.Text.Json; for greenfield apps, `Savvyio.Extensions.Text.Json` requires no additional package. Register the marshaller via `Savvyio.Extensions.DependencyInjection.Newtonsoft.Json`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|ICollection|⬇️|`AddValueObjectConverter`, `AddAggregateRootConverter`, `AddMetadataDictionaryConverter`, `AddRequestConverter`, `AddMessageConverter`, `AddSingleValueObjectConverter`| +|JsonSerializer|⬇️|`ResolvePropertyKeyByConvention`, `ResolveDictionaryKeyByConvention`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.QueueStorage.Commands.md b/.docfx/api/namespaces/Savvyio.Extensions.QueueStorage.Commands.md new file mode 100644 index 0000000..349b48d --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.QueueStorage.Commands.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.QueueStorage.Commands +summary: *content +--- +Use the `Savvyio.Extensions.QueueStorage.Commands` namespace to send and receive commands through Azure Queue Storage. `AzureCommandQueue` implements `ICommandQueue` by serializing commands and enqueuing them to an Azure Storage queue, then dequeuing and deserializing them on the consumer side. + +Register with `Savvyio.Extensions.DependencyInjection.QueueStorage.AddAzureCommandQueue`. For the corresponding event bus, see `Savvyio.Extensions.QueueStorage.EventDriven`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.QueueStorage.EventDriven.md b/.docfx/api/namespaces/Savvyio.Extensions.QueueStorage.EventDriven.md new file mode 100644 index 0000000..51f799c --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.QueueStorage.EventDriven.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.QueueStorage.EventDriven +summary: *content +--- +Savvy I/O integration events delivered via Azure Queue Storage are handled by this namespace. `AzureEventBus` implements `IEventBus` by serializing integration events and enqueuing/dequeuing them via Azure Storage. `AzureEventBusOptions` configures the queue name and serialization. + +Start with `AzureEventBus` as the implementation class for Azure Queue Storage event delivery. Choose this namespace when you need the concrete event-bus type to extend, test, or configure directly; for DI-based registration, use `Savvyio.Extensions.DependencyInjection.QueueStorage.AddAzureEventBus`. For the corresponding command queue, see `Savvyio.Extensions.QueueStorage.Commands`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.QueueStorage.md b/.docfx/api/namespaces/Savvyio.Extensions.QueueStorage.md new file mode 100644 index 0000000..962ae84 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.QueueStorage.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.QueueStorage +summary: *content +--- +Azure Queue Storage support in Savvy I/O is organized around the generic `AzureQueue` base class. The `Savvyio.Extensions.QueueStorage` namespace provides that base class, the shared `AzureQueueOptions`, and the send/receive options for fine-grained control. + +Start with `AzureQueueOptions` to configure the storage account connection string and queue name. Choose this namespace when you need to extend or customize the Azure Queue Storage base classes directly; for DI-based registration without dealing with base classes, use `Savvyio.Extensions.DependencyInjection.QueueStorage` instead. The concrete command queue is in `Savvyio.Extensions.QueueStorage.Commands` and the event bus in `Savvyio.Extensions.QueueStorage.EventDriven`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.Commands.md b/.docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.Commands.md new file mode 100644 index 0000000..3b14a44 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.Commands.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.RabbitMQ.Commands +summary: *content +--- +Savvy I/O commands delivered via RabbitMQ are handled by this namespace. `RabbitMqCommandQueue` implements `ICommandQueue` by publishing serialized commands to a RabbitMQ exchange and consuming them from a bound queue. `RabbitMqCommandQueueOptions` configures the exchange, routing key, and connection. + +Start with `RabbitMqCommandQueue` as the implementation class for RabbitMQ command delivery. Choose this namespace when you need the concrete command-queue type to extend, test, or configure directly; for DI-based registration, use `Savvyio.Extensions.DependencyInjection.RabbitMQ.AddRabbitMqCommandQueue`. For the corresponding event bus, see `Savvyio.Extensions.RabbitMQ.EventDriven`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.EventDriven.md b/.docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.EventDriven.md new file mode 100644 index 0000000..f3ddb69 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.EventDriven.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.RabbitMQ.EventDriven +summary: *content +--- +Savvy I/O integration events published via RabbitMQ are handled by this namespace. `RabbitMqEventBus` implements `IEventBus` by publishing integration events to a RabbitMQ exchange and consuming them from bound queues. `RabbitMqEventBusOptions` configures the exchange, routing key, and connection. + +Start with `RabbitMqEventBus` as the implementation class for RabbitMQ event delivery. Choose this namespace when you need the concrete event-bus type to extend, test, or configure directly; for DI-based registration, use `Savvyio.Extensions.DependencyInjection.RabbitMQ.AddRabbitMqEventBus`. For the corresponding command queue, see `Savvyio.Extensions.RabbitMQ.Commands`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.md b/.docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.md new file mode 100644 index 0000000..faaa96e --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.RabbitMQ.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.RabbitMQ +summary: *content +--- +Use the `Savvyio.Extensions.RabbitMQ` namespace for the core RabbitMQ integration types. It provides `RabbitMqMessage` as the RabbitMQ message envelope and `RabbitMqMessageOptions` for configuring exchange, routing key, connection, and serialization settings shared by both the command queue and event bus. + +The concrete command queue is in `Savvyio.Extensions.RabbitMQ.Commands` and the event bus in `Savvyio.Extensions.RabbitMQ.EventDriven`. Register both with `Savvyio.Extensions.DependencyInjection.RabbitMQ`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.Commands.md b/.docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.Commands.md new file mode 100644 index 0000000..31e9572 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.Commands.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.Commands +summary: *content +--- +Savvy I/O commands delivered via Amazon SQS are handled by this namespace. `AmazonCommandQueue` implements `ICommandQueue` by sending serialized commands to an SQS queue and polling to receive them. `AmazonCommandQueueOptions` configures the queue URL, AWS credentials, and serialization. + +Start with `AmazonCommandQueue` as the implementation class for SQS command delivery. Choose this namespace when you need the concrete SQS command-queue type to extend, test, or configure directly; for DI-based registration, use `Savvyio.Extensions.DependencyInjection.SimpleQueueService.AddAmazonCommandQueue`. For the corresponding SNS event bus, see `Savvyio.Extensions.SimpleQueueService.EventDriven`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.EventDriven.md b/.docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.EventDriven.md new file mode 100644 index 0000000..a672baf --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.EventDriven.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.EventDriven +summary: *content +--- +Savvy I/O integration events published via Amazon SNS/SQS are handled by this namespace. `AmazonEventBus` implements `IEventBus` by publishing to an SNS topic and polling the subscribed SQS queue for received events. `AmazonEventBusOptions` configures the SNS topic ARN, SQS queue URL, AWS credentials, and serialization. + +Start with `AmazonEventBus` as the implementation class for SNS/SQS event delivery. Choose this namespace when you need the concrete event-bus type to extend, test, or configure directly; for DI-based registration, use `Savvyio.Extensions.DependencyInjection.SimpleQueueService.AddAmazonEventBus`. `StringExtensions.ToSnsUri` converts a topic ARN to an SNS-compatible `Uri`. For the corresponding SQS command queue, see `Savvyio.Extensions.SimpleQueueService.Commands`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|string|⬇️|`ToSnsUri`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.md b/.docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.md new file mode 100644 index 0000000..5106ff5 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.SimpleQueueService.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService +summary: *content +--- +Amazon SQS/SNS support in Savvy I/O is organized around the `AmazonQueue` and `AmazonBus` base classes. The `Savvyio.Extensions.SimpleQueueService` namespace provides those base classes, the `AmazonMessage` envelope, and options types for configuring AWS credentials, region, and resource names. + +Start with `AmazonMessageOptions` to configure the SQS queue URL or SNS topic ARN along with AWS credentials and region. The concrete command queue is in `Savvyio.Extensions.SimpleQueueService.Commands` and the event bus in `Savvyio.Extensions.SimpleQueueService.EventDriven`. Register both with `Savvyio.Extensions.DependencyInjection.SimpleQueueService`. `ClientConfigExtensions` provides helpers for validating the AWS client configuration before use. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IClientConfig|⬇️|`IsValid`, `SimpleQueueService`, `SimpleNotificationService`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.Text.Json.Converters.md b/.docfx/api/namespaces/Savvyio.Extensions.Text.Json.Converters.md new file mode 100644 index 0000000..1785b36 --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.Text.Json.Converters.md @@ -0,0 +1,9 @@ +--- +uid: Savvyio.Extensions.Text.Json.Converters +summary: *content +--- +Each converter type in this namespace handles a specific Savvy I/O type: `MessageConverter` for `IMessage`, `MetadataDictionaryConverter` for metadata dictionaries, `DateTimeConverter` and `DateTimeOffsetConverter` for temporal values, `RequestConverter` for `IRequest`, and `SingleValueObjectConverter` for primitive value objects. Each is a concrete `JsonConverter` that you can add directly to `JsonSerializerOptions.Converters`. + +Start with `MessageConverter` and `MetadataDictionaryConverter` for messaging types. Choose the individual converter classes from this namespace when you need explicit control over which converters are active or when you need to compose them with other `JsonConverter` implementations; for one-call registration of all needed converters, use the extension methods in `Savvyio.Extensions.Text.Json` instead. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.Extensions.Text.Json.md b/.docfx/api/namespaces/Savvyio.Extensions.Text.Json.md new file mode 100644 index 0000000..32e306b --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.Text.Json.md @@ -0,0 +1,16 @@ +--- +uid: Savvyio.Extensions.Text.Json +summary: *content +--- +System.Text.Json is the modern, built-in serialization choice for Savvy I/O. `JsonMarshaller` is the `IMarshaller` implementation, and `JsonConverterExtensions` registers the converters that make `IMessage`, `IMetadataDictionary`, requests, and value objects round-trip correctly through `JsonSerializerOptions`. `JsonSerializerOptionsExtensions.Clone` deep-copies options for scoped contexts. + +Start with `JsonConverterExtensions.AddMessageConverter()` and `AddMetadataDictionaryConverter()` for messaging types. Add `AddRequestConverter`, `AddSingleValueObjectConverter`, `AddDateTimeConverter`, and `AddDateTimeOffsetConverter` as the domain model requires. Use `RemoveAllOf` to strip conflicting converters before composing new ones. Register the marshaller through `Savvyio.Extensions.DependencyInjection.Text.Json`; for applications that already depend on Newtonsoft.Json, `Savvyio.Extensions.Newtonsoft.Json` provides equivalent coverage. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|ICollection|⬇️|`RemoveAllOf`, `RemoveAllOf`, `AddMetadataDictionaryConverter`, `AddMessageConverter`, `AddRequestConverter`, `AddDateTimeConverter`, `AddDateTimeOffsetConverter`, `AddSingleValueObjectConverter`| +|JsonSerializerOptions|⬇️|`Clone`| diff --git a/.docfx/api/namespaces/Savvyio.Extensions.md b/.docfx/api/namespaces/Savvyio.Extensions.md new file mode 100644 index 0000000..55a57fd --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Extensions.md @@ -0,0 +1,15 @@ +--- +uid: Savvyio.Extensions +summary: *content +--- +Use the `Savvyio.Extensions` namespace to add a `Mediator` as the single unified entry point for commands, queries, and integration events in a CQRS application. Instead of injecting a separate dispatcher for each request type, inject `IMediator` and dispatch everything through one interface. + +Start with `SavvyioOptions.AddMediator` to register `Mediator` during DI setup. Use `UseAutomaticDispatcherDiscovery` and `UseAutomaticHandlerDiscovery` when you want the framework to scan assemblies and wire up dispatchers and handlers without manual registration. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|SavvyioOptions|⬇️|`AddMediator`, `UseAutomaticDispatcherDiscovery`, `UseAutomaticHandlerDiscovery`| diff --git a/.docfx/api/namespaces/Savvyio.Handlers.md b/.docfx/api/namespaces/Savvyio.Handlers.md index ed16580..bfdc641 100644 --- a/.docfx/api/namespaces/Savvyio.Handlers.md +++ b/.docfx/api/namespaces/Savvyio.Handlers.md @@ -2,13 +2,15 @@ uid: Savvyio.Handlers summary: *content --- -The `Savvyio.Handlers` namespace holds all the abstractions and core types related to handlers. +Use the `Savvyio.Handlers` namespace to build the handler layer of a CQRS application. It provides the registry contracts `IFireForgetRegistry` (for command and domain event handlers) and `IRequestReplyRegistry` (for query handlers), together with activator and handler base interfaces. The `OrphanedHandlerException` signals that a request reached a dispatcher with no registered handler. + +Start with `IFireForgetRegistry` for fire-and-forget command or event handlers and `IRequestReplyRegistry` for request-reply query handlers. Use `RegisterAsync` on either registry to subscribe handler delegates. If you use automatic handler discovery, configure it through `SavvyioOptions` in `Savvyio.Extensions.DependencyInjection`. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] -### Extension Methods +### Extension Members |Type|Ext|Methods| |--:|:-:|---| -|IFireForgetRegistry|⬇️|`RegisterAsync`| -|IRequestReplyRegistry|⬇️|`RegisterAsync`| +|IFireForgetRegistry|⬇️|`RegisterAsync`| +|IRequestReplyRegistry|⬇️|`RegisterAsync`| diff --git a/.docfx/api/namespaces/Savvyio.Messaging.Cryptography.md b/.docfx/api/namespaces/Savvyio.Messaging.Cryptography.md new file mode 100644 index 0000000..993348c --- /dev/null +++ b/.docfx/api/namespaces/Savvyio.Messaging.Cryptography.md @@ -0,0 +1,16 @@ +--- +uid: Savvyio.Messaging.Cryptography +summary: *content +--- +Messages traveling through a broker can be tampered with. The `Savvyio.Messaging.Cryptography` namespace protects command and event message integrity by attaching cryptographic signatures through `SignedMessage`, regardless of transport. + +Start with `MessageExtensions.Sign` to add an HMAC or asymmetric signature to any `IMessage`, producing a `SignedMessage`. On the consumer side, call `SignedMessageExtensions.CheckSignature` to verify authenticity before dispatching. Choose this namespace when messages travel through untrusted infrastructure and you need proof that the payload has not been altered; for cloud-native message signing without cryptographic overhead, broker-level security policies may be sufficient. For CloudEvents-specific signing, see `Savvyio.EventDriven.Messaging.CloudEvents.Cryptography`. + +[!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IMessage|⬇️|`Sign`| +|ISignedMessage|⬇️|`CheckSignature`| diff --git a/.docfx/api/namespaces/Savvyio.Messaging.md b/.docfx/api/namespaces/Savvyio.Messaging.md index 44bb867..57bd095 100644 --- a/.docfx/api/namespaces/Savvyio.Messaging.md +++ b/.docfx/api/namespaces/Savvyio.Messaging.md @@ -2,6 +2,14 @@ uid: Savvyio.Messaging summary: *content --- -The `Savvyio.Messaging` namespace holds all the abstractions and core types related to messaging. +Transport-agnostic messaging in Savvy I/O revolves around `Message`: a typed envelope that pairs a payload with a source URI, a type discriminator, and a unique message ID. The `Savvyio.Messaging` namespace provides this envelope, its options, and the async enumerable types for consuming message streams. + +Start with `Message` to create a message envelope around any command or integration event. `MessageOptions` configures the source and type metadata. `MessageAsyncEnumerable` and `MessageAsyncEnumerator` support async pull-based consumption from a queue or bus. Use `IMessage.Clone()` to duplicate a received message before re-processing or republishing it. For signed messages, see `Savvyio.Messaging.Cryptography`. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IMessage|⬇️|`Clone`| diff --git a/.docfx/api/namespaces/Savvyio.Queries.md b/.docfx/api/namespaces/Savvyio.Queries.md index 7c825fd..bb11109 100644 --- a/.docfx/api/namespaces/Savvyio.Queries.md +++ b/.docfx/api/namespaces/Savvyio.Queries.md @@ -2,6 +2,14 @@ uid: Savvyio.Queries summary: *content --- -The `Savvyio.Queries` namespace holds all the abstractions and core types related to queriying (Q in cQrs). +Use the `Savvyio.Queries` namespace to model the read side of a CQRS application. A query represents a request for data that does not change state — implement `Query` for typed result queries, then register a `QueryHandler` to produce the answer. + +Start with `Query` for your query payload classes. Register a handler that extends `QueryHandler`, and configure dispatching with `SavvyioOptions.AddQueryDispatcher` and `AddQueryHandler`. Route queries through `QueryDispatcher` or the higher-level `Mediator` from `Savvyio.Extensions`. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|SavvyioOptions|⬇️|`AddQueryHandler`, `AddQueryDispatcher`| diff --git a/.docfx/api/namespaces/Savvyio.Reflection.md b/.docfx/api/namespaces/Savvyio.Reflection.md index 6402ac0..8424a0d 100644 --- a/.docfx/api/namespaces/Savvyio.Reflection.md +++ b/.docfx/api/namespaces/Savvyio.Reflection.md @@ -2,6 +2,8 @@ uid: Savvyio.Reflection summary: *content --- -The `Savvyio.Reflection` namespace holds all the abstractions and core types related to reflection. +Use the `Savvyio.Reflection` namespace when you need low-level assembly inspection within the Savvy I/O framework. It provides `AssemblyContext`, which encapsulates metadata about an assembly that the handler discovery and registration infrastructure inspects at startup. + +`AssemblyContext` is used internally by the handler services descriptor to map handler implementations to their assembly origins. You rarely need to create it directly; it is populated automatically when you call `AddHandlerServicesDescriptor` during DI setup. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] diff --git a/.docfx/api/namespaces/Savvyio.md b/.docfx/api/namespaces/Savvyio.md index 6792951..e1cec9e 100644 --- a/.docfx/api/namespaces/Savvyio.md +++ b/.docfx/api/namespaces/Savvyio.md @@ -2,14 +2,17 @@ uid: Savvyio summary: *content --- -The `Savvyio` namespace provides the fundamental abstractions and classes for supporting a complete flow of DDD, CQRS and Event Sourcing concepts including the option to scale out using distributed subsystems. +Every Savvy I/O application shares one foundational requirement: a way to stamp causation IDs, correlation IDs, and timestamps onto requests, commands, queries, and events — and a single configuration surface that wires the handler and dispatcher graph into Microsoft DI. This namespace contains both the `IMetadata` contract and the `SavvyioOptions` model, making it the lowest-level dependency for every other Savvy I/O package. + +Start with `SavvyioOptions` — configure it through `AddSavvyIO` from `Savvyio.Extensions.DependencyInjection` and extend it with `AddDispatchers`, `AddHandlers`, and the more targeted overloads in the command, domain, query, and event-driven namespaces. Use `IMetadata` and its extension methods to stamp causation IDs, correlation IDs, and timestamps onto any request, command, or event as it flows through the system. This namespace is where you begin whenever you set up a new Savvy I/O application or extend its metadata model. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] -### Extension Methods +### Extension Members |Type|Ext|Methods| |--:|:-:|---| -|IMetadata|⬇️|`GetCausationId`, `GetCorrelationId`, `GetMemberType`, `SetCausationId`, `SetCorrelationId`, `SetEventId`, `SetTimestamp`, `SetMemberType`, `SaveMetadata`, `MergeMetadata`| +|T|⬇️|`GetCausationId`, `GetCorrelationId`, `GetRequestId`, `GetMemberType`, `SetCausationId`, `SetCorrelationId`, `SetRequestId`, `SetEventId`, `SetTimestamp`, `SetMemberType`, `SaveMetadata`| +|TDestination|⬇️|`MergeMetadata`| |SavvyioOptions|⬇️|`AddDispatchers`, `AddHandlers`| -|Task|⬇️|`SingleOrDefaultAsync`| +|Task>|⬇️|`SingleOrDefaultAsync`| From 18d651319d9e1d5c97e98bba8d29bf46ea88bd26 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Wed, 1 Jul 2026 21:47:47 +0200 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=92=AC=20document=20docfx=20maintenan?= =?UTF-8?q?ce=20standards=20in=20agents=20guide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace brief 'Official Documentation' section with comprehensive 'DocFX Documentation Maintenance' standards covering namespace/type pages, examples, availability, verification, and quality gates. Establishes clear workflow for agents to maintain API documentation current with public API changes. --- AGENTS.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a1a9063..4b8e35a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,8 +92,62 @@ Agents must never automatically commit code changes or push to remote repositori **Rationale:** Automatic commits can clutter history with incomplete work, temporary debugging code, or unintended changes. Unexpected remote operations risk overwriting or losing commits on shared branches. Always require explicit user approval before performing these actions. -## Official Documentation + +## DocFX Documentation Maintenance -- Public API conventions belong in `.docfx/api/namespaces/` and should be treated as the official documentation source for library behavior and naming vocabulary. -- When adding or renaming public APIs, update the relevant namespace page in `.docfx/api/namespaces/` if the change introduces or clarifies a convention. -- Keep internal reasoning, exploratory notes, and agent discussion out of DocFX pages; summarize only stable public guidance. +When changing public .NET APIs, keep the DocFX documentation current in the same change set. + +Documentation updates must cover public API only. Do not document private or internal types or members. Do not create namespace overview pages for namespaces that contain no public API. + +Public non-abstraction types — including enums, structs, records, plain classes, and static extension containers — are valid documentation targets. Generic public types and generic extension methods are valid documentation targets too. Do not exclude a type solely because it is generic or because reflection reports it as abstract and sealed (that is the IL pattern for a static class). + +For public non-abstraction types, include at least one realistic, copy/paste-ready usage example on the generated type page/overwrite section for that type UID. For example, a public `Class1` requires an example on the `Class1` API page, not only on the namespace page. Prefer deriving examples from existing unit, functional, or integration tests, but convert test code into real-life consumer-oriented usage. + +Missing type examples must be added through per-type DocFX overwrite files under `.docfx/api/types/{TypeUid}.md` in Codebelt repositories. Namespace overview text and `Extension Members` tables are not substitutes for type-page examples. + +Public extension methods must have examples too. Listing an extension method in an `Extension Members` table is required, but it is not enough. + +All added or changed code samples must be deterministic and verified to compile. Do not add pseudo-code, ellipses, hidden test helpers, or examples that rely on unverified behavior. + +Compilation is necessary but not sufficient. Do not present runtime implementation names such as `services.GetType().Name` or `host.GetType().FullName` as the example outcome. Show application behavior, configured state, a resolved domain service, an HTTP response, or another result that explains why a caller uses the API. Application-entry-point examples must not declare an empty local `Program` type merely to compile; show a real entry point or clearly identify the referenced application project. + +Every namespace containing public API must have a DocFX namespace overview page named after the namespace, such as `X.Y.Z.md`, under `.docfx/api/namespaces/`, using DocFX overwrite front matter with the namespace `uid`. + +Namespace pages must identify key entry points from release notes, package documentation, public factories/builders, and strong functional tests, then help readers choose among adjacent workflows. When the package complements a well-known upstream API, compare concrete acquisition, customization, lifecycle, and sharing tradeoffs from current official guidance; do not claim drop-in replacement compatibility without evidence. + +Namespaces exposing public extension methods must document those extension members at namespace level. The namespace page must include an `Extension Members` table listing the extended type, the extension marker, and the public extension methods. Extension members are rendered under the heading `Extension Members`. + +Both namespace overwrite files and type overwrite files are required deliverables in the same run. Generating only namespace pages or only type pages is incomplete. + +`docfx.json` must keep namespace and type overwrite files in separate subdirectories. `build.overwrite` must include both `api/namespaces/**/*.md` (for namespace pages) and `api/types/**/*.md` (for type pages). `build.content` must exclude both `api/namespaces/**` and `api/types/**` to prevent overwrite Markdown from being treated as conceptual content. Do not use `api/**/*.md` under `build.overwrite` or `build.content`. + +Availability must be documented by referencing the appropriate include file when one exists, or by adding explicit availability text when no suitable include exists. Availability must reflect the actual target frameworks, conditional compilation, and project configuration. + +For conditionally compiled APIs, choose the executable test framework from the asset that contains the API. Inspect the preprocessor condition, project TFMs, package `lib/` assets, and resolved consumer asset before changing a sample. For APIs under `NETSTANDARD2_0` or `NETSTANDARD2_0_OR_GREATER`, when modern `lib/netX.0/` assets also exist, use `net48` (or another supported .NET Framework target from `net462` onward) so the consumer selects `lib/netstandard2.0/`. Never use `netstandard*` as an executable target, and never use a modern `netX.0` target when it selects an asset where the API is absent. For other TFM guards, select a runnable consumer TFM that resolves to the containing asset and confirm that selection from restore or build evidence. + +Preserve manual documentation edits. Prefer additive changes, but correct stale or contradictory information so documentation remains accurate. + +Preserve working Markdown links, `Related:` references, and historical URL citations during prose rewrites. Remove or replace a URL only after directly verifying that the current destination returns HTTP 404. Timeouts, 403s, rate limits, DNS failures, and other lookup problems are not removal evidence. + +Interim scratch artifacts do not belong in the repository working tree. Store assessment queues, project manifests, review reports, captured validator output, progress notes, and one-off helper scripts in temp or session storage instead. New working-tree files are only legitimate when they are the managed `AGENTS.md` block, the active `docfx.json`, the deterministic `skip-compile-allowlist.json` waiver file when one is truly required, or DocFX-authored namespace/type Markdown that maps to a real public namespace or type. Everything else is blocking cleanup work, not a documentation deliverable. The validator auto-detects generic-arity type families (such as `MutableTuple`1`..`MutableTuple`N`) and skips redundant sibling examples from the public API surface alone, so no family-skip manifest is ever written into the repository. + +Skip markers are waivers, not fixes. A skip marker only suppresses compilation when it both existed before the current run and matches an entry in `.docfx/skip-compile-allowlist.json`. Each allowlist entry must include `diagnosticCode`, `filePath`, `uid` or `symbol`, `reason`, `approval`, and `lifetime` (`temporary` or `permanent`). Newly introduced or unallowlisted skip markers remain fail-level diagnostics and do not permit a completion claim. + +Do not emit a final report, audit result, completion summary, or handoff while `summary.canClaimCompletion` is false, `summary.remainingWorkItems` is greater than zero, `summary.remainingGates` is non-empty, `summary.fullVerificationRan` is false, fail-level diagnostics remain, `summary.newlyIntroducedSkipMarkers` is non-zero, or `summary.interimArtifacts` is non-zero. Large queues, many changed files, repetitive next steps, long runtimes, context pressure, session length, task size, or a "stable queue" are not valid stop reasons; the next action must be another remediation batch, a validator rerun, a validator/tooling fix, or a true blocker with exact evidence. + +Context pressure is not a completion condition. If the session feels constrained while work remains, continue with a smaller deterministic batch, regenerate deterministic queue state such as `--assessment-queue`, `--project-manifest`, or the active dry-run manifest/review pair, or report a true tooling failure with the exact command, exit code, and output. When naming a queue-state regeneration command, resolve it to a concrete temp/session path instead of leaving `` as a placeholder. Do not stop with phrases like "given context constraints", "best done in a follow-up", "remaining work requires authoring", "this is a massive task", or "I will provide a focused summary". A context-sized handoff while work remains is `FAIL_CONTEXT_HANDOFF_WITH_REMAINING_WORK`; the remediation is to continue with a smaller deterministic batch. + +Before completing documentation work, run the relevant verification commands, normally: + +```bash +dotnet build +dotnet test +dotnet run --file /scripts/docfx.cs -- --repo-root . --build-api-model --validate-samples --verify-docfx-build +``` + +Codebelt repositories are normally strong-name signed with a `.snk` file in the repository root on the main author's codespace. Preserve and copy that root `.snk` file when building a temporary copy. If the repository or temp copy has no root `.snk`, run build and test verification with `-p:SkipSignAssembly=true`, for example `dotnet build -p:SkipSignAssembly=true` and `dotnet test -p:SkipSignAssembly=true`. + +The final DocFX verification must run outside the working tree when possible. The `--verify-docfx-build` option copies the repository to a temp workspace, runs DocFX against the resolved `docfx.json` there, and removes the temp workspace afterward so generated API YAML, manifest files, and site output do not flood git status. Do not call the work complete until the final JSON reports `summary.fullVerificationRan: true`, `summary.canClaimCompletion: true`, `summary.remainingWorkItems: 0`, an empty `summary.remainingGates`, an empty `summary.remainingDiagnosticsByCode`, `summary.newlyIntroducedSkipMarkers: 0`, and `summary.interimArtifacts: 0`. + +If a command cannot be run, report the exact limitation or failure instead of claiming the documentation was verified. + From 3b49c670840e29262b6fc95253cf2e2b0b9e3538 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Wed, 1 Jul 2026 21:47:52 +0200 Subject: [PATCH 5/8] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20nuget=20depe?= =?UTF-8?q?ndencies=20to=20latest=20stable=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update AWSSDK packages (4.0.100), Microsoft.Data.Sqlite (10.0.9), Microsoft.Extensions.Logging.Abstractions (10.0.9), Microsoft.NET.Test.Sdk (18.7.0), NATS.Client packages (2.8.2), and EntityFrameworkCore for net9 (9.0.17) and net10 (10.0.9). Maintain Central Package Versioning discipline and ensure all dependencies align with latest stable releases. --- Directory.Packages.props | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 255f623..03ea2ce 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,11 +4,11 @@ true - - + + - + @@ -21,14 +21,14 @@ - - - + + + - - - + + + @@ -39,14 +39,14 @@ - - - + + + - - - + + + \ No newline at end of file From fd7fca922009a07bb5fb5518ef10492bade18eee Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Wed, 1 Jul 2026 21:47:56 +0200 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=92=9A=20fix=20deploy=20job=20conditi?= =?UTF-8?q?on=20to=20handle=20skipped=20optional=20jobs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add always() guard to deploy job condition to prevent skipped optional jobs (such as disabled macOS matrix runs) from suppressing deployment. Explicitly check success status of all upstream jobs to ensure deployment only runs when all required jobs succeed. --- .github/workflows/ci-pipeline.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 901dc6e..73407fa 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -341,7 +341,8 @@ jobs: security-events: write deploy: - if: github.event_name != 'pull_request' + # Avoid skipped optional jobs (for example disabled macOS matrix runs) from suppressing deployment. + if: ${{ always() && github.event_name != 'pull_request' && needs.build.result == 'success' && needs.pack.result == 'success' && needs.test_qualitygate.result == 'success' && needs.integration_test.result == 'success' && needs.integration_test_rabbitmq.result == 'success' && needs.integration_test_nats.result == 'success' && needs.sonarcloud.result == 'success' && needs.codecov.result == 'success' && needs.codeql.result == 'success' }} name: call-nuget needs: [build, pack, test_qualitygate, integration_test, integration_test_rabbitmq, integration_test_nats, sonarcloud, codecov, codeql] uses: codebeltnet/jobs-nuget-push/.github/workflows/default.yml@v3 From a547367714dafa65d9a21cb3cb933634d36a0a04 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Wed, 1 Jul 2026 23:12:53 +0200 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20add=20type=20overwr?= =?UTF-8?q?ite=20pages=20for=20public=20api=20surface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generate deterministic per-type DocFX overwrite files under .docfx/api/types/ with realistic, copy/paste-ready usage examples for all public non-abstraction types including enums, structs, records, and static extension containers. Examples sourced from unit and functional tests, verified to compile and demonstrate real application behavior. --- .../api/types/Savvyio.AsyncEventHandler`1.md | 53 ++++ .../Savvyio.Commands.CommandDispatcher.md | 35 +++ ...io.Commands.Messaging.CommandExtensions.md | 39 +++ ...Commands.Messaging.InMemoryCommandQueue.md | 51 ++++ ...vvyio.Commands.SavvyioOptionsExtensions.md | 30 +++ .../Savvyio.Dispatchers.ServiceLocator.md | 41 +++ .../Savvyio.Domain.DomainEventDispatcher.md | 35 +++ ....Domain.DomainEventDispatcherExtensions.md | 51 ++++ .../Savvyio.Domain.DomainEventExtensions.md | 25 ++ .../types/Savvyio.Domain.DomainException.md | 35 +++ ...entSourcing.TracedDomainEventExtensions.md | 32 +++ ...Savvyio.Domain.SavvyioOptionsExtensions.md | 26 ++ ....EventDriven.IntegrationEventDispatcher.md | 40 +++ ....EventDriven.IntegrationEventExtensions.md | 32 +++ ...iven.Messaging.CloudEvents.CloudEvent`1.md | 29 +++ ...vents.Cryptography.CloudEventExtensions.md | 40 +++ ...Cryptography.SignedCloudEventExtensions.md | 42 +++ ...dEvents.Cryptography.SignedCloudEvent`1.md | 29 +++ ...Messaging.CloudEvents.MessageExtensions.md | 27 ++ ....EventDriven.Messaging.InMemoryEventBus.md | 46 ++++ ...en.Messaging.IntegrationEventExtensions.md | 39 +++ ...io.EventDriven.SavvyioOptionsExtensions.md | 26 ++ ...vyio.Extensions.Dapper.DapperDataSource.md | 185 +++++++++++++ ...tensions.Dapper.DapperDataSourceOptions.md | 184 +++++++++++++ ...io.Extensions.Dapper.DapperQueryOptions.md | 31 +++ ...rExtensions.DapperExtensionsDataStore`1.md | 197 ++++++++++++++ ...tensions.DapperExtensionsQueryOptions`1.md | 34 +++ ...ection.Dapper.DapperDataSourceOptions`1.md | 187 +++++++++++++ ...encyInjection.Dapper.DapperDataSource`1.md | 189 ++++++++++++++ ...tion.Dapper.ServiceCollectionExtensions.md | 246 ++++++++++++++++++ ...rExtensions.DapperExtensionsDataStore`2.md | 201 ++++++++++++++ ...rExtensions.ServiceCollectionExtensions.md | 199 ++++++++++++++ ...ection.Data.ServiceCollectionExtensions.md | 76 ++++++ ...entSourcing.ServiceCollectionExtensions.md | 64 +++++ ...tion.Domain.ServiceCollectionExtensions.md | 89 +++++++ ...Core.Domain.EfCoreAggregateDataSource`1.md | 60 +++++ ...Core.Domain.EfCoreAggregateRepository`3.md | 48 ++++ ...rcing.EfCoreTracedAggregateRepository`3.md | 91 +++++++ ...entSourcing.ServiceCollectionExtensions.md | 90 +++++++ ...Core.Domain.ServiceCollectionExtensions.md | 62 +++++ ...ection.EFCore.EfCoreDataSourceOptions`1.md | 46 ++++ ...encyInjection.EFCore.EfCoreDataSource`1.md | 51 ++++ ...dencyInjection.EFCore.EfCoreDataStore`2.md | 55 ++++ ...dencyInjection.EFCore.EfCoreDbContext`1.md | 55 ++++ ...encyInjection.EFCore.EfCoreRepository`3.md | 53 ++++ ...tion.EFCore.ServiceCollectionExtensions.md | 60 +++++ ...n.Messaging.ServiceCollectionExtensions.md | 58 +++++ ...njection.NATS.Commands.NatsCommandQueue.md | 39 +++ ...n.NATS.Commands.NatsCommandQueueOptions.md | 27 ++ ...Injection.NATS.EventDriven.NatsEventBus.md | 40 +++ ...on.NATS.EventDriven.NatsEventBusOptions.md | 26 ++ ...ection.NATS.ServiceCollectionExtensions.md | 35 +++ ...onsoft.Json.ServiceCollectionExtensions.md | 28 ++ ...njection.QueueStorage.AzureQueueOptions.md | 24 ++ ...QueueStorage.Commands.AzureCommandQueue.md | 29 +++ ....QueueStorage.EventDriven.AzureEventBus.md | 28 ++ ...torage.EventDriven.AzureEventBusOptions.md | 25 ++ ...ueueStorage.ServiceCollectionExtensions.md | 30 +++ ....RabbitMQ.Commands.RabbitMqCommandQueue.md | 28 ++ ...MQ.Commands.RabbitMqCommandQueueOptions.md | 28 ++ ...n.RabbitMQ.EventDriven.RabbitMqEventBus.md | 28 ++ ...tMQ.EventDriven.RabbitMqEventBusOptions.md | 26 ++ ...on.RabbitMQ.ServiceCollectionExtensions.md | 35 +++ ...ction.SavvyioDependencyInjectionOptions.md | 36 +++ ...cyInjection.ServiceCollectionExtensions.md | 41 +++ ...pendencyInjection.ServiceLocatorOptions.md | 30 +++ ...encyInjection.ServiceProviderExtensions.md | 35 +++ ...ueueService.Commands.AmazonCommandQueue.md | 26 ++ ...vice.Commands.AmazonCommandQueueOptions.md | 35 +++ ...QueueService.EventDriven.AmazonEventBus.md | 32 +++ ...rvice.EventDriven.AmazonEventBusOptions.md | 35 +++ ...ueueService.ServiceCollectionExtensions.md | 38 +++ ...n.Text.Json.ServiceCollectionExtensions.md | 28 ++ ....Domain.DomainEventDispatcherExtensions.md | 83 ++++++ ...EFCore.Domain.EfCoreAggregateDataSource.md | 82 ++++++ ...Core.Domain.EfCoreAggregateRepository`2.md | 73 ++++++ ...g.EfCoreTracedAggregateEntityExtensions.md | 80 ++++++ ...cing.EfCoreTracedAggregateEntityOptions.md | 56 ++++ ...tSourcing.EfCoreTracedAggregateEntity`2.md | 79 ++++++ ...rcing.EfCoreTracedAggregateRepository`2.md | 87 +++++++ ...in.EventSourcing.ModelBuilderExtensions.md | 61 +++++ ...entSourcing.TracedDomainEventExtensions.md | 53 ++++ ...vyio.Extensions.EFCore.EfCoreDataSource.md | 73 ++++++ ...tensions.EFCore.EfCoreDataSourceOptions.md | 46 ++++ ...yio.Extensions.EFCore.EfCoreDataStore`1.md | 67 +++++ ...vvyio.Extensions.EFCore.EfCoreDbContext.md | 57 ++++ ....Extensions.EFCore.EfCoreQueryOptions`1.md | 60 +++++ ...io.Extensions.EFCore.EfCoreRepository`2.md | 68 +++++ .../api/types/Savvyio.Extensions.Mediator.md | 44 ++++ ...tensions.NATS.Commands.NatsCommandQueue.md | 35 +++ ...s.NATS.Commands.NatsCommandQueueOptions.md | 29 +++ ...xtensions.NATS.EventDriven.NatsEventBus.md | 32 +++ ...ns.NATS.EventDriven.NatsEventBusOptions.md | 27 ++ ...vyio.Extensions.NATS.NatsMessageOptions.md | 26 ++ ....Json.Converters.AggregateRootConverter.md | 37 +++ ...onsoft.Json.Converters.MessageConverter.md | 37 +++ ...onsoft.Json.Converters.RequestConverter.md | 28 ++ ...n.Converters.SingleValueObjectConverter.md | 33 +++ ...ft.Json.Converters.ValueObjectConverter.md | 39 +++ ...Newtonsoft.Json.JsonConverterExtensions.md | 46 ++++ ...ewtonsoft.Json.JsonSerializerExtensions.md | 38 +++ ...ewtonsoft.Json.NewtonsoftJsonMarshaller.md | 31 +++ ...tensions.QueueStorage.AzureQueueOptions.md | 35 +++ ...s.QueueStorage.AzureQueueReceiveOptions.md | 29 +++ ...ions.QueueStorage.AzureQueueSendOptions.md | 29 +++ ...QueueStorage.Commands.AzureCommandQueue.md | 31 +++ ....QueueStorage.EventDriven.AzureEventBus.md | 31 +++ ...torage.EventDriven.AzureEventBusOptions.md | 25 ++ ....RabbitMQ.Commands.RabbitMqCommandQueue.md | 25 ++ ...MQ.Commands.RabbitMqCommandQueueOptions.md | 29 +++ ...s.RabbitMQ.EventDriven.RabbitMqEventBus.md | 21 ++ ...tMQ.EventDriven.RabbitMqEventBusOptions.md | 27 ++ ...ensions.RabbitMQ.RabbitMqMessageOptions.md | 26 ++ ...yio.Extensions.SavvyioOptionsExtensions.md | 28 ++ ...SimpleQueueService.AmazonMessageOptions.md | 29 +++ ...ueueService.AmazonMessageReceiveOptions.md | 35 +++ ...eQueueService.AmazonResourceNameOptions.md | 26 ++ ...mpleQueueService.ClientConfigExtensions.md | 28 ++ ...ueueService.Commands.AmazonCommandQueue.md | 23 ++ ...vice.Commands.AmazonCommandQueueOptions.md | 32 +++ ...QueueService.EventDriven.AmazonEventBus.md | 23 ++ ...rvice.EventDriven.AmazonEventBusOptions.md | 33 +++ ...eueService.EventDriven.StringExtensions.md | 27 ++ ....Text.Json.Converters.DateTimeConverter.md | 28 ++ ...Json.Converters.DateTimeOffsetConverter.md | 28 ++ ...s.Text.Json.Converters.MessageConverter.md | 40 +++ ....Converters.MetadataDictionaryConverter.md | 35 +++ ...s.Text.Json.Converters.RequestConverter.md | 31 +++ ...n.Converters.SingleValueObjectConverter.md | 36 +++ ...sions.Text.Json.JsonConverterExtensions.md | 47 ++++ ...yio.Extensions.Text.Json.JsonMarshaller.md | 32 +++ ...xt.Json.JsonSerializerOptionsExtensions.md | 28 ++ .../types/Savvyio.HandlerDiscoveryModel.md | 36 +++ .docfx/api/types/Savvyio.HandlerFactory.md | 31 +++ .../Savvyio.HandlerServiceAssemblyModel.md | 33 +++ ...ServiceTypeImplementationDelegatesModel.md | 28 ++ ...o.HandlerServiceTypeImplementationModel.md | 37 +++ .../Savvyio.HandlerServicesDescriptor.md | 28 ++ ...o.Handlers.FireForgetRegistryExtensions.md | 45 ++++ ...vvyio.Handlers.OrphanedHandlerException.md | 35 +++ ...Handlers.RequestReplyRegistryExtensions.md | 45 ++++ ...Savvyio.Messaging.AcknowledgedEventArgs.md | 35 +++ ...essaging.Cryptography.MessageExtensions.md | 39 +++ ...ng.Cryptography.SignedMessageExtensions.md | 40 +++ ...aging.Cryptography.SignedMessageOptions.md | 22 ++ ....Messaging.Cryptography.SignedMessage`1.md | 33 +++ ...ssaging.MessageAsyncEnumerableOptions`1.md | 31 +++ ...vyio.Messaging.MessageAsyncEnumerable`1.md | 43 +++ .../Savvyio.Messaging.MessageExtensions.md | 25 ++ .../types/Savvyio.Messaging.MessageOptions.md | 23 ++ .../api/types/Savvyio.Messaging.Message`1.md | 31 +++ ...Savvyio.Messaging.SubscribeAsyncOptions.md | 20 ++ .../api/types/Savvyio.MetadataDictionary.md | 21 ++ .../api/types/Savvyio.MetadataExtensions.md | 37 +++ .docfx/api/types/Savvyio.MetadataFactory.md | 24 ++ .../types/Savvyio.Queries.QueryDispatcher.md | 32 +++ ...avvyio.Queries.SavvyioOptionsExtensions.md | 30 +++ .../Savvyio.Reflection.AssemblyContext.md | 25 ++ .docfx/api/types/Savvyio.SavvyioOptions.md | 41 +++ .../types/Savvyio.SavvyioOptionsExtensions.md | 28 ++ .docfx/api/types/Savvyio.TaskExtensions.md | 25 ++ 161 files changed, 7553 insertions(+) create mode 100644 .docfx/api/types/Savvyio.AsyncEventHandler`1.md create mode 100644 .docfx/api/types/Savvyio.Commands.CommandDispatcher.md create mode 100644 .docfx/api/types/Savvyio.Commands.Messaging.CommandExtensions.md create mode 100644 .docfx/api/types/Savvyio.Commands.Messaging.InMemoryCommandQueue.md create mode 100644 .docfx/api/types/Savvyio.Commands.SavvyioOptionsExtensions.md create mode 100644 .docfx/api/types/Savvyio.Dispatchers.ServiceLocator.md create mode 100644 .docfx/api/types/Savvyio.Domain.DomainEventDispatcher.md create mode 100644 .docfx/api/types/Savvyio.Domain.DomainEventDispatcherExtensions.md create mode 100644 .docfx/api/types/Savvyio.Domain.DomainEventExtensions.md create mode 100644 .docfx/api/types/Savvyio.Domain.DomainException.md create mode 100644 .docfx/api/types/Savvyio.Domain.EventSourcing.TracedDomainEventExtensions.md create mode 100644 .docfx/api/types/Savvyio.Domain.SavvyioOptionsExtensions.md create mode 100644 .docfx/api/types/Savvyio.EventDriven.IntegrationEventDispatcher.md create mode 100644 .docfx/api/types/Savvyio.EventDriven.IntegrationEventExtensions.md create mode 100644 .docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent`1.md create mode 100644 .docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.CloudEventExtensions.md create mode 100644 .docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEventExtensions.md create mode 100644 .docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent`1.md create mode 100644 .docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.MessageExtensions.md create mode 100644 .docfx/api/types/Savvyio.EventDriven.Messaging.InMemoryEventBus.md create mode 100644 .docfx/api/types/Savvyio.EventDriven.Messaging.IntegrationEventExtensions.md create mode 100644 .docfx/api/types/Savvyio.EventDriven.SavvyioOptionsExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Dapper.DapperDataSource.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Dapper.DapperDataSourceOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Dapper.DapperQueryOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DapperExtensions.DapperExtensionsDataStore`1.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DapperExtensions.DapperExtensionsQueryOptions`1.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.DapperDataSourceOptions`1.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.DapperDataSource`1.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.DapperExtensions.DapperExtensionsDataStore`2.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.DapperExtensions.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.Data.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.Domain.EventSourcing.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.Domain.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EfCoreAggregateDataSource`1.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EfCoreAggregateRepository`3.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository`3.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataSourceOptions`1.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataSource`1.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataStore`2.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDbContext`1.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreRepository`3.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.Messaging.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.Commands.NatsCommandQueue.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.Commands.NatsCommandQueueOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.NatsEventBus.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.NatsEventBusOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.AzureQueueOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.Commands.AzureCommandQueue.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.AzureEventBus.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.AzureEventBusOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.RabbitMqCommandQueue.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.RabbitMqCommandQueueOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.RabbitMqEventBus.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.RabbitMqEventBusOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.SavvyioDependencyInjectionOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceLocatorOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceProviderExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.AmazonCommandQueue.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.AmazonCommandQueueOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.AmazonEventBus.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.AmazonEventBusOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.DependencyInjection.Text.Json.ServiceCollectionExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.Domain.DomainEventDispatcherExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.Domain.EfCoreAggregateDataSource.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.Domain.EfCoreAggregateRepository`2.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntityExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntityOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntity`2.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository`2.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.ModelBuilderExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.TracedDomainEventExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataSource.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataSourceOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataStore`1.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDbContext.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.EfCoreQueryOptions`1.md create mode 100644 .docfx/api/types/Savvyio.Extensions.EFCore.EfCoreRepository`2.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Mediator.md create mode 100644 .docfx/api/types/Savvyio.Extensions.NATS.Commands.NatsCommandQueue.md create mode 100644 .docfx/api/types/Savvyio.Extensions.NATS.Commands.NatsCommandQueueOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.NATS.EventDriven.NatsEventBus.md create mode 100644 .docfx/api/types/Savvyio.Extensions.NATS.EventDriven.NatsEventBusOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.NATS.NatsMessageOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.AggregateRootConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.MessageConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.RequestConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.SingleValueObjectConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.ValueObjectConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.JsonConverterExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.JsonSerializerExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.NewtonsoftJsonMarshaller.md create mode 100644 .docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueReceiveOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueSendOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.QueueStorage.Commands.AzureCommandQueue.md create mode 100644 .docfx/api/types/Savvyio.Extensions.QueueStorage.EventDriven.AzureEventBus.md create mode 100644 .docfx/api/types/Savvyio.Extensions.QueueStorage.EventDriven.AzureEventBusOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.RabbitMQ.Commands.RabbitMqCommandQueue.md create mode 100644 .docfx/api/types/Savvyio.Extensions.RabbitMQ.Commands.RabbitMqCommandQueueOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.RabbitMQ.EventDriven.RabbitMqEventBus.md create mode 100644 .docfx/api/types/Savvyio.Extensions.RabbitMQ.EventDriven.RabbitMqEventBusOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.RabbitMQ.RabbitMqMessageOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.SavvyioOptionsExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonMessageOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonMessageReceiveOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonResourceNameOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.SimpleQueueService.ClientConfigExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.SimpleQueueService.Commands.AmazonCommandQueue.md create mode 100644 .docfx/api/types/Savvyio.Extensions.SimpleQueueService.Commands.AmazonCommandQueueOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.AmazonEventBus.md create mode 100644 .docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.AmazonEventBusOptions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.StringExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Text.Json.Converters.DateTimeConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Text.Json.Converters.DateTimeOffsetConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Text.Json.Converters.MessageConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Text.Json.Converters.MetadataDictionaryConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Text.Json.Converters.RequestConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Text.Json.Converters.SingleValueObjectConverter.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Text.Json.JsonConverterExtensions.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Text.Json.JsonMarshaller.md create mode 100644 .docfx/api/types/Savvyio.Extensions.Text.Json.JsonSerializerOptionsExtensions.md create mode 100644 .docfx/api/types/Savvyio.HandlerDiscoveryModel.md create mode 100644 .docfx/api/types/Savvyio.HandlerFactory.md create mode 100644 .docfx/api/types/Savvyio.HandlerServiceAssemblyModel.md create mode 100644 .docfx/api/types/Savvyio.HandlerServiceTypeImplementationDelegatesModel.md create mode 100644 .docfx/api/types/Savvyio.HandlerServiceTypeImplementationModel.md create mode 100644 .docfx/api/types/Savvyio.HandlerServicesDescriptor.md create mode 100644 .docfx/api/types/Savvyio.Handlers.FireForgetRegistryExtensions.md create mode 100644 .docfx/api/types/Savvyio.Handlers.OrphanedHandlerException.md create mode 100644 .docfx/api/types/Savvyio.Handlers.RequestReplyRegistryExtensions.md create mode 100644 .docfx/api/types/Savvyio.Messaging.AcknowledgedEventArgs.md create mode 100644 .docfx/api/types/Savvyio.Messaging.Cryptography.MessageExtensions.md create mode 100644 .docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessageExtensions.md create mode 100644 .docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessageOptions.md create mode 100644 .docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessage`1.md create mode 100644 .docfx/api/types/Savvyio.Messaging.MessageAsyncEnumerableOptions`1.md create mode 100644 .docfx/api/types/Savvyio.Messaging.MessageAsyncEnumerable`1.md create mode 100644 .docfx/api/types/Savvyio.Messaging.MessageExtensions.md create mode 100644 .docfx/api/types/Savvyio.Messaging.MessageOptions.md create mode 100644 .docfx/api/types/Savvyio.Messaging.Message`1.md create mode 100644 .docfx/api/types/Savvyio.Messaging.SubscribeAsyncOptions.md create mode 100644 .docfx/api/types/Savvyio.MetadataDictionary.md create mode 100644 .docfx/api/types/Savvyio.MetadataExtensions.md create mode 100644 .docfx/api/types/Savvyio.MetadataFactory.md create mode 100644 .docfx/api/types/Savvyio.Queries.QueryDispatcher.md create mode 100644 .docfx/api/types/Savvyio.Queries.SavvyioOptionsExtensions.md create mode 100644 .docfx/api/types/Savvyio.Reflection.AssemblyContext.md create mode 100644 .docfx/api/types/Savvyio.SavvyioOptions.md create mode 100644 .docfx/api/types/Savvyio.SavvyioOptionsExtensions.md create mode 100644 .docfx/api/types/Savvyio.TaskExtensions.md diff --git a/.docfx/api/types/Savvyio.AsyncEventHandler`1.md b/.docfx/api/types/Savvyio.AsyncEventHandler`1.md new file mode 100644 index 0000000..3ac34e7 --- /dev/null +++ b/.docfx/api/types/Savvyio.AsyncEventHandler`1.md @@ -0,0 +1,53 @@ +--- +uid: Savvyio.AsyncEventHandler`1 +example: +- *content +--- +This example shows how to subscribe an asynchronous event handler that enriches event data before the publisher continues. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Savvyio; + +namespace ExampleApp; + +public sealed class OrderMessagePump +{ + public event AsyncEventHandler? MessageReceived; + + public Task PublishAsync(string orderId) => MessageReceived?.Invoke(this, new OrderMessageReceivedEventArgs(orderId)) ?? Task.CompletedTask; +} + +public sealed class OrderMessageReceivedEventArgs : EventArgs +{ + public OrderMessageReceivedEventArgs(string orderId) + { + OrderId = orderId; + Metadata = new Dictionary(); + } + + public string OrderId { get; } + + public IDictionary Metadata { get; } +} + +public sealed class AsyncEventHandlerExample +{ + public async Task> CaptureAsync() + { + var pump = new OrderMessagePump(); + IDictionary? snapshot = null; + pump.MessageReceived += async (_, args) => + { + args.Metadata["orderId"] = args.OrderId; + args.Metadata["processedAtUtc"] = DateTime.UtcNow; + snapshot = args.Metadata; + await Task.CompletedTask; + }; + await pump.PublishAsync("ORD-42").ConfigureAwait(false); + return snapshot ?? new Dictionary(); + } +} +``` diff --git a/.docfx/api/types/Savvyio.Commands.CommandDispatcher.md b/.docfx/api/types/Savvyio.Commands.CommandDispatcher.md new file mode 100644 index 0000000..d36e3c0 --- /dev/null +++ b/.docfx/api/types/Savvyio.Commands.CommandDispatcher.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Commands.CommandDispatcher +example: +- *content +--- +This example shows how to wire the built-in command dispatcher to a concrete command handler through ServiceLocator. The setup mirrors a mediator pipeline where the handler tracks processed order identifiers and the dispatcher invokes it with a fire-and-forget command. + +```csharp +using System.Collections.Generic; +using Savvyio; +using Savvyio.Commands; +using Savvyio.Dispatchers; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class CommandDispatcherExample +{ + public IReadOnlyCollection Commit() + { + var handler = new CreateOrderHandler(); + var dispatcher = new CommandDispatcher(new ServiceLocator(serviceType => serviceType == typeof(ICommandHandler) ? new object[] { handler } : [])); + dispatcher.Commit(new CreateOrderCommand("ORD-42")); + return handler.ProcessedOrders; + } +} + +public sealed class CreateOrderHandler : ICommandHandler +{ + public List ProcessedOrders { get; } = new(); + public IFireForgetActivator Delegates => HandlerFactory.CreateFireForget(registry => registry.Register(command => ProcessedOrders.Add(command.OrderId))); +} + +public sealed record CreateOrderCommand(string OrderId) : Request, ICommand; +``` diff --git a/.docfx/api/types/Savvyio.Commands.Messaging.CommandExtensions.md b/.docfx/api/types/Savvyio.Commands.Messaging.CommandExtensions.md new file mode 100644 index 0000000..0947f04 --- /dev/null +++ b/.docfx/api/types/Savvyio.Commands.Messaging.CommandExtensions.md @@ -0,0 +1,39 @@ +--- +uid: Savvyio.Commands.Messaging.CommandExtensions +example: +- *content +--- +The following example shows how to wrap a command in a transport-ready `Message` by calling `ToMessage`. + +```csharp +using System; +using Savvyio.Commands; +using Savvyio.Commands.Messaging; +using Savvyio.Messaging; + +namespace ExampleApp.CommandMessages; + +public sealed class CommandExtensionsUsage +{ + public CommandExtensionsUsage() + { + var command = new CreateInvoiceCommandMessage("INV-42"); + IMessage message = command.ToMessage( + new Uri("https://api.example.com/commands/invoices"), + nameof(CreateInvoiceCommandMessage), + options => options.MessageId = "msg-invoice-42"); + + MessageId = message.Id; + Type = message.Type; + Source = message.Source; + } + + public string MessageId { get; } + + public string Type { get; } + + public string Source { get; } +} + +public sealed record CreateInvoiceCommandMessage(string InvoiceId) : Command; +``` diff --git a/.docfx/api/types/Savvyio.Commands.Messaging.InMemoryCommandQueue.md b/.docfx/api/types/Savvyio.Commands.Messaging.InMemoryCommandQueue.md new file mode 100644 index 0000000..d30a0c5 --- /dev/null +++ b/.docfx/api/types/Savvyio.Commands.Messaging.InMemoryCommandQueue.md @@ -0,0 +1,51 @@ +--- +uid: Savvyio.Commands.Messaging.InMemoryCommandQueue +example: +- *content +--- +The following example shows how to enqueue command messages and drain them from `InMemoryCommandQueue` in a test-friendly workflow. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Savvyio.Commands; +using Savvyio.Commands.Messaging; +using Savvyio.Messaging; + +namespace ExampleApp.InMemoryCommandQueueSample; + +public sealed class InMemoryCommandQueueUsage +{ + public InMemoryCommandQueueUsage() + { + RunAsync().GetAwaiter().GetResult(); + } + + public IReadOnlyList ReceivedInvoiceIds => _receivedInvoiceIds; + + private readonly List _receivedInvoiceIds = new(); + + private async Task RunAsync() + { + var queue = new InMemoryCommandQueue(); + IMessage[] messages = + { + new CreateInvoiceQueueCommand("INV-42").ToMessage(new Uri("https://api.example.com/commands/invoices"), nameof(CreateInvoiceQueueCommand)), + new CreateInvoiceQueueCommand("INV-43").ToMessage(new Uri("https://api.example.com/commands/invoices"), nameof(CreateInvoiceQueueCommand)) + }; + + await queue.SendAsync(messages); + + await foreach (var message in queue.ReceiveAsync()) + { + if (message.Data is CreateInvoiceQueueCommand command) + { + _receivedInvoiceIds.Add(command.InvoiceId); + } + } + } +} + +public sealed record CreateInvoiceQueueCommand(string InvoiceId) : Command; +``` diff --git a/.docfx/api/types/Savvyio.Commands.SavvyioOptionsExtensions.md b/.docfx/api/types/Savvyio.Commands.SavvyioOptionsExtensions.md new file mode 100644 index 0000000..8eb4788 --- /dev/null +++ b/.docfx/api/types/Savvyio.Commands.SavvyioOptionsExtensions.md @@ -0,0 +1,30 @@ +--- +uid: Savvyio.Commands.SavvyioOptionsExtensions +example: +- *content +--- +This example shows how to configure SavvyioOptions for a command-only application service. The options register both the command handler implementation and the default command dispatcher so command commits can resolve without manual type bookkeeping. + +```csharp +using Savvyio; +using Savvyio.Commands; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class CommandOptionsExtensionsExample +{ + public int Configure() + { + var options = new SavvyioOptions().AddCommandHandler().AddCommandDispatcher(); + return options.HandlerImplementationTypes.Count + options.DispatcherImplementationTypes.Count; + } +} + +public sealed class CreateOrderHandler : ICommandHandler +{ + public IFireForgetActivator Delegates => HandlerFactory.CreateFireForget(registry => registry.Register(_ => { })); +} + +public sealed record CreateOrderCommand(string OrderId) : Request, ICommand; +``` diff --git a/.docfx/api/types/Savvyio.Dispatchers.ServiceLocator.md b/.docfx/api/types/Savvyio.Dispatchers.ServiceLocator.md new file mode 100644 index 0000000..efbab22 --- /dev/null +++ b/.docfx/api/types/Savvyio.Dispatchers.ServiceLocator.md @@ -0,0 +1,41 @@ +--- +uid: Savvyio.Dispatchers.ServiceLocator +example: +- *content +--- +This example shows how to adapt an application service collection to Savvy I/O with a lightweight service locator. + +```csharp +using System; +using System.Collections.Generic; +using System.Linq; +using Savvyio.Dispatchers; + +namespace ExampleApp; + +public sealed class ServiceLocatorExample +{ + public bool CanResolveDispatcher() + { + var services = new Dictionary + { + [typeof(ICheckoutDispatcher)] = new CheckoutDispatcher() + }; + + var locator = new ServiceLocator(serviceType => + services.TryGetValue(serviceType, out var service) + ? new[] { service } + : Array.Empty()); + + return locator.GetServices(typeof(ICheckoutDispatcher)).Single() is CheckoutDispatcher; + } +} + +public interface ICheckoutDispatcher +{ +} + +public sealed class CheckoutDispatcher : ICheckoutDispatcher +{ +} +``` diff --git a/.docfx/api/types/Savvyio.Domain.DomainEventDispatcher.md b/.docfx/api/types/Savvyio.Domain.DomainEventDispatcher.md new file mode 100644 index 0000000..0a5bfbd --- /dev/null +++ b/.docfx/api/types/Savvyio.Domain.DomainEventDispatcher.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Domain.DomainEventDispatcher +example: +- *content +--- +This example shows how to route an in-process domain event to a registered handler through the default domain event dispatcher. The setup keeps the aggregate logic isolated while the handler records which account events were observed. + +```csharp +using System.Collections.Generic; +using Savvyio; +using Savvyio.Dispatchers; +using Savvyio.Domain; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class DomainEventDispatcherExample +{ + public IReadOnlyCollection Raise() + { + var handler = new AccountOpenedHandler(); + var dispatcher = new DomainEventDispatcher(new ServiceLocator(serviceType => serviceType == typeof(IDomainEventHandler) ? new object[] { handler } : [])); + dispatcher.Raise(new AccountOpenedEvent("ACC-42")); + return handler.ProcessedAccounts; + } +} + +public sealed class AccountOpenedHandler : IDomainEventHandler +{ + public List ProcessedAccounts { get; } = new(); + public IFireForgetActivator Delegates => HandlerFactory.CreateFireForget(registry => registry.Register(e => ProcessedAccounts.Add(e.AccountId))); +} + +public sealed record AccountOpenedEvent(string AccountId) : Request, IDomainEvent; +``` diff --git a/.docfx/api/types/Savvyio.Domain.DomainEventDispatcherExtensions.md b/.docfx/api/types/Savvyio.Domain.DomainEventDispatcherExtensions.md new file mode 100644 index 0000000..7d377f8 --- /dev/null +++ b/.docfx/api/types/Savvyio.Domain.DomainEventDispatcherExtensions.md @@ -0,0 +1,51 @@ +--- +uid: Savvyio.Domain.DomainEventDispatcherExtensions +example: +- *content +--- +This example shows how to flush the pending events collected by an aggregate through IDomainEventDispatcher. The sample raises one batch synchronously and a second batch asynchronously so both RaiseMany and RaiseManyAsync are exercised in the workflow that clears the aggregate event list. + +```csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Savvyio; +using Savvyio.Dispatchers; +using Savvyio.Domain; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class DomainEventDispatcherExtensionsExample +{ + public async Task<(bool SyncCleared, bool AsyncCleared)> RaisePendingEventsAsync() + { + var dispatcher = new DomainEventDispatcher(new ServiceLocator(serviceType => serviceType == typeof(IDomainEventHandler) ? new object[] { new AccountOpenedHandler() } : [])); + + var syncAggregate = new AccountAggregate(); + syncAggregate.Record(new AccountOpenedEvent("ACC-42")); + dispatcher.RaiseMany(syncAggregate); + + var asyncAggregate = new AccountAggregate(); + asyncAggregate.Record(new AccountOpenedEvent("ACC-43")); + await dispatcher.RaiseManyAsync(asyncAggregate).ConfigureAwait(false); + + return (syncAggregate.Events.Count == 0, asyncAggregate.Events.Count == 0); + } +} + +public sealed class AccountAggregate : IAggregateRoot +{ + private readonly List _events = new(); + public IMetadataDictionary Metadata { get; } = new MetadataDictionary(); + public IReadOnlyList Events => _events; + public void Record(AccountOpenedEvent e) => _events.Add(e); + public void RemoveAllEvents() => _events.Clear(); +} + +public sealed class AccountOpenedHandler : IDomainEventHandler +{ + public IFireForgetActivator Delegates => HandlerFactory.CreateFireForget(registry => registry.Register(_ => { })); +} + +public sealed record AccountOpenedEvent(string AccountId) : Request, IDomainEvent; +``` diff --git a/.docfx/api/types/Savvyio.Domain.DomainEventExtensions.md b/.docfx/api/types/Savvyio.Domain.DomainEventExtensions.md new file mode 100644 index 0000000..8bfce47 --- /dev/null +++ b/.docfx/api/types/Savvyio.Domain.DomainEventExtensions.md @@ -0,0 +1,25 @@ +--- +uid: Savvyio.Domain.DomainEventExtensions +example: +- *content +--- +This example shows how to stamp a domain event with envelope metadata and read the event identifier and timestamp later. + +```csharp +using System; +using Savvyio; +using Savvyio.Domain; + +namespace ExampleApp; + +public sealed class DomainEventExtensionsExample +{ + public (string EventId, DateTime Timestamp) Describe() + { + var e = new AccountOpenedEvent("ACC-42").SetEventId("evt-42").SetTimestamp(new DateTime(2026,7,1,0,0,0,DateTimeKind.Utc)); + return (e.GetEventId(), e.GetTimestamp()); + } +} + +public sealed record AccountOpenedEvent(string AccountId) : Request, IDomainEvent; +``` diff --git a/.docfx/api/types/Savvyio.Domain.DomainException.md b/.docfx/api/types/Savvyio.Domain.DomainException.md new file mode 100644 index 0000000..026ded5 --- /dev/null +++ b/.docfx/api/types/Savvyio.Domain.DomainException.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Domain.DomainException +example: +- *content +--- +Throw a `DomainException` when a domain invariant is violated to surface a domain-meaningful error that the application layer can map to an appropriate response. + +```csharp +using System; +using Savvyio.Domain; + +namespace ExampleApp; + +public sealed class DomainExceptionExample +{ + public void ValidateOrder(int quantity) + { + try + { + EnsurePositiveQuantity(quantity); + } + catch (DomainException exception) + { + Console.WriteLine($"Domain violation: {exception.Message}"); + throw; + } + } + + private static void EnsurePositiveQuantity(int quantity) + { + if (quantity <= 0) { throw new DomainException("Order quantity must be greater than zero."); } + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Domain.EventSourcing.TracedDomainEventExtensions.md b/.docfx/api/types/Savvyio.Domain.EventSourcing.TracedDomainEventExtensions.md new file mode 100644 index 0000000..87cd64c --- /dev/null +++ b/.docfx/api/types/Savvyio.Domain.EventSourcing.TracedDomainEventExtensions.md @@ -0,0 +1,32 @@ +--- +uid: Savvyio.Domain.EventSourcing.TracedDomainEventExtensions +example: +- *content +--- +Use `TracedDomainEventExtensions` to set and read back the aggregate version and member type on a `ITracedDomainEvent`. Call `SetAggregateVersion` when recording the event and `GetAggregateVersion` when replaying the aggregate stream. + +```csharp +using System; +using Savvyio.Domain.EventSourcing; + +namespace ExampleApp; + +public sealed class TracedDomainEventExtensionsExample +{ + public (long Version, string MemberType) StampAndRead() + { + var e = new AccountStateCapturedEvent("ACC-42") + .SetAggregateVersion(7); + e.Metadata[Savvyio.MetadataDictionary.MemberType] = typeof(AccountStateCapturedEvent).FullName; + + long version = e.GetAggregateVersion(); + string memberType = e.GetMemberType(); + Console.WriteLine($"Version: {version}, MemberType: {memberType}"); + return (version, memberType); + } +} + +public sealed record AccountStateCapturedEvent(string AccountId) : Savvyio.Request, ITracedDomainEvent; +``` + + diff --git a/.docfx/api/types/Savvyio.Domain.SavvyioOptionsExtensions.md b/.docfx/api/types/Savvyio.Domain.SavvyioOptionsExtensions.md new file mode 100644 index 0000000..1df12a3 --- /dev/null +++ b/.docfx/api/types/Savvyio.Domain.SavvyioOptionsExtensions.md @@ -0,0 +1,26 @@ +--- +uid: Savvyio.Domain.SavvyioOptionsExtensions +example: +- *content +--- +This example shows how to register a domain-event handler together with the default domain event dispatcher. + +```csharp +using Savvyio; +using Savvyio.Domain; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class DomainOptionsExtensionsExample +{ + public SavvyioOptions Configure() => new SavvyioOptions().AddDomainEventHandler().AddDomainEventDispatcher(); +} + +public sealed class AccountOpenedHandler : IDomainEventHandler +{ + public IFireForgetActivator Delegates => HandlerFactory.CreateFireForget(registry => registry.Register(_ => { })); +} + +public sealed record AccountOpenedEvent(string AccountId) : Request, IDomainEvent; +``` diff --git a/.docfx/api/types/Savvyio.EventDriven.IntegrationEventDispatcher.md b/.docfx/api/types/Savvyio.EventDriven.IntegrationEventDispatcher.md new file mode 100644 index 0000000..a81a46e --- /dev/null +++ b/.docfx/api/types/Savvyio.EventDriven.IntegrationEventDispatcher.md @@ -0,0 +1,40 @@ +--- +uid: Savvyio.EventDriven.IntegrationEventDispatcher +example: +- *content +--- +`IntegrationEventDispatcher` routes an integration event to all registered `IIntegrationEventHandler` implementations through a `ServiceLocator`. Register the handler and create the dispatcher with a service locator that resolves handlers by their service type. + +```csharp +using System.Collections.Generic; +using Savvyio; +using Savvyio.Dispatchers; +using Savvyio.EventDriven; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class IntegrationEventDispatcherExample +{ + public IReadOnlyCollection Publish() + { + var handler = new AccountCreatedHandler(); + var locator = new ServiceLocator(serviceType => + serviceType == typeof(IIntegrationEventHandler) ? new object[] { handler } : new object[0]); + var dispatcher = new IntegrationEventDispatcher(locator); + dispatcher.Publish(new AccountCreatedEvent("ACC-42", "alice@example.com")); + return handler.NotifiedEmails; + } +} + +public sealed class AccountCreatedHandler : IIntegrationEventHandler +{ + public List NotifiedEmails { get; } = new(); + + public IFireForgetActivator Delegates => + HandlerFactory.CreateFireForget(r => + r.Register(e => NotifiedEmails.Add(e.Email))); +} + +public sealed record AccountCreatedEvent(string AccountId, string Email) : Request, IIntegrationEvent; +``` diff --git a/.docfx/api/types/Savvyio.EventDriven.IntegrationEventExtensions.md b/.docfx/api/types/Savvyio.EventDriven.IntegrationEventExtensions.md new file mode 100644 index 0000000..930c1f3 --- /dev/null +++ b/.docfx/api/types/Savvyio.EventDriven.IntegrationEventExtensions.md @@ -0,0 +1,32 @@ +--- +uid: Savvyio.EventDriven.IntegrationEventExtensions +example: +- *content +--- +Use `IntegrationEventExtensions` to read the event ID, timestamp, and member type from an integration event's metadata before publishing it to another subsystem. + +```csharp +using System; +using Savvyio.EventDriven; + +namespace ExampleApp; + +public sealed class IntegrationEventExtensionsExample +{ + public (string EventId, DateTime Timestamp, string MemberType) Describe() + { + var e = new MemberCreatedEvent("MEM-42"); + e.Metadata[Savvyio.MetadataDictionary.MemberType] = typeof(MemberCreatedEvent).FullName; + + string eventId = e.GetEventId(); + DateTime timestamp = e.GetTimestamp(); + string memberType = e.GetMemberType(); + Console.WriteLine($"Event {eventId} at {timestamp:O}, type: {memberType}"); + return (eventId, timestamp, memberType); + } +} + +public sealed record MemberCreatedEvent(string MemberId) : IntegrationEvent; +``` + + diff --git a/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent`1.md b/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent`1.md new file mode 100644 index 0000000..dc6a344 --- /dev/null +++ b/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent`1.md @@ -0,0 +1,29 @@ +--- +uid: Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent`1 +example: +- *content +--- +This example shows how to convert a transport message into a concrete CloudEvent envelope and add extension attributes before publishing it. The explicit CloudEvent type is then available for downstream inspection of specversion, payload, and custom partition metadata. + +```csharp +using System; +using Savvyio; +using Savvyio.EventDriven; +using Savvyio.EventDriven.Messaging.CloudEvents; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class CloudEventExample +{ + public string Create() + { + var message = new Message("msg-42", new Uri("https://api.example.com/members"), "members.created", new MemberCreatedEvent("MEM-42")); + var cloudEvent = (CloudEvent)message.ToCloudEvent(); + cloudEvent["partitionkey"] = "members"; + return $"{cloudEvent.Specversion}:{cloudEvent["partitionkey"]}"; + } +} + +public sealed record MemberCreatedEvent(string MemberId) : Request, IIntegrationEvent; +``` diff --git a/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.CloudEventExtensions.md b/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.CloudEventExtensions.md new file mode 100644 index 0000000..e986ecc --- /dev/null +++ b/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.CloudEventExtensions.md @@ -0,0 +1,40 @@ +--- +uid: Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.CloudEventExtensions +example: +- *content +--- +This example shows how to sign a CloudEvent before handing it to an external bus. The sample includes a simple marshaller and verifies that the resulting signed envelope carries a non-empty signature that subscribers can later validate. + +```csharp +using System; +using System.IO; +using System.Text; +using Savvyio; +using Savvyio.EventDriven; +using Savvyio.EventDriven.Messaging.CloudEvents; +using Savvyio.EventDriven.Messaging.CloudEvents.Cryptography; +using Savvyio.Messaging; +using Savvyio.Messaging.Cryptography; + +namespace ExampleApp; + +public sealed class DemoMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) => new MemoryStream(Encoding.UTF8.GetBytes(value?.ToString() ?? string.Empty)); + public Stream Serialize(object value, Type inputType) => Serialize(value?.ToString() ?? string.Empty); + public TValue Deserialize(Stream data) => throw new NotSupportedException(); + public object Deserialize(Stream data, Type returnType) => throw new NotSupportedException(); +} + +public sealed class CloudEventCryptographyExtensionsExample +{ + public bool Sign() + { + var message = new Message("msg-42", new Uri("https://api.example.com/members"), "members.created", new MemberCreatedEvent("MEM-42")); + var signed = message.ToCloudEvent().SignCloudEvent(new DemoMarshaller(), options => options.SignatureSecret = new byte[] { 1, 2, 3 }); + return !string.IsNullOrWhiteSpace(signed.Signature); + } +} + +public sealed record MemberCreatedEvent(string MemberId) : Request, IIntegrationEvent; +``` diff --git a/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEventExtensions.md b/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEventExtensions.md new file mode 100644 index 0000000..4c79d0f --- /dev/null +++ b/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEventExtensions.md @@ -0,0 +1,42 @@ +--- +uid: Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEventExtensions +example: +- *content +--- +This example shows how to verify a signed CloudEvent before deserializing it into application-level event processing. + +```csharp +using System; +using Savvyio; +using Savvyio.EventDriven; +using Savvyio.EventDriven.Messaging.CloudEvents; +using Savvyio.EventDriven.Messaging.CloudEvents.Cryptography; +using Savvyio.Messaging; + +namespace ExampleApp; + +using System; +using System.IO; +using System.Text; +using Savvyio; + +public sealed class DemoMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) => new MemoryStream(Encoding.UTF8.GetBytes(value?.ToString() ?? string.Empty)); + public Stream Serialize(object value, Type inputType) => Serialize(value?.ToString() ?? string.Empty); + public TValue Deserialize(Stream data) => throw new NotSupportedException(); + public object Deserialize(Stream data, Type returnType) => throw new NotSupportedException(); +} + +public sealed class SignedCloudEventExtensionsExample +{ + public void Verify() + { + var message = new Message("msg-42", new Uri("https://api.example.com/members"), "members.created", new MemberCreatedEvent("MEM-42")); + var signed = message.ToCloudEvent().SignCloudEvent(new DemoMarshaller(), options => options.SignatureSecret = new byte[] { 1, 2, 3 }); + signed.CheckCloudEventSignature(new DemoMarshaller(), options => options.SignatureSecret = new byte[] { 1, 2, 3 }); + } +} + +public sealed record MemberCreatedEvent(string MemberId) : Request, IIntegrationEvent; +``` diff --git a/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent`1.md b/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent`1.md new file mode 100644 index 0000000..8243c5e --- /dev/null +++ b/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent`1.md @@ -0,0 +1,29 @@ +--- +uid: Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent`1 +example: +- *content +--- +This example shows how to wrap a CloudEvent together with the signature that protects its serialized envelope. + +```csharp +using System; +using Savvyio; +using Savvyio.EventDriven; +using Savvyio.EventDriven.Messaging.CloudEvents; +using Savvyio.EventDriven.Messaging.CloudEvents.Cryptography; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class SignedCloudEventExample +{ + public ISignedCloudEvent Create() + { + var message = new Message("msg-42", new Uri("https://api.example.com/members"), "members.created", new MemberCreatedEvent("MEM-42")); + var cloudEvent = message.ToCloudEvent(); + return new SignedCloudEvent(cloudEvent, "signature-value"); + } +} + +public sealed record MemberCreatedEvent(string MemberId) : Request, IIntegrationEvent; +``` diff --git a/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.MessageExtensions.md b/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.MessageExtensions.md new file mode 100644 index 0000000..23f3f80 --- /dev/null +++ b/.docfx/api/types/Savvyio.EventDriven.Messaging.CloudEvents.MessageExtensions.md @@ -0,0 +1,27 @@ +--- +uid: Savvyio.EventDriven.Messaging.CloudEvents.MessageExtensions +example: +- *content +--- +This example shows how to convert a message envelope into a CloudEvents-compliant envelope before publishing it externally. + +```csharp +using System; +using Savvyio; +using Savvyio.EventDriven; +using Savvyio.EventDriven.Messaging.CloudEvents; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class CloudEventMessageExtensionsExample +{ + public ICloudEvent Convert() + { + var message = new Message("msg-42", new Uri("https://api.example.com/members"), "members.created", new MemberCreatedEvent("MEM-42")); + return message.ToCloudEvent(); + } +} + +public sealed record MemberCreatedEvent(string MemberId) : Request, IIntegrationEvent; +``` diff --git a/.docfx/api/types/Savvyio.EventDriven.Messaging.InMemoryEventBus.md b/.docfx/api/types/Savvyio.EventDriven.Messaging.InMemoryEventBus.md new file mode 100644 index 0000000..9103356 --- /dev/null +++ b/.docfx/api/types/Savvyio.EventDriven.Messaging.InMemoryEventBus.md @@ -0,0 +1,46 @@ +--- +uid: Savvyio.EventDriven.Messaging.InMemoryEventBus +example: +- *content +--- +`InMemoryEventBus` publishes and drains `IMessage` envelopes in the same process without a real broker, making it the test-time substitute for NATS, RabbitMQ, or Azure Queue Storage. The setup requires an integration event wrapped in `IMessage` via `IntegrationEventExtensions.ToMessage`; after `PublishAsync`, call `SubscribeAsync` to drain the internal queue and receive the delivered payloads. The expected outcome is that the subscriber callback fires with the original event data so you can assert message-level behavior in isolation. + +```csharp +using System; +using System.Threading.Tasks; +using Savvyio.EventDriven; +using Savvyio.EventDriven.Messaging; +using Savvyio.Messaging; + +namespace ExampleApp.InMemoryEventBusSample; + +public sealed class InMemoryEventBusUsage +{ + public InMemoryEventBusUsage() + { + RunAsync().GetAwaiter().GetResult(); + } + + public string DeliveredEmailAddress { get; private set; } = string.Empty; + + private async Task RunAsync() + { + var bus = new InMemoryEventBus(); + IMessage message = new MemberInvitedEventBusMessage("jane@example.com") + .ToMessage(new Uri("https://api.example.com/invitations"), nameof(MemberInvitedEventBusMessage)); + + await bus.PublishAsync(message); + await bus.SubscribeAsync((received, _) => + { + if (received.Data is MemberInvitedEventBusMessage invited) + { + DeliveredEmailAddress = invited.EmailAddress; + } + + return Task.CompletedTask; + }); + } +} + +public sealed record MemberInvitedEventBusMessage(string EmailAddress) : IntegrationEvent; +``` diff --git a/.docfx/api/types/Savvyio.EventDriven.Messaging.IntegrationEventExtensions.md b/.docfx/api/types/Savvyio.EventDriven.Messaging.IntegrationEventExtensions.md new file mode 100644 index 0000000..9835650 --- /dev/null +++ b/.docfx/api/types/Savvyio.EventDriven.Messaging.IntegrationEventExtensions.md @@ -0,0 +1,39 @@ +--- +uid: Savvyio.EventDriven.Messaging.IntegrationEventExtensions +example: +- *content +--- +The following example shows how to wrap an integration event in a transport-friendly `Message` by calling `ToMessage`. + +```csharp +using System; +using Savvyio.EventDriven; +using Savvyio.EventDriven.Messaging; +using Savvyio.Messaging; + +namespace ExampleApp.IntegrationMessages; + +public sealed class IntegrationEventMessagingExtensionsUsage +{ + public IntegrationEventMessagingExtensionsUsage() + { + var integrationEvent = new MemberWelcomeEmailQueued("member-42"); + IMessage message = integrationEvent.ToMessage( + new Uri("https://api.example.com/messages/member-42"), + nameof(MemberWelcomeEmailQueued), + options => options.MessageId = "msg-member-42"); + + MessageId = message.Id; + Type = message.Type; + Source = message.Source; + } + + public string MessageId { get; } + + public string Type { get; } + + public string Source { get; } +} + +public sealed record MemberWelcomeEmailQueued(string MemberId) : IntegrationEvent; +``` diff --git a/.docfx/api/types/Savvyio.EventDriven.SavvyioOptionsExtensions.md b/.docfx/api/types/Savvyio.EventDriven.SavvyioOptionsExtensions.md new file mode 100644 index 0000000..72c9147 --- /dev/null +++ b/.docfx/api/types/Savvyio.EventDriven.SavvyioOptionsExtensions.md @@ -0,0 +1,26 @@ +--- +uid: Savvyio.EventDriven.SavvyioOptionsExtensions +example: +- *content +--- +This example shows how to register an integration-event handler together with the default integration event dispatcher. + +```csharp +using Savvyio; +using Savvyio.EventDriven; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class EventDrivenOptionsExtensionsExample +{ + public SavvyioOptions Configure() => new SavvyioOptions().AddIntegrationEventHandler().AddIntegrationEventDispatcher(); +} + +public sealed class MemberCreatedHandler : IIntegrationEventHandler +{ + public IFireForgetActivator Delegates => HandlerFactory.CreateFireForget(registry => registry.Register(_ => { })); +} + +public sealed record MemberCreatedEvent(string MemberId) : Request, IIntegrationEvent; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Dapper.DapperDataSource.md b/.docfx/api/types/Savvyio.Extensions.Dapper.DapperDataSource.md new file mode 100644 index 0000000..09d9d20 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Dapper.DapperDataSource.md @@ -0,0 +1,185 @@ +--- +uid: Savvyio.Extensions.Dapper.DapperDataSource +example: +- *content +--- +`DapperDataSource` is the Savvy I/O base class that wraps a Dapper connection factory. Subclass it to provide a named connection source, then inject the instance into `DapperDataStore` subclasses so they share one connection lifecycle. The example creates a concrete data source, opens a connection from it, and verifies the connection is non-null. + +```csharp +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using Savvyio.Extensions.Dapper; + +namespace ExampleApp; + +public sealed class DapperSourceExample +{ + public IDbCommand CreateCommand() + { + var source = new DapperDataSource(new DapperDataSourceOptions + { + ConnectionFactory = () => new FakeDbConnection() + }); + + using var transaction = source.BeginTransaction(); + return source.CreateCommand(); + } +} + +public sealed class FakeDbConnection : IDbConnection +{ + private ConnectionState _state; + + public string ConnectionString { get; set; } = "Data Source=fake;"; + + public int ConnectionTimeout => 30; + + public string Database => "Fake"; + + public ConnectionState State => _state; + + public IDbTransaction BeginTransaction() + { + return new FakeDbTransaction(this, IsolationLevel.ReadCommitted); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return new FakeDbTransaction(this, il); + } + + public void ChangeDatabase(string databaseName) + { + } + + public void Close() + { + _state = ConnectionState.Closed; + } + + public IDbCommand CreateCommand() + { + return new FakeDbCommand(this); + } + + public void Open() + { + _state = ConnectionState.Open; + } + + public void Dispose() + { + Close(); + } +} + +public sealed class FakeDbTransaction : IDbTransaction +{ + public FakeDbTransaction(IDbConnection connection, IsolationLevel isolationLevel) + { + Connection = connection; + IsolationLevel = isolationLevel; + } + + public IDbConnection Connection { get; } + + public IsolationLevel IsolationLevel { get; } + + public void Commit() + { + } + + public void Rollback() + { + } + + public void Dispose() + { + } +} + +public sealed class FakeDbCommand : IDbCommand +{ + public FakeDbCommand(IDbConnection connection) + { + Connection = connection; + Parameters = new FakeParameterCollection(); + } + + public string CommandText { get; set; } = string.Empty; + + public int CommandTimeout { get; set; } + + public CommandType CommandType { get; set; } + + public IDbConnection Connection { get; set; } + + public IDataParameterCollection Parameters { get; } + + public IDbTransaction? Transaction { get; set; } + + public UpdateRowSource UpdatedRowSource { get; set; } + + public void Cancel() + { + } + + public IDbDataParameter CreateParameter() + { + throw new NotSupportedException(); + } + + public void Dispose() + { + } + + public int ExecuteNonQuery() + { + throw new NotSupportedException(); + } + + public IDataReader ExecuteReader() + { + throw new NotSupportedException(); + } + + public IDataReader ExecuteReader(CommandBehavior behavior) + { + throw new NotSupportedException(); + } + + public object ExecuteScalar() + { + throw new NotSupportedException(); + } + + public void Prepare() + { + } +} + +public sealed class FakeParameterCollection : List, IDataParameterCollection +{ + public object this[string parameterName] + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public bool Contains(string parameterName) + { + return false; + } + + public int IndexOf(string parameterName) + { + return -1; + } + + public void RemoveAt(string parameterName) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Dapper.DapperDataSourceOptions.md b/.docfx/api/types/Savvyio.Extensions.Dapper.DapperDataSourceOptions.md new file mode 100644 index 0000000..c323090 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Dapper.DapperDataSourceOptions.md @@ -0,0 +1,184 @@ +--- +uid: Savvyio.Extensions.Dapper.DapperDataSourceOptions +example: +- *content +--- +`DapperDataSourceOptions` holds the `ConnectionFactory` delegate that `DapperDataSource` uses to create `IDbConnection` instances. Setting `ConnectionFactory` to a lambda that opens a database connection is the minimum required configuration. The example configures a factory pointing at an in-memory SQLite database and confirms the options object passes validation. + +```csharp +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using Savvyio.Extensions.Dapper; + +namespace ExampleApp; + +public sealed class DapperOptionsExample +{ + public DapperDataSource CreateSource() + { + var options = new DapperDataSourceOptions + { + ConnectionFactory = () => new FakeDbConnection() + }; + + return new DapperDataSource(options); + } +} + +public sealed class FakeDbConnection : IDbConnection +{ + private ConnectionState _state; + + public string ConnectionString { get; set; } = "Data Source=fake;"; + + public int ConnectionTimeout => 30; + + public string Database => "Fake"; + + public ConnectionState State => _state; + + public IDbTransaction BeginTransaction() + { + return new FakeDbTransaction(this, IsolationLevel.ReadCommitted); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return new FakeDbTransaction(this, il); + } + + public void ChangeDatabase(string databaseName) + { + } + + public void Close() + { + _state = ConnectionState.Closed; + } + + public IDbCommand CreateCommand() + { + return new FakeDbCommand(this); + } + + public void Open() + { + _state = ConnectionState.Open; + } + + public void Dispose() + { + Close(); + } +} + +public sealed class FakeDbTransaction : IDbTransaction +{ + public FakeDbTransaction(IDbConnection connection, IsolationLevel isolationLevel) + { + Connection = connection; + IsolationLevel = isolationLevel; + } + + public IDbConnection Connection { get; } + + public IsolationLevel IsolationLevel { get; } + + public void Commit() + { + } + + public void Rollback() + { + } + + public void Dispose() + { + } +} + +public sealed class FakeDbCommand : IDbCommand +{ + public FakeDbCommand(IDbConnection connection) + { + Connection = connection; + Parameters = new FakeParameterCollection(); + } + + public string CommandText { get; set; } = string.Empty; + + public int CommandTimeout { get; set; } + + public CommandType CommandType { get; set; } + + public IDbConnection Connection { get; set; } + + public IDataParameterCollection Parameters { get; } + + public IDbTransaction? Transaction { get; set; } + + public UpdateRowSource UpdatedRowSource { get; set; } + + public void Cancel() + { + } + + public IDbDataParameter CreateParameter() + { + throw new NotSupportedException(); + } + + public void Dispose() + { + } + + public int ExecuteNonQuery() + { + throw new NotSupportedException(); + } + + public IDataReader ExecuteReader() + { + throw new NotSupportedException(); + } + + public IDataReader ExecuteReader(CommandBehavior behavior) + { + throw new NotSupportedException(); + } + + public object ExecuteScalar() + { + throw new NotSupportedException(); + } + + public void Prepare() + { + } +} + +public sealed class FakeParameterCollection : List, IDataParameterCollection +{ + public object this[string parameterName] + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public bool Contains(string parameterName) + { + return false; + } + + public int IndexOf(string parameterName) + { + return -1; + } + + public void RemoveAt(string parameterName) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Dapper.DapperQueryOptions.md b/.docfx/api/types/Savvyio.Extensions.Dapper.DapperQueryOptions.md new file mode 100644 index 0000000..cfb0668 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Dapper.DapperQueryOptions.md @@ -0,0 +1,31 @@ +--- +uid: Savvyio.Extensions.Dapper.DapperQueryOptions +example: +- *content +--- +This example shows how `DapperQueryOptions` becomes a `CommandDefinition` that carries the SQL text, parameters, and timeout for one query. + +```csharp +using System; +using System.Data; +using Dapper; +using Savvyio.Extensions.Dapper; + +namespace ExampleApp; + +public sealed class QueryOptionsExample +{ + public CommandDefinition BuildCommand() + { + var options = new DapperQueryOptions + { + CommandText = "SELECT * FROM Orders WHERE Status = @Status", + Parameters = new { Status = "Pending" }, + CommandTimeout = TimeSpan.FromSeconds(15), + CommandType = CommandType.Text + }; + + return options; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DapperExtensions.DapperExtensionsDataStore`1.md b/.docfx/api/types/Savvyio.Extensions.DapperExtensions.DapperExtensionsDataStore`1.md new file mode 100644 index 0000000..c36b754 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DapperExtensions.DapperExtensionsDataStore`1.md @@ -0,0 +1,197 @@ +--- +uid: Savvyio.Extensions.DapperExtensions.DapperExtensionsDataStore`1 +example: +- *content +--- +`DapperExtensionsDataStore` provides automatic CRUD operations for the entity type `T` using the DapperExtensions library. Subclass it with the entity type and data source type, supply the `IDapperDataSource` in the constructor, and override any CRUD methods you need to customize. The example creates a concrete order data store, adds an entity through `CreateAsync`, and retrieves it through `GetByIdAsync` to confirm the round-trip. + +```csharp +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Threading.Tasks; +using Savvyio.Extensions.Dapper; +using Savvyio.Extensions.DapperExtensions; + +namespace ExampleApp; + +public sealed class ProjectionQueries +{ + public Task> LoadPendingOrdersAsync() + { + var source = new DapperDataSource(new DapperDataSourceOptions + { + ConnectionFactory = () => new FakeDbConnection() + }); + + var store = new DapperExtensionsDataStore(source); + return store.FindAllAsync(options => + { + options.Predicate = order => order.Status; + options.Value = "Pending"; + }); + } +} + +public sealed class OrderProjection +{ + public long Id { get; set; } + + public string Status { get; set; } = string.Empty; +} + +public sealed class FakeDbConnection : IDbConnection +{ + private ConnectionState _state; + + public string ConnectionString { get; set; } = "Data Source=fake;"; + + public int ConnectionTimeout => 30; + + public string Database => "Fake"; + + public ConnectionState State => _state; + + public IDbTransaction BeginTransaction() + { + return new FakeDbTransaction(this, IsolationLevel.ReadCommitted); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return new FakeDbTransaction(this, il); + } + + public void ChangeDatabase(string databaseName) + { + } + + public void Close() + { + _state = ConnectionState.Closed; + } + + public IDbCommand CreateCommand() + { + return new FakeDbCommand(this); + } + + public void Open() + { + _state = ConnectionState.Open; + } + + public void Dispose() + { + Close(); + } +} + +public sealed class FakeDbTransaction : IDbTransaction +{ + public FakeDbTransaction(IDbConnection connection, IsolationLevel isolationLevel) + { + Connection = connection; + IsolationLevel = isolationLevel; + } + + public IDbConnection Connection { get; } + + public IsolationLevel IsolationLevel { get; } + + public void Commit() + { + } + + public void Rollback() + { + } + + public void Dispose() + { + } +} + +public sealed class FakeDbCommand : IDbCommand +{ + public FakeDbCommand(IDbConnection connection) + { + Connection = connection; + Parameters = new FakeParameterCollection(); + } + + public string CommandText { get; set; } = string.Empty; + + public int CommandTimeout { get; set; } + + public CommandType CommandType { get; set; } + + public IDbConnection Connection { get; set; } + + public IDataParameterCollection Parameters { get; } + + public IDbTransaction? Transaction { get; set; } + + public UpdateRowSource UpdatedRowSource { get; set; } + + public void Cancel() + { + } + + public IDbDataParameter CreateParameter() + { + throw new System.NotSupportedException(); + } + + public void Dispose() + { + } + + public int ExecuteNonQuery() + { + throw new System.NotSupportedException(); + } + + public IDataReader ExecuteReader() + { + throw new System.NotSupportedException(); + } + + public IDataReader ExecuteReader(CommandBehavior behavior) + { + throw new System.NotSupportedException(); + } + + public object ExecuteScalar() + { + throw new System.NotSupportedException(); + } + + public void Prepare() + { + } +} + +public sealed class FakeParameterCollection : List, IDataParameterCollection +{ + public object this[string parameterName] + { + get => throw new System.NotSupportedException(); + set => throw new System.NotSupportedException(); + } + + public bool Contains(string parameterName) + { + return false; + } + + public int IndexOf(string parameterName) + { + return -1; + } + + public void RemoveAt(string parameterName) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DapperExtensions.DapperExtensionsQueryOptions`1.md b/.docfx/api/types/Savvyio.Extensions.DapperExtensions.DapperExtensionsQueryOptions`1.md new file mode 100644 index 0000000..90c934d --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DapperExtensions.DapperExtensionsQueryOptions`1.md @@ -0,0 +1,34 @@ +--- +uid: Savvyio.Extensions.DapperExtensions.DapperExtensionsQueryOptions`1 +example: +- *content +--- +This example shows how `DapperExtensionsQueryOptions` expresses a field predicate that a `DapperExtensionsDataStore` can apply. + +```csharp +using DapperExtensions; +using DapperExtensions.Predicate; +using Savvyio.Extensions.DapperExtensions; + +namespace ExampleApp; + +public sealed class PredicateOptionsExample +{ + public DapperExtensionsQueryOptions Create() + { + return new DapperExtensionsQueryOptions + { + Predicate = order => order.Status, + Op = Operator.Eq, + Value = "Pending" + }; + } +} + +public sealed class OrderProjection +{ + public long Id { get; set; } + + public string Status { get; set; } = string.Empty; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.DapperDataSourceOptions`1.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.DapperDataSourceOptions`1.md new file mode 100644 index 0000000..225fd9d --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.DapperDataSourceOptions`1.md @@ -0,0 +1,187 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Dapper.DapperDataSourceOptions`1 +example: +- *content +--- +`DapperDataSourceOptions` holds the `ConnectionFactory` and `Lifetime` values populated when `AddDapperDataSource` is called. The `ConnectionFactory` property must produce an open database connection. The example configures options with a test factory and shows how the type appears in a complete DI setup. + +```csharp +using System.Collections; +using System.Collections.Generic; +using System.Data; +using Savvyio.Extensions.DependencyInjection.Dapper; + +namespace ExampleApp; + +public sealed class MarkerOptionsExample +{ + public DapperDataSource CreateSource() + { + var options = new DapperDataSourceOptions + { + ConnectionFactory = () => new FakeDbConnection() + }; + + return new DapperDataSource(options); + } +} + +public sealed class OrdersMarker +{ +} + +public sealed class FakeDbConnection : IDbConnection +{ + private ConnectionState _state; + + public string ConnectionString { get; set; } = "Data Source=fake;"; + + public int ConnectionTimeout => 30; + + public string Database => "Fake"; + + public ConnectionState State => _state; + + public IDbTransaction BeginTransaction() + { + return new FakeDbTransaction(this, IsolationLevel.ReadCommitted); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return new FakeDbTransaction(this, il); + } + + public void ChangeDatabase(string databaseName) + { + } + + public void Close() + { + _state = ConnectionState.Closed; + } + + public IDbCommand CreateCommand() + { + return new FakeDbCommand(this); + } + + public void Open() + { + _state = ConnectionState.Open; + } + + public void Dispose() + { + Close(); + } +} + +public sealed class FakeDbTransaction : IDbTransaction +{ + public FakeDbTransaction(IDbConnection connection, IsolationLevel isolationLevel) + { + Connection = connection; + IsolationLevel = isolationLevel; + } + + public IDbConnection Connection { get; } + + public IsolationLevel IsolationLevel { get; } + + public void Commit() + { + } + + public void Rollback() + { + } + + public void Dispose() + { + } +} + +public sealed class FakeDbCommand : IDbCommand +{ + public FakeDbCommand(IDbConnection connection) + { + Connection = connection; + Parameters = new FakeParameterCollection(); + } + + public string CommandText { get; set; } = string.Empty; + + public int CommandTimeout { get; set; } + + public CommandType CommandType { get; set; } + + public IDbConnection Connection { get; set; } + + public IDataParameterCollection Parameters { get; } + + public IDbTransaction? Transaction { get; set; } + + public UpdateRowSource UpdatedRowSource { get; set; } + + public void Cancel() + { + } + + public IDbDataParameter CreateParameter() + { + throw new System.NotSupportedException(); + } + + public void Dispose() + { + } + + public int ExecuteNonQuery() + { + throw new System.NotSupportedException(); + } + + public IDataReader ExecuteReader() + { + throw new System.NotSupportedException(); + } + + public IDataReader ExecuteReader(CommandBehavior behavior) + { + throw new System.NotSupportedException(); + } + + public object ExecuteScalar() + { + throw new System.NotSupportedException(); + } + + public void Prepare() + { + } +} + +public sealed class FakeParameterCollection : List, IDataParameterCollection +{ + public object this[string parameterName] + { + get => throw new System.NotSupportedException(); + set => throw new System.NotSupportedException(); + } + + public bool Contains(string parameterName) + { + return false; + } + + public int IndexOf(string parameterName) + { + return -1; + } + + public void RemoveAt(string parameterName) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.DapperDataSource`1.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.DapperDataSource`1.md new file mode 100644 index 0000000..90248c4 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.DapperDataSource`1.md @@ -0,0 +1,189 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Dapper.DapperDataSource`1 +example: +- *content +--- +The DI-registered `DapperDataSource` is the concrete data source bound by `AddDapperDataSource`. Resolve it as `IDapperDataSource` to obtain connections for Dapper data store operations. The example registers the data source and resolves it from the built provider to confirm the DI binding. + +```csharp +using System.Collections; +using System.Collections.Generic; +using System.Data; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.Dapper; + +namespace ExampleApp; + +public sealed class MarkerBasedDapperSourceExample +{ + public bool IsResolvedAsMarkedSource() + { + var services = new ServiceCollection(); + services.AddDapperDataSource(options => options.ConnectionFactory = () => new FakeDbConnection()); + + using var provider = services.BuildServiceProvider(); + var source = provider.GetRequiredService>(); + + return source is DapperDataSource; + } +} + +public sealed class OrdersMarker +{ +} + +public sealed class FakeDbConnection : IDbConnection +{ + private ConnectionState _state; + + public string ConnectionString { get; set; } = "Data Source=fake;"; + + public int ConnectionTimeout => 30; + + public string Database => "Fake"; + + public ConnectionState State => _state; + + public IDbTransaction BeginTransaction() + { + return new FakeDbTransaction(this, IsolationLevel.ReadCommitted); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return new FakeDbTransaction(this, il); + } + + public void ChangeDatabase(string databaseName) + { + } + + public void Close() + { + _state = ConnectionState.Closed; + } + + public IDbCommand CreateCommand() + { + return new FakeDbCommand(this); + } + + public void Open() + { + _state = ConnectionState.Open; + } + + public void Dispose() + { + Close(); + } +} + +public sealed class FakeDbTransaction : IDbTransaction +{ + public FakeDbTransaction(IDbConnection connection, IsolationLevel isolationLevel) + { + Connection = connection; + IsolationLevel = isolationLevel; + } + + public IDbConnection Connection { get; } + + public IsolationLevel IsolationLevel { get; } + + public void Commit() + { + } + + public void Rollback() + { + } + + public void Dispose() + { + } +} + +public sealed class FakeDbCommand : IDbCommand +{ + public FakeDbCommand(IDbConnection connection) + { + Connection = connection; + Parameters = new FakeParameterCollection(); + } + + public string CommandText { get; set; } = string.Empty; + + public int CommandTimeout { get; set; } + + public CommandType CommandType { get; set; } + + public IDbConnection Connection { get; set; } + + public IDataParameterCollection Parameters { get; } + + public IDbTransaction? Transaction { get; set; } + + public UpdateRowSource UpdatedRowSource { get; set; } + + public void Cancel() + { + } + + public IDbDataParameter CreateParameter() + { + throw new System.NotSupportedException(); + } + + public void Dispose() + { + } + + public int ExecuteNonQuery() + { + throw new System.NotSupportedException(); + } + + public IDataReader ExecuteReader() + { + throw new System.NotSupportedException(); + } + + public IDataReader ExecuteReader(CommandBehavior behavior) + { + throw new System.NotSupportedException(); + } + + public object ExecuteScalar() + { + throw new System.NotSupportedException(); + } + + public void Prepare() + { + } +} + +public sealed class FakeParameterCollection : List, IDataParameterCollection +{ + public object this[string parameterName] + { + get => throw new System.NotSupportedException(); + set => throw new System.NotSupportedException(); + } + + public bool Contains(string parameterName) + { + return false; + } + + public int IndexOf(string parameterName) + { + return -1; + } + + public void RemoveAt(string parameterName) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.ServiceCollectionExtensions.md new file mode 100644 index 0000000..d91f956 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Dapper.ServiceCollectionExtensions.md @@ -0,0 +1,246 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Dapper.ServiceCollectionExtensions +example: +- *content +--- +Wiring Dapper persistence into Savvy I/O requires two sequential registrations: `AddDapperDataSource` to register the connection factory and `IDapperDataSource`, and `AddDapperDataStore` to bind each data store implementation to its service interface. The connection factory lambda must return an IDbConnection instance; the data source must be registered before the data stores. The example builds a provider with both registrations and confirms the data store resolves to the expected concrete type. + +```csharp +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Data; +using Savvyio.Extensions.Dapper; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.Dapper; + +namespace ExampleApp; + +public sealed class DapperRegistrationExample +{ + public ServiceProvider BuildProvider() + { + var services = new ServiceCollection(); + services.AddMarshaller(p => new FakeMarshaller()); + services.AddDapperDataSource(options => options.ConnectionFactory = () => new FakeDbConnection()); + services.AddDapperDataStore(); + + return services.BuildServiceProvider(); + } + + public bool HasExpectedRegistration(ServiceProvider provider) + { + var source = provider.GetRequiredService(); + var store = provider.GetRequiredService>(); + + return source is DapperDataSource && store is OrderProjectionStore; + } +} + +public sealed class OrderProjection +{ + public long Id { get; set; } + + public string Status { get; set; } = string.Empty; +} + +public sealed class OrderProjectionStore : DapperDataStore +{ + public OrderProjectionStore(IDapperDataSource source) : base(source) + { + } + + public override Task CreateAsync(OrderProjection dto, Action? setup = null) + { + return Task.CompletedTask; + } + + public override Task UpdateAsync(OrderProjection dto, Action? setup = null) + { + return Task.CompletedTask; + } + + public override Task GetByIdAsync(object id, Action? setup = null) + { + return Task.FromResult(new OrderProjection { Id = Convert.ToInt64(id), Status = "Pending" }); + } + + public override Task> FindAllAsync(Action? setup = null) + { + return Task.FromResult>(Array.Empty()); + } + + public override Task DeleteAsync(OrderProjection dto, Action? setup = null) + { + return Task.CompletedTask; + } +} + +public sealed class FakeDbConnection : IDbConnection +{ + private ConnectionState _state; + + public string ConnectionString { get; set; } = "Data Source=fake;"; + + public int ConnectionTimeout => 30; + + public string Database => "Fake"; + + public ConnectionState State => _state; + + public IDbTransaction BeginTransaction() + { + return new FakeDbTransaction(this, IsolationLevel.ReadCommitted); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return new FakeDbTransaction(this, il); + } + + public void ChangeDatabase(string databaseName) + { + } + + public void Close() + { + _state = ConnectionState.Closed; + } + + public IDbCommand CreateCommand() + { + return new FakeDbCommand(this); + } + + public void Open() + { + _state = ConnectionState.Open; + } + + public void Dispose() + { + Close(); + } +} + +public sealed class FakeDbTransaction : IDbTransaction +{ + public FakeDbTransaction(IDbConnection connection, IsolationLevel isolationLevel) + { + Connection = connection; + IsolationLevel = isolationLevel; + } + + public IDbConnection Connection { get; } + + public IsolationLevel IsolationLevel { get; } + + public void Commit() + { + } + + public void Rollback() + { + } + + public void Dispose() + { + } +} + +public sealed class FakeDbCommand : IDbCommand +{ + public FakeDbCommand(IDbConnection connection) + { + Connection = connection; + Parameters = new FakeParameterCollection(); + } + + public string CommandText { get; set; } = string.Empty; + + public int CommandTimeout { get; set; } + + public CommandType CommandType { get; set; } + + public IDbConnection Connection { get; set; } + + public IDataParameterCollection Parameters { get; } + + public IDbTransaction? Transaction { get; set; } + + public UpdateRowSource UpdatedRowSource { get; set; } + + public void Cancel() + { + } + + public IDbDataParameter CreateParameter() + { + throw new NotSupportedException(); + } + + public void Dispose() + { + } + + public int ExecuteNonQuery() + { + throw new NotSupportedException(); + } + + public IDataReader ExecuteReader() + { + throw new NotSupportedException(); + } + + public IDataReader ExecuteReader(CommandBehavior behavior) + { + throw new NotSupportedException(); + } + + public object ExecuteScalar() + { + throw new NotSupportedException(); + } + + public void Prepare() + { + } +} + +public sealed class FakeParameterCollection : List, IDataParameterCollection +{ + public object this[string parameterName] + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public bool Contains(string parameterName) + { + return false; + } + + public int IndexOf(string parameterName) + { + return -1; + } + + public void RemoveAt(string parameterName) + { + } +} + +public sealed class FakeMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) => Stream.Null; + public Stream Serialize(object value, Type inputType) => Stream.Null; + public TValue Deserialize(Stream data) => default!; + public object Deserialize(Stream data, Type returnType) => null!; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.DapperExtensions.DapperExtensionsDataStore`2.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.DapperExtensions.DapperExtensionsDataStore`2.md new file mode 100644 index 0000000..e65ac74 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.DapperExtensions.DapperExtensionsDataStore`2.md @@ -0,0 +1,201 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.DapperExtensions.DapperExtensionsDataStore`2 +example: +- *content +--- +The DI-registered `DapperExtensionsDataStore` resolves as `IPersistentDataStore` when registered via `AddDapperExtensionsDataStore`. Supply the data source created by `AddDapperDataSource` and the concrete store type. The example registers both services and resolves the store to verify correct DI wiring. + +```csharp +using System.Collections; +using System.Collections.Generic; +using System.Data; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Data; +using Savvyio.Extensions.DapperExtensions; +using Savvyio.Extensions.DependencyInjection.Dapper; +using Savvyio.Extensions.DependencyInjection.Data; +using Savvyio.Extensions.DependencyInjection.DapperExtensions; + +namespace ExampleApp; + +public sealed class MarkerBasedDapperExtensionsStoreExample +{ + public bool IsResolvedAsMarkedStore() + { + var services = new ServiceCollection(); + services.AddDapperDataSource(options => options.ConnectionFactory = () => new FakeDbConnection()); + services.AddDapperExtensionsDataStore(); + + using var provider = services.BuildServiceProvider(); + var store = provider.GetRequiredService, OrdersMarker>>(); + + return store is DapperExtensionsDataStore; + } +} + +public sealed class OrdersMarker +{ +} + +public sealed class OrderProjection +{ + public long Id { get; set; } + + public string Status { get; set; } = string.Empty; +} + +public sealed class FakeDbConnection : IDbConnection +{ + private ConnectionState _state; + + public string ConnectionString { get; set; } = "Data Source=fake;"; + + public int ConnectionTimeout => 30; + + public string Database => "Fake"; + + public ConnectionState State => _state; + + public IDbTransaction BeginTransaction() + { + return new FakeDbTransaction(this, IsolationLevel.ReadCommitted); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return new FakeDbTransaction(this, il); + } + + public void ChangeDatabase(string databaseName) + { + } + + public void Close() + { + _state = ConnectionState.Closed; + } + + public IDbCommand CreateCommand() + { + return new FakeDbCommand(this); + } + + public void Open() + { + _state = ConnectionState.Open; + } + + public void Dispose() + { + Close(); + } +} + +public sealed class FakeDbTransaction : IDbTransaction +{ + public FakeDbTransaction(IDbConnection connection, IsolationLevel isolationLevel) + { + Connection = connection; + IsolationLevel = isolationLevel; + } + + public IDbConnection Connection { get; } + + public IsolationLevel IsolationLevel { get; } + + public void Commit() + { + } + + public void Rollback() + { + } + + public void Dispose() + { + } +} + +public sealed class FakeDbCommand : IDbCommand +{ + public FakeDbCommand(IDbConnection connection) + { + Connection = connection; + Parameters = new FakeParameterCollection(); + } + + public string CommandText { get; set; } = string.Empty; + + public int CommandTimeout { get; set; } + + public CommandType CommandType { get; set; } + + public IDbConnection Connection { get; set; } + + public IDataParameterCollection Parameters { get; } + + public IDbTransaction? Transaction { get; set; } + + public UpdateRowSource UpdatedRowSource { get; set; } + + public void Cancel() + { + } + + public IDbDataParameter CreateParameter() + { + throw new System.NotSupportedException(); + } + + public void Dispose() + { + } + + public int ExecuteNonQuery() + { + throw new System.NotSupportedException(); + } + + public IDataReader ExecuteReader() + { + throw new System.NotSupportedException(); + } + + public IDataReader ExecuteReader(CommandBehavior behavior) + { + throw new System.NotSupportedException(); + } + + public object ExecuteScalar() + { + throw new System.NotSupportedException(); + } + + public void Prepare() + { + } +} + +public sealed class FakeParameterCollection : List, IDataParameterCollection +{ + public object this[string parameterName] + { + get => throw new System.NotSupportedException(); + set => throw new System.NotSupportedException(); + } + + public bool Contains(string parameterName) + { + return false; + } + + public int IndexOf(string parameterName) + { + return -1; + } + + public void RemoveAt(string parameterName) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.DapperExtensions.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.DapperExtensions.ServiceCollectionExtensions.md new file mode 100644 index 0000000..11309ab --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.DapperExtensions.ServiceCollectionExtensions.md @@ -0,0 +1,199 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.DapperExtensions.ServiceCollectionExtensions +example: +- *content +--- +Registering a DapperExtensions data store in Savvy I/O requires a connection factory from `AddDapperDataSource` (in the `Savvyio.Extensions.DependencyInjection.Dapper` namespace) and a data store bound via `AddDapperExtensionsDataStore`. The data source must be registered first; the data store registration picks up the same connection factory. The example registers both services, builds the provider, and resolves the data store by its service interface. + +```csharp +using System.Collections; +using System.Collections.Generic; +using System.Data; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Data; +using Savvyio.Extensions.DapperExtensions; +using Savvyio.Extensions.DependencyInjection.Dapper; +using Savvyio.Extensions.DependencyInjection.DapperExtensions; + +namespace ExampleApp; + +public sealed class DapperExtensionsRegistrationExample +{ + public ServiceProvider BuildProvider() + { + var services = new ServiceCollection(); + services.AddDapperDataSource(options => options.ConnectionFactory = () => new FakeDbConnection()); + services.AddDapperExtensionsDataStore(); + + return services.BuildServiceProvider(); + } + + public bool HasExpectedRegistration(ServiceProvider provider) + { + var store = provider.GetRequiredService>>(); + return store is DapperExtensionsDataStore; + } +} + +public sealed class OrderProjection +{ + public long Id { get; set; } + + public string Status { get; set; } = string.Empty; +} + +public sealed class FakeDbConnection : IDbConnection +{ + private ConnectionState _state; + + public string ConnectionString { get; set; } = "Data Source=fake;"; + + public int ConnectionTimeout => 30; + + public string Database => "Fake"; + + public ConnectionState State => _state; + + public IDbTransaction BeginTransaction() + { + return new FakeDbTransaction(this, IsolationLevel.ReadCommitted); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return new FakeDbTransaction(this, il); + } + + public void ChangeDatabase(string databaseName) + { + } + + public void Close() + { + _state = ConnectionState.Closed; + } + + public IDbCommand CreateCommand() + { + return new FakeDbCommand(this); + } + + public void Open() + { + _state = ConnectionState.Open; + } + + public void Dispose() + { + Close(); + } +} + +public sealed class FakeDbTransaction : IDbTransaction +{ + public FakeDbTransaction(IDbConnection connection, IsolationLevel isolationLevel) + { + Connection = connection; + IsolationLevel = isolationLevel; + } + + public IDbConnection Connection { get; } + + public IsolationLevel IsolationLevel { get; } + + public void Commit() + { + } + + public void Rollback() + { + } + + public void Dispose() + { + } +} + +public sealed class FakeDbCommand : IDbCommand +{ + public FakeDbCommand(IDbConnection connection) + { + Connection = connection; + Parameters = new FakeParameterCollection(); + } + + public string CommandText { get; set; } = string.Empty; + + public int CommandTimeout { get; set; } + + public CommandType CommandType { get; set; } + + public IDbConnection Connection { get; set; } + + public IDataParameterCollection Parameters { get; } + + public IDbTransaction? Transaction { get; set; } + + public UpdateRowSource UpdatedRowSource { get; set; } + + public void Cancel() + { + } + + public IDbDataParameter CreateParameter() + { + throw new System.NotSupportedException(); + } + + public void Dispose() + { + } + + public int ExecuteNonQuery() + { + throw new System.NotSupportedException(); + } + + public IDataReader ExecuteReader() + { + throw new System.NotSupportedException(); + } + + public IDataReader ExecuteReader(CommandBehavior behavior) + { + throw new System.NotSupportedException(); + } + + public object ExecuteScalar() + { + throw new System.NotSupportedException(); + } + + public void Prepare() + { + } +} + +public sealed class FakeParameterCollection : List, IDataParameterCollection +{ + public object this[string parameterName] + { + get => throw new System.NotSupportedException(); + set => throw new System.NotSupportedException(); + } + + public bool Contains(string parameterName) + { + return false; + } + + public int IndexOf(string parameterName) + { + return -1; + } + + public void RemoveAt(string parameterName) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Data.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Data.ServiceCollectionExtensions.md new file mode 100644 index 0000000..7082a6f --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Data.ServiceCollectionExtensions.md @@ -0,0 +1,76 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Data.ServiceCollectionExtensions +example: +- *content +--- +Register a custom data source and persistent data store with `AddDataStore` for application read and write models. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Cuemon.Threading; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Data; +using Savvyio.Extensions.DependencyInjection.Data; + +namespace ExampleApp; + +public static class DataStoreRegistration +{ + public static IServiceCollection AddOrdersDataStore(this IServiceCollection services) + { + services.AddDataSource(); + services.AddDataStore(); + return services; + } +} + +public sealed class InMemoryDataSource : Savvyio.IDataSource +{ +} + +public sealed class OrderDocument +{ + public string Id { get; init; } = string.Empty; +} + +public sealed class OrderDataStore : IPersistentDataStore +{ + public OrderDataStore(Savvyio.IDataSource dataSource) + { + } + + public Task CreateAsync(OrderDocument dto, Action setup = null) + { + return Task.CompletedTask; + } + + public Task DeleteAsync(OrderDocument dto, Action setup = null) + { + return Task.CompletedTask; + } + + public Task> FindAllAsync(Action setup = null) + { + return Task.FromResult>(Array.Empty()); + } + + public Task FindAsync(Action setup = null) + { + return Task.FromResult(new OrderDocument()); + } + + public Task GetByIdAsync(object id, Action setup = null) + { + return Task.FromResult(new OrderDocument { Id = id?.ToString() ?? string.Empty }); + } + + public Task UpdateAsync(OrderDocument dto, Action setup = null) + { + return Task.CompletedTask; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Domain.EventSourcing.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Domain.EventSourcing.ServiceCollectionExtensions.md new file mode 100644 index 0000000..28e4e0d --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Domain.EventSourcing.ServiceCollectionExtensions.md @@ -0,0 +1,64 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Domain.EventSourcing.ServiceCollectionExtensions +example: +- *content +--- +`AddTracedAggregateRepository` binds `ITracedAggregateRepository` to its implementation. Call it alongside `AddDataSource` and `AddUnitOfWork` to complete the event-sourcing persistence registration. + +```csharp +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Cuemon.Threading; +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Domain; +using Savvyio.Domain.EventSourcing; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.Domain; +using Savvyio.Extensions.DependencyInjection.Domain.EventSourcing; + +namespace ExampleApp; + +public static class EventSourcingRegistration +{ + public static IServiceCollection Configure(IServiceCollection services) + { + services.AddDataSource(); + services.AddUnitOfWork(); + services.AddTracedAggregateRepository(); + return services; + } +} + +public sealed class EventStoreSource : Savvyio.IDataSource, IUnitOfWork +{ + public Task SaveChangesAsync(Action setup = null) => Task.CompletedTask; +} + +public sealed class OrderHistory : ITracedAggregateRoot +{ + public long Version => 0; + public IReadOnlyList Events => Array.Empty(); + public void RemoveAllEvents() { } + public IMetadataDictionary Metadata { get; } = new MetadataDictionary(); + public Guid Id { get; } = Guid.NewGuid(); +} + +public sealed class OrderHistoryRepository : ITracedAggregateRepository +{ + public OrderHistoryRepository(Savvyio.IDataSource source) { } + public void Add(OrderHistory entity) { } + public void AddRange(IEnumerable entities) { } + public void Remove(OrderHistory entity) { } + public void RemoveRange(IEnumerable entities) { } + public Task FindAsync(Expression> predicate, Action setup = null) => Task.FromResult(new OrderHistory()); + public Task> FindAllAsync(Expression> predicate = null, Action setup = null) => Task.FromResult>(Array.Empty()); + public Task GetByIdAsync(Guid id, Action setup = null) => Task.FromResult(new OrderHistory()); +} +``` + + + + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Domain.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Domain.ServiceCollectionExtensions.md new file mode 100644 index 0000000..03f0b61 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Domain.ServiceCollectionExtensions.md @@ -0,0 +1,89 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Domain.ServiceCollectionExtensions +example: +- *content +--- +Registering DDD aggregate repositories in Savvy I/O involves `AddAggregateRepository` for aggregate persistence, `AddRepository` for read-model repositories, and `AddUnitOfWork` for coordinating saves. Each call takes the service interface, entity type, and key type as type arguments. The example registers all three patterns alongside a shared data source and resolves each service to verify the full domain persistence wiring. + +```csharp +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Cuemon.Threading; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Domain; +using Savvyio.Extensions.DependencyInjection.Domain; + +namespace ExampleApp; + +public static class DomainRegistration +{ + public static IServiceCollection AddOrderPersistence(this IServiceCollection services) + { + services.AddDataSource(); + services.AddUnitOfWork(); + services.AddAggregateRepository(); + services.AddRepository(); + return services; + } +} + +public sealed class OrderDataSource : IDataSource, IUnitOfWork +{ + public Task SaveChangesAsync(Action setup = null) + { + return Task.CompletedTask; + } +} + +public sealed class OrderAggregate : AggregateRoot +{ + public OrderAggregate(Guid id, string customerId) : base(id) + { + CustomerId = customerId; + } + + public string CustomerId { get; private set; } +} + +public sealed class OrderRepository : IAggregateRepository +{ + public OrderRepository(Savvyio.IDataSource dataSource) + { + } + + public void Add(OrderAggregate entity) + { + } + + public void AddRange(IEnumerable entities) + { + } + + public Task> FindAllAsync(Expression> predicate = null, Action setup = null) + { + return Task.FromResult>(Array.Empty()); + } + + public Task FindAsync(Expression> predicate, Action setup = null) + { + return Task.FromResult(new OrderAggregate(Guid.NewGuid(), "ALFKI")); + } + + public Task GetByIdAsync(Guid id, Action setup = null) + { + return Task.FromResult(new OrderAggregate(id, "ALFKI")); + } + + public void Remove(OrderAggregate entity) + { + } + + public void RemoveRange(IEnumerable entities) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EfCoreAggregateDataSource`1.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EfCoreAggregateDataSource`1.md new file mode 100644 index 0000000..4e4f251 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EfCoreAggregateDataSource`1.md @@ -0,0 +1,60 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.Domain.EfCoreAggregateDataSource`1 +example: +- *content +--- +This example shows how `EfCoreAggregateDataSource` can be resolved from DI when an aggregate store needs a marker-specific pipeline. + +```csharp +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Domain; +using Savvyio.Extensions.DependencyInjection.EFCore; +using Savvyio.Extensions.DependencyInjection.EFCore.Domain; + +namespace ExampleApp; + +public sealed class MarkerAggregateDataSourceExample +{ + public bool IsResolvedAsMarkedAggregateSource() + { + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddEfCoreAggregateDataSource(options => + { + options.ContextConfigurator = builder => builder.EnableDetailedErrors(); + options.ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(order => order.Id); + }); + + using var provider = services.BuildServiceProvider(); + var source = provider.GetRequiredService>(); + + return source is EfCoreAggregateDataSource; + } +} + +public sealed class OrderingMarker +{ +} + +public sealed class RecordingDomainEventDispatcher : IDomainEventDispatcher +{ + public void Raise(IDomainEvent request) + { + } + + public Task RaiseAsync(IDomainEvent request, Action? setup = null) + { + return Task.CompletedTask; + } +} + +public sealed class OrderAggregate : Aggregate, IAggregateRoot +{ + public OrderAggregate(Guid id) : base(id) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EfCoreAggregateRepository`3.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EfCoreAggregateRepository`3.md new file mode 100644 index 0000000..a496cd4 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EfCoreAggregateRepository`3.md @@ -0,0 +1,48 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.Domain.EfCoreAggregateRepository`3 +example: +- *content +--- +The DI-registered `EfCoreAggregateRepository` resolves as `IAggregateRepository` when registered via `AddEfCoreAggregateRepository`. The aggregate data source registered by `AddEfCoreAggregateDataSource` provides the context. The example registers both services and resolves the repository interface. + +```csharp +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Domain; +using Savvyio.Extensions.DependencyInjection.Domain; +using Savvyio.Extensions.DependencyInjection.EFCore; +using Savvyio.Extensions.DependencyInjection.EFCore.Domain; + +namespace ExampleApp; + +public sealed class MarkerAggregateRepositoryExample +{ + public bool IsResolvedAsMarkedAggregateRepository() + { + var services = new ServiceCollection(); + services.AddEfCoreDataSource(options => + { + options.ContextConfigurator = builder => builder.EnableDetailedErrors(); + options.ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(order => order.Id); + }); + services.AddEfCoreAggregateRepository(); + + using var provider = services.BuildServiceProvider(); + var repository = provider.GetRequiredService>(); + + return repository is EfCoreAggregateRepository; + } +} + +public sealed class OrderingMarker +{ +} + +public sealed class OrderAggregate : Aggregate, IAggregateRoot +{ + public OrderAggregate(Guid id) : base(id) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository`3.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository`3.md new file mode 100644 index 0000000..0538935 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository`3.md @@ -0,0 +1,91 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository`3 +example: +- *content +--- +This example shows how `EfCoreTracedAggregateRepository` can be resolved when event-sourced aggregates need both a marker and a marshaller. + +```csharp +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Domain.EventSourcing; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.Domain.EventSourcing; +using Savvyio.Extensions.DependencyInjection.EFCore; +using Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing; +using Savvyio.Extensions.EFCore.Domain.EventSourcing; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class MarkerTracedRepositoryExample +{ + public bool IsResolvedAsMarkedTracedRepository() + { + var services = new ServiceCollection(); + services.AddMarshaller(); + services.AddEfCoreDataSource(options => + { + options.ContextConfigurator = builder => builder.EnableDetailedErrors(); + options.ModelConstructor = modelBuilder => modelBuilder.AddEventSourcing(); + }); + services.AddEfCoreTracedAggregateRepository(); + + using var provider = services.BuildServiceProvider(); + var repository = provider.GetRequiredService>(); + + return repository is EfCoreTracedAggregateRepository; + } +} + +public sealed class TimelineMarker +{ +} + +public sealed class OrderTimeline : TracedAggregateRoot +{ + public OrderTimeline(Guid id, string orderNumber) : base() + { + AddEvent(new OrderPlaced(id, orderNumber)); + } + + private OrderTimeline(Guid id, IEnumerable events) : base(id, events) + { + } + + protected override void RegisterDelegates(IFireForgetRegistry handler) + { + handler.Register(_ => { }); + } +} + +public sealed record OrderPlaced(Guid OrderId, string OrderNumber) : TracedDomainEvent; + +public sealed class SimpleMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value)); + } + + public Stream Serialize(object value, Type inputType) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value, inputType)); + } + + public TValue Deserialize(Stream data) + { + return JsonSerializer.Deserialize(data)!; + } + + public object Deserialize(Stream data, Type returnType) + { + return JsonSerializer.Deserialize(data, returnType)!; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.ServiceCollectionExtensions.md new file mode 100644 index 0000000..b08c73c --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.ServiceCollectionExtensions.md @@ -0,0 +1,90 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing.ServiceCollectionExtensions +example: +- *content +--- +Event-sourced aggregates backed by EF Core need a data source registered by `AddEfCoreAggregateDataSource` and a traced repository registered by `AddEfCoreTracedAggregateRepository`. The schema must be configured separately in `OnModelCreating` using `ModelBuilderExtensions.AddEventSourcing`. The example registers both services alongside the marshaller and resolves the traced repository interface to verify the full event-sourcing registration. + +```csharp +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Domain.EventSourcing; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.Domain.EventSourcing; +using Savvyio.Extensions.DependencyInjection.EFCore; +using Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing; +using Savvyio.Extensions.EFCore.Domain.EventSourcing; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class TracedRegistrationExample +{ + public ServiceProvider BuildProvider() + { + var services = new ServiceCollection(); + services.AddMarshaller(); + services.AddEfCoreDataSource(options => + { + options.ContextConfigurator = builder => builder.EnableDetailedErrors(); + options.ModelConstructor = modelBuilder => modelBuilder.AddEventSourcing(); + }); + services.AddEfCoreTracedAggregateRepository(); + + return services.BuildServiceProvider(); + } + + public bool HasExpectedRegistration(ServiceProvider provider) + { + var repository = provider.GetRequiredService>(); + return repository is Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository; + } +} + +public sealed class OrderTimeline : TracedAggregateRoot +{ + public OrderTimeline(Guid id, string orderNumber) : base() + { + AddEvent(new OrderPlaced(id, orderNumber)); + } + + private OrderTimeline(Guid id, IEnumerable events) : base(id, events) + { + } + + protected override void RegisterDelegates(IFireForgetRegistry handler) + { + handler.Register(_ => { }); + } +} + +public sealed record OrderPlaced(Guid OrderId, string OrderNumber) : TracedDomainEvent; + +public sealed class SimpleMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value)); + } + + public Stream Serialize(object value, Type inputType) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value, inputType)); + } + + public TValue Deserialize(Stream data) + { + return JsonSerializer.Deserialize(data)!; + } + + public object Deserialize(Stream data, Type returnType) + { + return JsonSerializer.Deserialize(data, returnType)!; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.ServiceCollectionExtensions.md new file mode 100644 index 0000000..01df240 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.Domain.ServiceCollectionExtensions.md @@ -0,0 +1,62 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.Domain.ServiceCollectionExtensions +example: +- *content +--- +Registering domain aggregates backed by EF Core requires two registrations: `AddEfCoreAggregateDataSource` to bind the aggregate context as `IEfCoreDataSource`, and `AddEfCoreAggregateRepository` to add the repository on top. The aggregate data source sets the boundary for which context the repository uses; it must be registered first. The example registers both services and resolves the repository interface to confirm the aggregate wiring. + +```csharp +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Domain; +using Savvyio.Extensions.DependencyInjection.Domain; +using Savvyio.Extensions.DependencyInjection.EFCore; +using Savvyio.Extensions.DependencyInjection.EFCore.Domain; + +namespace ExampleApp; + +public sealed class AggregateRegistrationExample +{ + public ServiceProvider BuildProvider() + { + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddEfCoreAggregateDataSource(options => + { + options.ContextConfigurator = builder => builder.EnableDetailedErrors(); + options.ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(order => order.Id); + }); + services.AddEfCoreAggregateRepository(); + + return services.BuildServiceProvider(); + } + + public bool HasExpectedRegistrations(ServiceProvider provider) + { + var repository = provider.GetRequiredService>(); + + return repository is Savvyio.Extensions.EFCore.Domain.EfCoreAggregateRepository; + } +} + +public sealed class RecordingDomainEventDispatcher : IDomainEventDispatcher +{ + public void Raise(IDomainEvent request) + { + } + + public Task RaiseAsync(IDomainEvent request, Action? setup = null) + { + return Task.CompletedTask; + } +} + +public sealed class OrderAggregate : Aggregate, IAggregateRoot +{ + public OrderAggregate(Guid id) : base(id) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataSourceOptions`1.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataSourceOptions`1.md new file mode 100644 index 0000000..1f4894e --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataSourceOptions`1.md @@ -0,0 +1,46 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataSourceOptions`1 +example: +- *content +--- +`EfCoreDataSourceOptions` carries the `ContextConfigurator` and service lifetime values used by `AddEfCoreDataSource` to configure the `DbContextOptionsBuilder`. Set `ContextConfigurator` to a lambda that calls `UseInMemoryDatabase` or `UseSqlServer` on the builder. The example configures an in-memory database and demonstrates how the options type flows into the DI registration. + +```csharp +using System; +using Microsoft.EntityFrameworkCore; +using Savvyio; +using Savvyio.Extensions.DependencyInjection.EFCore; + +namespace ExampleApp; + +public sealed class MarkerOptionsExample +{ + public EfCoreDbContext CreateContext() + { + var options = new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableSensitiveDataLogging(), + ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(product => product.Id) + }; + + return new EfCoreDbContext(options); + } +} + +public sealed class CatalogMarker +{ +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name) + { + Id = id; + Name = name; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataSource`1.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataSource`1.md new file mode 100644 index 0000000..9b49dc9 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataSource`1.md @@ -0,0 +1,51 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataSource`1 +example: +- *content +--- +The DI-registered `EfCoreDataSource` is the concrete `IEfCoreDataSource` bound by `AddEfCoreDataSource`. Resolve it as `IEfCoreDataSource` to access the `DbContext` factory or pass it to EF Core repositories. The example registers the data source with an in-memory provider and resolves it from the service provider. + +```csharp +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Extensions.DependencyInjection.EFCore; + +namespace ExampleApp; + +public sealed class MarkerBasedDataSourceExample +{ + public bool IsResolvedAsMarkedSource() + { + var services = new ServiceCollection(); + services.AddEfCoreDataSource(options => + { + options.ContextConfigurator = builder => builder.EnableDetailedErrors(); + options.ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(product => product.Id); + }); + + using var provider = services.BuildServiceProvider(); + var source = provider.GetRequiredService>(); + + return source is EfCoreDataSource; + } +} + +public sealed class CatalogMarker +{ +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name) + { + Id = id; + Name = name; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataStore`2.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataStore`2.md new file mode 100644 index 0000000..ba29154 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataStore`2.md @@ -0,0 +1,55 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDataStore`2 +example: +- *content +--- +The DI-registered `EfCoreDataStore` resolves as `IPersistentDataStore>` when registered via `AddEfCoreDataStore`. The data source registered by `AddEfCoreDataSource` provides the context; the concrete store type adds entity-specific query logic. The example resolves the store from the provider and confirms the registered concrete type. + +```csharp +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Data; +using Savvyio.Extensions.DependencyInjection.Data; +using Savvyio.Extensions.DependencyInjection.EFCore; +using Savvyio.Extensions.EFCore; + +namespace ExampleApp; + +public sealed class MarkerBasedDataStoreExample +{ + public bool IsResolvedAsMarkedStore() + { + var services = new ServiceCollection(); + services.AddEfCoreDataSource(options => + { + options.ContextConfigurator = builder => builder.EnableDetailedErrors(); + options.ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(product => product.Id); + }); + services.AddEfCoreDataStore(); + + using var provider = services.BuildServiceProvider(); + var store = provider.GetRequiredService, CatalogMarker>>(); + + return store is EfCoreDataStore; + } +} + +public sealed class CatalogMarker +{ +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name) + { + Id = id; + Name = name; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDbContext`1.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDbContext`1.md new file mode 100644 index 0000000..12159ae --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDbContext`1.md @@ -0,0 +1,55 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.EfCoreDbContext`1 +example: +- *content +--- +This example shows how `EfCoreDbContext` can be used when a DI marker needs its own EF Core context instance. + +```csharp +using System; +using Microsoft.EntityFrameworkCore; +using Savvyio; +using Savvyio.Extensions.DependencyInjection.EFCore; + +namespace ExampleApp; + +public sealed class MarkerContextFactory +{ + public OrdersDbContext Create() + { + var options = new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableDetailedErrors(), + ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(order => order.Id) + }; + + return new OrdersDbContext(options); + } +} + +public sealed class OrdersDbContext : EfCoreDbContext +{ + public OrdersDbContext(EfCoreDataSourceOptions options) : base(options) + { + } + + public DbSet Orders => Set(); +} + +public sealed class OrdersMarker +{ +} + +public sealed class OrderRecord : IIdentity +{ + public OrderRecord(Guid id, string orderNumber) + { + Id = id; + OrderNumber = orderNumber; + } + + public Guid Id { get; } + + public string OrderNumber { get; private set; } = string.Empty; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreRepository`3.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreRepository`3.md new file mode 100644 index 0000000..d0a1a3b --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.EfCoreRepository`3.md @@ -0,0 +1,53 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.EfCoreRepository`3 +example: +- *content +--- +The DI-registered `EfCoreRepository` resolves as `IRepository` when registered via `AddEfCoreRepository`. The data source registered by `AddEfCoreDataSource` provides the context. The example registers and resolves the repository to verify the complete EF Core repository wiring. + +```csharp +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Extensions.DependencyInjection.Domain; +using Savvyio.Extensions.DependencyInjection.EFCore; + +namespace ExampleApp; + +public sealed class MarkerRepositoryExample +{ + public bool IsResolvedAsMarkedRepository() + { + var services = new ServiceCollection(); + services.AddEfCoreDataSource(options => + { + options.ContextConfigurator = builder => builder.EnableDetailedErrors(); + options.ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(product => product.Id); + }); + services.AddEfCoreRepository(); + + using var provider = services.BuildServiceProvider(); + var repository = provider.GetRequiredService>(); + + return repository is EfCoreRepository; + } +} + +public sealed class CatalogMarker +{ +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name) + { + Id = id; + Name = name; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.ServiceCollectionExtensions.md new file mode 100644 index 0000000..7cc7bca --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.EFCore.ServiceCollectionExtensions.md @@ -0,0 +1,60 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.EFCore.ServiceCollectionExtensions +example: +- *content +--- +Composing an EF Core data layer in Savvy I/O requires three registrations: `AddEfCoreDataSource` to bind the `DbContext` and `IEfCoreDataSource`, `AddEfCoreDataStore` for generic read/write stores, and `AddEfCoreRepository` for aggregate-scoped repositories. Each registration takes the concrete type as a type argument, and the data source must be registered first because stores and repositories depend on it. The example resolves both a repository and a data store from the built service provider to confirm the bindings are correct. + +```csharp +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Data; +using Savvyio.Domain; +using Savvyio.Extensions.DependencyInjection.EFCore; +using Savvyio.Extensions.EFCore; + +namespace ExampleApp; + +public sealed class RegistrationExample +{ + public ServiceProvider BuildProvider() + { + var services = new ServiceCollection(); + services.AddEfCoreDataSource(options => + { + options.ContextConfigurator = builder => builder.EnableDetailedErrors(); + options.ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(product => product.Id); + }); + services.AddEfCoreDataStore(); + services.AddEfCoreRepository(); + + return services.BuildServiceProvider(); + } + + public bool HasExpectedRegistrations(ServiceProvider provider) + { + var dataSource = provider.GetRequiredService(); + var dataStore = provider.GetRequiredService>>(); + var repository = provider.GetRequiredService>(); + + return dataSource is EfCoreDataSource && + dataStore is EfCoreDataStore && + repository is EfCoreRepository; + } +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name) + { + Id = id; + Name = name; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Messaging.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Messaging.ServiceCollectionExtensions.md new file mode 100644 index 0000000..15d1438 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Messaging.ServiceCollectionExtensions.md @@ -0,0 +1,58 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Messaging.ServiceCollectionExtensions +example: +- *content +--- +Registering transport-agnostic messaging in Savvy I/O uses `AddMessageQueue` for point-to-point command delivery and `AddMessageBus` for publish-subscribe event delivery. Both methods accept the service interface and the concrete implementation type, enabling seamless transport swapping between in-memory, RabbitMQ, NATS, and cloud brokers. The example registers both channels with concrete implementations and resolves each through its abstract interface. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Cuemon.Threading; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Commands; +using Savvyio.EventDriven; +using Savvyio.Extensions.DependencyInjection.Messaging; +using Savvyio.Messaging; + +namespace ExampleApp; + +public static class MessagingRegistration +{ + public static IServiceCollection AddMessagingChannels(this IServiceCollection services) + { + services.AddMessageQueue(); + services.AddMessageBus(); + return services; + } +} + +public sealed class OrderCommandQueue : IPointToPointChannel +{ + public async IAsyncEnumerable> ReceiveAsync(Action setup = null) + { + await Task.CompletedTask; + yield break; + } + + public Task SendAsync(IEnumerable> messages, Action setup = null) + { + return Task.CompletedTask; + } +} + +public sealed class OrderEventBus : IPublishSubscribeChannel +{ + public Task PublishAsync(IMessage message, Action setup = null) + { + return Task.CompletedTask; + } + + public Task SubscribeAsync(Func, CancellationToken, Task> asyncHandler, Action setup = null) + { + return Task.CompletedTask; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.Commands.NatsCommandQueue.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.Commands.NatsCommandQueue.md new file mode 100644 index 0000000..c547923 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.Commands.NatsCommandQueue.md @@ -0,0 +1,39 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.NATS.Commands.NatsCommandQueue`1 +example: +- *content +--- +`NatsCommandQueue` is the DI-registered NATS command queue with a lifetime marker. Register it with `AddNatsCommandQueue` and resolve it as `NatsCommandQueue` or `IPointToPointChannel`. + +```csharp +using System; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Commands; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.NATS; +using Savvyio.Extensions.NATS.Commands; +using Savvyio.Messaging; + +namespace ExampleApp; + +public static class NatsQueueUsage +{ + public static NatsCommandQueue GetQueue(IServiceProvider provider) + { + return provider.GetRequiredService(); + } + + public static IPointToPointChannel GetChannel(IServiceProvider provider) + { + return provider.GetRequiredService>(); + } + + public static IServiceCollection Register(IServiceCollection services) + { + services.AddSavvyIO(); + services.AddNatsCommandQueue(o => { o.Subject = "commands"; }); + return services; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.Commands.NatsCommandQueueOptions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.Commands.NatsCommandQueueOptions.md new file mode 100644 index 0000000..52ad703 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.Commands.NatsCommandQueueOptions.md @@ -0,0 +1,27 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.NATS.Commands.NatsCommandQueueOptions`1 +example: +- *content +--- +`NatsCommandQueueOptions` carries the NATS JetStream stream, consumer, subject, and connection settings for a DI-registered NATS command queue. + +```csharp +using System; +using Savvyio.Extensions.NATS.Commands; + +namespace ExampleApp; + +public class NatsQueueOptionsExample +{ + public static NatsCommandQueueOptions CreateOptions() + { + return new NatsCommandQueueOptions + { + NatsUrl = new Uri("nats://localhost:4222"), + Subject = "account-commands", + StreamName = "savvyio-commands", + ConsumerName = "account-consumer" + }; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.NatsEventBus.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.NatsEventBus.md new file mode 100644 index 0000000..54fa09e --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.NatsEventBus.md @@ -0,0 +1,40 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.NATS.EventDriven.NatsEventBus`1 +example: +- *content +--- +`NatsEventBus` is the DI-registered NATS event bus with a lifetime marker. Register it with `AddNatsEventBus` and resolve it as `NatsEventBus` or `IPublishSubscribeChannel`. + +```csharp +using System; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.EventDriven; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.NATS; +using Savvyio.Extensions.NATS.EventDriven; +using Savvyio.Messaging; + +namespace ExampleApp; + +public static class NatsBusUsage +{ + public static NatsEventBus GetBus(IServiceProvider provider) + { + return provider.GetRequiredService(); + } + + public static IPublishSubscribeChannel GetChannel(IServiceProvider provider) + { + return provider.GetRequiredService>(); + } + + public static IServiceCollection Register(IServiceCollection services) + { + services.AddSavvyIO(); + services.AddNatsEventBus(o => { o.NatsUrl = new Uri("nats://localhost:4222"); o.Subject = "events"; }); + return services; + } +} +``` + + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.NatsEventBusOptions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.NatsEventBusOptions.md new file mode 100644 index 0000000..e5fd22d --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.EventDriven.NatsEventBusOptions.md @@ -0,0 +1,26 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.NATS.EventDriven.NatsEventBusOptions`1 +example: +- *content +--- +`NatsEventBusOptions` (or its DI generic variant) carries the NATS server URL and subject settings used when `AddNatsEventBus` registers the NATS event bus in DI. + +```csharp +using System; +using Savvyio.Extensions.NATS.EventDriven; + +namespace ExampleApp; + +public class NatsEventBusOptionsExample +{ + public static NatsEventBusOptions CreateOptions() + { + return new NatsEventBusOptions + { + NatsUrl = new Uri("nats://localhost:4222"), + Subject = "events.account" + }; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.ServiceCollectionExtensions.md new file mode 100644 index 0000000..0ac4083 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.NATS.ServiceCollectionExtensions.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.NATS.ServiceCollectionExtensions +example: +- *content +--- +Register NATS command queues and event buses in the DI container with `AddNatsCommandQueue` and `AddNatsEventBus`. Both methods configure NATS JetStream settings and register the concrete queue/bus types. + +```csharp +using System; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.NATS; + +namespace ExampleApp; + +public static class NatsRegistration +{ + public static IServiceCollection Configure(IServiceCollection services) + { + services.AddSavvyIO(); + services.AddNatsCommandQueue(options => + { + options.NatsUrl = new Uri("nats://localhost:4222"); + options.Subject = "account-commands"; + options.StreamName = "savvyio-commands"; + }); + services.AddNatsEventBus(options => + { + options.NatsUrl = new Uri("nats://localhost:4222"); + options.Subject = "account-events"; + }); + return services; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json.ServiceCollectionExtensions.md new file mode 100644 index 0000000..57dc939 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json.ServiceCollectionExtensions.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Newtonsoft.Json.ServiceCollectionExtensions +example: +- *content +--- +Register `NewtonsoftJsonMarshaller` in the DI container with `AddNewtonsoftJsonMarshaller`. + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Savvyio.Extensions.DependencyInjection.Newtonsoft.Json; + +namespace ExampleApp; + +public static class JsonDependencyInjectionSetup +{ + public static IServiceCollection AddNewtonsoftSerialization(this IServiceCollection services) + { + services.AddNewtonsoftJsonMarshaller(options => + { + options.Settings.Formatting = Formatting.Indented; + options.Settings.NullValueHandling = NullValueHandling.Ignore; + }); + + return services; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.AzureQueueOptions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.AzureQueueOptions.md new file mode 100644 index 0000000..eb93fb2 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.AzureQueueOptions.md @@ -0,0 +1,24 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.QueueStorage.AzureQueueOptions`1 +example: +- *content +--- +`AzureQueueOptions` configured via `AddAzureCommandQueue` or `AddAzureEventBus` controls the Azure Storage connection and queue name used by the registered service. + +```csharp +using Savvyio.Extensions.QueueStorage; + +namespace ExampleApp; + +public class DependencyInjectionQueueOptionsExample +{ + public static AzureQueueOptions CreateOptions() + { + return new AzureQueueOptions + { + ConnectionString = "UseDevelopmentStorage=true", + QueueName = "account-commands" + }; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.Commands.AzureCommandQueue.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.Commands.AzureCommandQueue.md new file mode 100644 index 0000000..e89e650 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.Commands.AzureCommandQueue.md @@ -0,0 +1,29 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.QueueStorage.Commands.AzureCommandQueue`1 +example: +- *content +--- +`AzureCommandQueue` is the DI-registered Azure Queue Storage command queue with a lifetime marker. It implements both `ISender` and `IReceiver`. Inject it directly or through one of those interfaces to send and receive commands via Azure Storage queues. + +```csharp +using Savvyio.Commands; +using Savvyio.Extensions.QueueStorage.Commands; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class AzureCommandService +{ + private readonly ISender _sender; + private readonly IReceiver _receiver; + + public AzureCommandService(AzureCommandQueue queue) + { + _sender = queue; + _receiver = queue; + } + + public ISender Sender => _sender; + public IReceiver Receiver => _receiver; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.AzureEventBus.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.AzureEventBus.md new file mode 100644 index 0000000..f9488ab --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.AzureEventBus.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.AzureEventBus`1 +example: +- *content +--- +`AzureEventBus` is the DI-registered Azure Queue Storage event bus with a lifetime marker. It implements `IPublishSubscribeChannel`. Inject it to subscribe to and publish integration events through an Azure Storage queue. + +```csharp +using System; +using System.Threading.Tasks; +using Savvyio.EventDriven; +using Savvyio.Extensions.QueueStorage.EventDriven; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class EventListener +{ + public static async Task SubscribeAsync(AzureEventBus bus) + { + await bus.SubscribeAsync(async (message, token) => + { + Console.WriteLine($"Received event: {message.Type}"); + await Task.CompletedTask; + }).ConfigureAwait(false); + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.AzureEventBusOptions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.AzureEventBusOptions.md new file mode 100644 index 0000000..04143e8 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.AzureEventBusOptions.md @@ -0,0 +1,25 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.QueueStorage.EventDriven.AzureEventBusOptions`1 +example: +- *content +--- +`AzureEventBusOptions` (or its DI generic variant) carries the Azure Event Grid topic endpoint settings used when `AddAzureEventBus` registers the event bus in DI. + +```csharp +using System; +using Savvyio.Extensions.QueueStorage.EventDriven; + +namespace ExampleApp; + +public class DiAzureEventBusOptionsExample +{ + public static AzureEventBusOptions CreateOptions() + { + return new AzureEventBusOptions + { + TopicEndpoint = new Uri("https://myeventgridtopic.westeurope-1.eventgrid.azure.net/api/events") + }; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.ServiceCollectionExtensions.md new file mode 100644 index 0000000..d52b542 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.QueueStorage.ServiceCollectionExtensions.md @@ -0,0 +1,30 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.QueueStorage.ServiceCollectionExtensions +example: +- *content +--- +Register Azure Queue Storage command queues and event buses in the DI container with `AddAzureCommandQueue` and `AddAzureEventBus`. + +```csharp +using System; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.QueueStorage; + +namespace ExampleApp; + +public static class AzureQueueRegistration +{ + public static IServiceCollection Configure(IServiceCollection services) + { + services.AddSavvyIO(); + services.AddAzureCommandQueue( + o => { o.ConnectionString = "UseDevelopmentStorage=true"; o.QueueName = "commands"; }); + services.AddAzureEventBus( + azureQueueSetup: o => { o.ConnectionString = "UseDevelopmentStorage=true"; o.QueueName = "events"; }, + azureEventBusSetup: o => { o.TopicEndpoint = new Uri("https://myeventgridtopic.westeurope-1.eventgrid.azure.net/api/events"); }); + return services; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.RabbitMqCommandQueue.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.RabbitMqCommandQueue.md new file mode 100644 index 0000000..ddb9f80 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.RabbitMqCommandQueue.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.RabbitMqCommandQueue`1 +example: +- *content +--- +The `RabbitMqCommandQueue` registered by `AddRabbitMqCommandQueue` is the concrete DI-managed command queue; resolve it to access `SendAsync` and `ReceiveAsync`. + +```csharp +using System; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Commands; +using Savvyio.Extensions.RabbitMQ.Commands; +using Savvyio.Messaging; + +namespace ExampleApp; + +public class RabbitMqCommandQueueUsage +{ + private readonly RabbitMqCommandQueue _queue; + + public RabbitMqCommandQueueUsage(RabbitMqCommandQueue queue) + { + _queue = queue; + } + + public IPointToPointChannel AsChannel() => _queue; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.RabbitMqCommandQueueOptions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.RabbitMqCommandQueueOptions.md new file mode 100644 index 0000000..e9d7c71 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.RabbitMqCommandQueueOptions.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.RabbitMQ.Commands.RabbitMqCommandQueueOptions`1 +example: +- *content +--- +`RabbitMqCommandQueueOptions` carries the AMQP URL and queue settings used when `AddRabbitMqCommandQueue` registers the RabbitMQ command queue in DI. + +```csharp +using System; +using Savvyio.Extensions.RabbitMQ.Commands; + +namespace ExampleApp; + +public class RabbitMqCommandQueueOptionsExample +{ + public static RabbitMqCommandQueueOptions CreateOptions() + { + return new RabbitMqCommandQueueOptions + { + AmqpUrl = new Uri("amqp://guest:guest@localhost:5672"), + QueueName = "account-commands", + Durable = true, + Persistent = true, + AutoAcknowledge = false + }; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.RabbitMqEventBus.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.RabbitMqEventBus.md new file mode 100644 index 0000000..fab2100 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.RabbitMqEventBus.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.RabbitMqEventBus`1 +example: +- *content +--- +The `RabbitMqEventBus` registered by `AddRabbitMqEventBus` is the concrete DI-managed event bus; resolve it to access `PublishAsync` and `SubscribeAsync`. + +```csharp +using System; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.EventDriven; +using Savvyio.Extensions.RabbitMQ.EventDriven; +using Savvyio.Messaging; + +namespace ExampleApp; + +public class RabbitMqEventBusUsage +{ + private readonly RabbitMqEventBus _bus; + + public RabbitMqEventBusUsage(RabbitMqEventBus bus) + { + _bus = bus; + } + + public IPublishSubscribeChannel AsChannel() => _bus; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.RabbitMqEventBusOptions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.RabbitMqEventBusOptions.md new file mode 100644 index 0000000..3a66856 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.RabbitMqEventBusOptions.md @@ -0,0 +1,26 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.RabbitMQ.EventDriven.RabbitMqEventBusOptions`1 +example: +- *content +--- +`RabbitMqEventBusOptions` carries the AMQP URL and exchange settings used when `AddRabbitMqEventBus` registers the RabbitMQ event bus in DI. + +```csharp +using System; +using Savvyio.Extensions.RabbitMQ.EventDriven; + +namespace ExampleApp; + +public class RabbitMqEventBusOptionsExample +{ + public static RabbitMqEventBusOptions CreateOptions() + { + return new RabbitMqEventBusOptions + { + AmqpUrl = new Uri("amqp://guest:guest@localhost:5672"), + ExchangeName = "account-events", + Persistent = true + }; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.ServiceCollectionExtensions.md new file mode 100644 index 0000000..7c9a6a0 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.RabbitMQ.ServiceCollectionExtensions.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.RabbitMQ.ServiceCollectionExtensions +example: +- *content +--- +Register RabbitMQ command queues and event buses in the DI container with `AddRabbitMqCommandQueue` and `AddRabbitMqEventBus`. Both methods configure AMQP connection settings and register the concrete queue/bus types. + +```csharp +using System; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.RabbitMQ; + +namespace ExampleApp; + +public static class RabbitMqRegistration +{ + public static IServiceCollection Configure(IServiceCollection services) + { + services.AddSavvyIO(); + services.AddRabbitMqCommandQueue(options => + { + options.AmqpUrl = new Uri("amqp://guest:guest@localhost:5672"); + options.QueueName = "account-commands"; + options.Durable = true; + }); + services.AddRabbitMqEventBus(options => + { + options.AmqpUrl = new Uri("amqp://guest:guest@localhost:5672"); + options.ExchangeName = "account-events"; + }); + return services; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SavvyioDependencyInjectionOptions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SavvyioDependencyInjectionOptions.md new file mode 100644 index 0000000..716f791 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SavvyioDependencyInjectionOptions.md @@ -0,0 +1,36 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.SavvyioDependencyInjectionOptions +example: +- *content +--- +Configure a `SavvyioDependencyInjectionOptions` instance to control assembly scanning, handler and dispatcher discovery, and DI service lifetimes. + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Extensions.DependencyInjection; + +namespace ExampleApp; + +public static class DependencyInjectionSetup +{ + public static IServiceCollection AddApplicationMessaging(this IServiceCollection services) + { + services.AddSavvyIO(ConfigureOptions); + services.AddHandlerServicesDescriptor(); + return services; + } + + private static void ConfigureOptions(SavvyioDependencyInjectionOptions options) + { + options.AddAssemblyRangeToScan(typeof(SavvyioOptions).Assembly, typeof(DependencyInjectionSetup).Assembly); + options.EnableDispatcherDiscovery(); + options.EnableHandlerDiscovery(); + options.EnableHandlerServicesDescriptor(); + options.ServiceLocatorLifetime = ServiceLifetime.Singleton; + options.HandlerServicesLifetime = ServiceLifetime.Scoped; + options.DispatcherServicesLifetime = ServiceLifetime.Singleton; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceCollectionExtensions.md new file mode 100644 index 0000000..6354e54 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceCollectionExtensions.md @@ -0,0 +1,41 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.ServiceCollectionExtensions +example: +- *content +--- +Register Savvy I/O in the DI container with `AddSavvyIO`, add the handler descriptor, a service locator, configured options, a data source, and a marshaller — all through `IServiceCollection` extension methods. + +```csharp +using System.IO; +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Extensions.DependencyInjection; + +namespace ExampleApp; + +public static class ServiceRegistration +{ + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + services.AddSavvyIO(options => options.EnableHandlerServicesDescriptor()); + services.AddHandlerServicesDescriptor(); + services.AddServiceLocator(); + services.AddConfiguredOptions(_ => { }); + services.AddDataSource(); + services.AddMarshaller(p => new AppMarshaller()); + return services; + } +} + +public sealed class AppDataSource : IDataSource { } + +public sealed class AppMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) => Stream.Null; + public Stream Serialize(object value, System.Type inputType) => Stream.Null; + public TValue Deserialize(Stream data) => default!; + public object Deserialize(Stream data, System.Type returnType) => null!; +} +``` + + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceLocatorOptions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceLocatorOptions.md new file mode 100644 index 0000000..c90dbb4 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceLocatorOptions.md @@ -0,0 +1,30 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.ServiceLocatorOptions +example: +- *content +--- +Configure a `ServiceLocatorOptions` instance to provide a custom `IServiceLocator` factory, passed to `AddServiceLocator`. + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Dispatchers; +using Savvyio.Extensions.DependencyInjection; + +namespace ExampleApp; + +public static class ServiceLocatorSetup +{ + public static IServiceCollection AddLocator(this IServiceCollection services) + { + services.AddServiceLocator(ConfigureLocator); + return services; + } + + private static void ConfigureLocator(ServiceLocatorOptions options) + { + options.Lifetime = ServiceLifetime.Singleton; + options.ImplementationFactory = provider => new ServiceLocator(provider.GetServices); + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceProviderExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceProviderExtensions.md new file mode 100644 index 0000000..10f4e2a --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.ServiceProviderExtensions.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.ServiceProviderExtensions +example: +- *content +--- +`WriteHandlerDiscoveriesToLog` resolves `IHandlerServicesDescriptor` from the service provider and writes the handler discovery report to the named logger. Call it at startup after building the service provider. + +```csharp +using System; +using Microsoft.Extensions.DependencyInjection; +using Savvyio; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Handlers; + +namespace ExampleApp; + +public static class DependencyInjectionDiagnostics +{ + public static IServiceProvider BuildProvider() + { + var services = new ServiceCollection(); + services.AddSavvyIO(options => options.EnableHandlerServicesDescriptor()); + services.AddHandlerServicesDescriptor(); + return services.BuildServiceProvider(); + } + + public static void LogRegisteredHandlers(IServiceProvider provider) + { + provider.WriteHandlerDiscoveriesToLog(); + } +} + +public sealed class HandlerDiscoveryLogCategory { } +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.AmazonCommandQueue.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.AmazonCommandQueue.md new file mode 100644 index 0000000..026ce6d --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.AmazonCommandQueue.md @@ -0,0 +1,26 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.AmazonCommandQueue`1 +example: +- *content +--- +`AmazonCommandQueue` is the DI-registered Amazon SQS command queue with a lifetime marker. Inject it and use it as `IPointToPointChannel` to send and receive commands through Amazon SQS, or as `ISender` when only the send path is needed. + +```csharp +using Savvyio.Commands; +using Savvyio.Extensions.SimpleQueueService.Commands; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class SqsCommandService +{ + private readonly IPointToPointChannel _channel; + + public SqsCommandService(AmazonCommandQueue queue) + { + _channel = queue; + } + + public IPointToPointChannel Channel => _channel; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.AmazonCommandQueueOptions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.AmazonCommandQueueOptions.md new file mode 100644 index 0000000..6805932 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.AmazonCommandQueueOptions.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.SimpleQueueService.Commands.AmazonCommandQueueOptions`1 +example: +- *content +--- +`AmazonCommandQueueOptions` configures the DI-registered SQS command queue with AWS credentials, endpoint, and the SQS queue URL. The options are passed through the `AddAmazonCommandQueue` setup delegate. + +```csharp +using System; +using Amazon; +using Amazon.Runtime; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.SimpleQueueService; +using Savvyio.Extensions.SimpleQueueService.Commands; + +namespace ExampleApp; + +public class DiCommandQueueRegistration +{ + public static AmazonCommandQueueOptions CreateAndInspect() + { + var options = new AmazonCommandQueueOptions + { + Credentials = new AnonymousAWSCredentials(), + Endpoint = RegionEndpoint.EUWest1, + SourceQueue = new Uri("https://sqs.eu-west-1.amazonaws.com/123456789012/commands") + }; + Console.WriteLine($"Queue: {options.SourceQueue}"); + return options; + } +} +``` + + + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.AmazonEventBus.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.AmazonEventBus.md new file mode 100644 index 0000000..02899de --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.AmazonEventBus.md @@ -0,0 +1,32 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.AmazonEventBus`1 +example: +- *content +--- +`AmazonEventBus` is the DI-registered Amazon SNS/SQS event bus with a lifetime marker. Inject it as `IPublishSubscribeChannel` to publish events to an SNS topic and receive them via SQS. + +```csharp +using System; +using System.Threading.Tasks; +using Savvyio.EventDriven; +using Savvyio.Extensions.SimpleQueueService.EventDriven; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class SnsEventPublisher +{ + private readonly IPublishSubscribeChannel _bus; + + public SnsEventPublisher(AmazonEventBus bus) + { + _bus = bus; + } + + public Task PublishAsync(IMessage message) + { + return _bus.PublishAsync(message); + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.AmazonEventBusOptions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.AmazonEventBusOptions.md new file mode 100644 index 0000000..410b016 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.AmazonEventBusOptions.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.SimpleQueueService.EventDriven.AmazonEventBusOptions`1 +example: +- *content +--- +`AmazonEventBusOptions` configures the DI-registered SNS/SQS event bus with AWS credentials and the source queue URL. The options are passed through the `AddAmazonEventBus` setup delegate. + +```csharp +using System; +using Amazon; +using Amazon.Runtime; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.SimpleQueueService; +using Savvyio.Extensions.SimpleQueueService.EventDriven; + +namespace ExampleApp; + +public class DiEventBusRegistration +{ + public static AmazonEventBusOptions CreateAndInspect() + { + var options = new AmazonEventBusOptions + { + Credentials = new AnonymousAWSCredentials(), + Endpoint = RegionEndpoint.EUWest1, + SourceQueue = new Uri("https://sqs.eu-west-1.amazonaws.com/123456789012/events") + }; + Console.WriteLine($"Event queue: {options.SourceQueue}"); + return options; + } +} +``` + + + diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.ServiceCollectionExtensions.md new file mode 100644 index 0000000..1ecdd8c --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.SimpleQueueService.ServiceCollectionExtensions.md @@ -0,0 +1,38 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.SimpleQueueService.ServiceCollectionExtensions +example: +- *content +--- +Register Amazon SQS command queues and SNS event buses in the DI container with `AddAmazonCommandQueue` and `AddAmazonEventBus`. Both methods configure AWS credentials and resource settings. + +```csharp +using System; +using Amazon; +using Amazon.Runtime; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.SimpleQueueService; + +namespace ExampleApp; + +public static class AmazonRegistration +{ + public static IServiceCollection Configure(IServiceCollection services) + { + services.AddSavvyIO(); + services.AddAmazonCommandQueue(options => + { + options.Credentials = new AnonymousAWSCredentials(); + options.Endpoint = RegionEndpoint.EUWest1; + options.SourceQueue = new Uri("https://sqs.eu-west-1.amazonaws.com/123456789012/account-commands"); + }); + services.AddAmazonEventBus(options => + { + options.Credentials = new AnonymousAWSCredentials(); + options.Endpoint = RegionEndpoint.EUWest1; + options.SourceQueue = new Uri("https://sqs.eu-west-1.amazonaws.com/123456789012/account-events"); + }); + return services; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Text.Json.ServiceCollectionExtensions.md b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Text.Json.ServiceCollectionExtensions.md new file mode 100644 index 0000000..98d8163 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.DependencyInjection.Text.Json.ServiceCollectionExtensions.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.DependencyInjection.Text.Json.ServiceCollectionExtensions +example: +- *content +--- +Register `JsonMarshaller` in the DI container with `AddJsonMarshaller`. + +```csharp +using System.Text.Json; +using Microsoft.Extensions.DependencyInjection; +using Savvyio.Extensions.DependencyInjection.Text.Json; + +namespace ExampleApp; + +public static class JsonDependencyInjectionSetup +{ + public static IServiceCollection AddSystemTextJsonSerialization(this IServiceCollection services) + { + services.AddJsonMarshaller(options => + { + options.Settings.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.Settings.WriteIndented = true; + }); + + return services; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.DomainEventDispatcherExtensions.md b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.DomainEventDispatcherExtensions.md new file mode 100644 index 0000000..42898d3 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.DomainEventDispatcherExtensions.md @@ -0,0 +1,83 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain.DomainEventDispatcherExtensions +example: +- *content +--- +`DomainEventDispatcherExtensions.RaiseMany` and `RaiseManyAsync` iterate over the domain events accumulated on an `IAggregateRoot` after a save operation and dispatch each event through `IDomainEventDispatcher`. Inject `IDomainEventDispatcher` into the repository, call the save operation, and then call `RaiseManyAsync` with the saved aggregate. The example shows a save-then-raise pattern using an EF Core–backed order repository. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Savvyio.Domain; +using Savvyio.Extensions.EFCore.Domain; + +namespace ExampleApp; + +public sealed class DomainEventWorkflow +{ + public async Task PublishPendingEventsAsync() + { + var dispatcher = new RecordingDomainEventDispatcher(); + var context = new OrdersContext(); + var order = OrderAggregate.Place(Guid.NewGuid(), "PO-1024"); + + context.Orders.Add(order); + + dispatcher.RaiseMany(context); + dispatcher.RaiseManyAsync(context).GetAwaiter().GetResult(); + + await Task.CompletedTask; + return dispatcher.Published.Count; + } +} + +public sealed class OrdersContext : DbContext +{ + public DbSet Orders => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(order => order.Id); + } +} + +public sealed class RecordingDomainEventDispatcher : IDomainEventDispatcher +{ + public List Published { get; } = new(); + + public void Raise(IDomainEvent request) + { + Published.Add(request); + } + + public Task RaiseAsync(IDomainEvent request, Action? setup = null) + { + Published.Add(request); + return Task.CompletedTask; + } +} + +public sealed class OrderAggregate : Aggregate, IAggregateRoot +{ + private OrderAggregate() + { + } + + private OrderAggregate(Guid id, string orderNumber) : base(id) + { + OrderNumber = orderNumber; + AddEvent(new OrderPlaced(orderNumber)); + } + + public string OrderNumber { get; private set; } = string.Empty; + + public static OrderAggregate Place(Guid id, string orderNumber) + { + return new OrderAggregate(id, orderNumber); + } +} + +public sealed record OrderPlaced(string OrderNumber) : DomainEvent; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EfCoreAggregateDataSource.md b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EfCoreAggregateDataSource.md new file mode 100644 index 0000000..02b6b7e --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EfCoreAggregateDataSource.md @@ -0,0 +1,82 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain.EfCoreAggregateDataSource +example: +- *content +--- +`EfCoreAggregateDataSource` is the domain-scoped EF Core data source that implements `IEfCoreDataSource` and restricts context usage to aggregate root operations. Subclass it with the specific `DbContext` type to provide a named aggregate context factory. The example wires up a minimal aggregate context and confirms it resolves from the data source. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Savvyio.Domain; +using Savvyio.Extensions.EFCore; +using Savvyio.Extensions.EFCore.Domain; + +namespace ExampleApp; + +public sealed class OrderingWorkflow +{ + public async Task SaveOrderAsync() + { + var dispatcher = new RecordingDomainEventDispatcher(); + var source = new OrderingDataSource(dispatcher, new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableDetailedErrors(), + ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(order => order.Id) + }); + + var repository = new EfCoreAggregateRepository(source); + repository.Add(OrderAggregate.Place(Guid.NewGuid(), "PO-2048")); + + await source.SaveChangesAsync(); + return dispatcher.Published.Count; + } +} + +public sealed class OrderingDataSource : EfCoreAggregateDataSource +{ + public OrderingDataSource(IDomainEventDispatcher dispatcher, EfCoreDataSourceOptions options) : base(dispatcher, options) + { + } +} + +public sealed class RecordingDomainEventDispatcher : IDomainEventDispatcher +{ + public List Published { get; } = new(); + + public void Raise(IDomainEvent request) + { + Published.Add(request); + } + + public Task RaiseAsync(IDomainEvent request, Action? setup = null) + { + Published.Add(request); + return Task.CompletedTask; + } +} + +public sealed class OrderAggregate : Aggregate, IAggregateRoot +{ + private OrderAggregate() + { + } + + private OrderAggregate(Guid id, string orderNumber) : base(id) + { + OrderNumber = orderNumber; + AddEvent(new OrderPlaced(orderNumber)); + } + + public string OrderNumber { get; private set; } = string.Empty; + + public static OrderAggregate Place(Guid id, string orderNumber) + { + return new OrderAggregate(id, orderNumber); + } +} + +public sealed record OrderPlaced(string OrderNumber) : DomainEvent; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EfCoreAggregateRepository`2.md b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EfCoreAggregateRepository`2.md new file mode 100644 index 0000000..1b57e51 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EfCoreAggregateRepository`2.md @@ -0,0 +1,73 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain.EfCoreAggregateRepository`2 +example: +- *content +--- +`EfCoreAggregateRepository` provides EF Core persistence for aggregate roots with strict boundary enforcement. Subclass it by providing the aggregate root type and key type, then inject the aggregate data source. The example creates a concrete repository, calls `AddAsync`, and resolves the aggregate to verify the persistence workflow. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Savvyio.Domain; +using Savvyio.Extensions.EFCore; +using Savvyio.Extensions.EFCore.Domain; + +namespace ExampleApp; + +public sealed class OrderingQueries +{ + public Task> LoadPriorityOrdersAsync() + { + var source = new OrderingDataSource(new EfCoreDataSourceOptions()); + var repository = new OrderingRepository(source); + + repository.Add(OrderAggregate.Place(Guid.NewGuid(), "PO-4096", true)); + return repository.FindPriorityOrdersAsync(); + } +} + +public sealed class OrderingDataSource : EfCoreDataSource +{ + public OrderingDataSource(EfCoreDataSourceOptions options) : base(options) + { + } +} + +public sealed class OrderingRepository : EfCoreAggregateRepository +{ + public OrderingRepository(IEfCoreDataSource source) : base(source) + { + } + + public Task> FindPriorityOrdersAsync() + { + return FindAllAsync(order => order.IsPriority); + } +} + +public sealed class OrderAggregate : Aggregate, IAggregateRoot +{ + private OrderAggregate() + { + } + + private OrderAggregate(Guid id, string orderNumber, bool isPriority) : base(id) + { + OrderNumber = orderNumber; + IsPriority = isPriority; + AddEvent(new OrderPlaced(orderNumber)); + } + + public string OrderNumber { get; private set; } = string.Empty; + + public bool IsPriority { get; private set; } + + public static OrderAggregate Place(Guid id, string orderNumber, bool isPriority) + { + return new OrderAggregate(id, orderNumber, isPriority); + } +} + +public sealed record OrderPlaced(string OrderNumber) : DomainEvent; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntityExtensions.md b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntityExtensions.md new file mode 100644 index 0000000..d036ae3 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntityExtensions.md @@ -0,0 +1,80 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntityExtensions +example: +- *content +--- +`EfCoreTracedAggregateEntityExtensions.ToTracedDomainEvent` rehydrates an `EfCoreTracedAggregateEntity` row back into the corresponding `ITracedDomainEvent` by deserializing the payload with the registered marshaller. This is called internally by `EfCoreTracedAggregateRepository` when loading aggregate history. The example creates a traced entity, calls `ToTracedDomainEvent`, and verifies the deserialized event matches the original. + +```csharp +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using Savvyio; +using Savvyio.Domain.EventSourcing; +using Savvyio.Extensions.EFCore.Domain.EventSourcing; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class EventHydrationExample +{ + public OrderPlaced Rehydrate() + { + var aggregate = new OrderTimeline(Guid.NewGuid(), "PO-7001"); + var marshaller = new SimpleMarshaller(); + var entity = new EfCoreTracedAggregateEntity(aggregate, aggregate.Events.Single(), marshaller); + + return (OrderPlaced)entity.ToTracedDomainEvent(typeof(OrderPlaced), marshaller); + } +} + +public sealed class OrderTimeline : TracedAggregateRoot +{ + public OrderTimeline(Guid id, string orderNumber) : base() + { + AddEvent(new OrderPlaced(id, orderNumber)); + } + + private OrderTimeline(Guid id, IEnumerable events) : base(id, events) + { + } + + public string OrderNumber { get; private set; } = string.Empty; + + protected override void RegisterDelegates(IFireForgetRegistry handler) + { + handler.Register(e => + { + Id = e.OrderId; + OrderNumber = e.OrderNumber; + }); + } +} + +public sealed record OrderPlaced(Guid OrderId, string OrderNumber) : TracedDomainEvent; + +public sealed class SimpleMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value)); + } + + public Stream Serialize(object value, Type inputType) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value, inputType)); + } + + public TValue Deserialize(Stream data) + { + return JsonSerializer.Deserialize(data)!; + } + + public object Deserialize(Stream data, Type returnType) + { + return JsonSerializer.Deserialize(data, returnType)!; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntityOptions.md b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntityOptions.md new file mode 100644 index 0000000..ad51ce5 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntityOptions.md @@ -0,0 +1,56 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntityOptions +example: +- *content +--- +``EfCoreTracedAggregateEntityOptions`` customizes the event-store table name and column names used by ``ModelBuilderExtensions.AddEventSourcing``. Configure it inside the setup lambda to override the defaults before EF Core creates the migration schema. The example applies custom column names and prints them to verify the configured values. + +```csharp +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Savvyio.Domain.EventSourcing; +using Savvyio.Extensions.EFCore.Domain.EventSourcing; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class EventSchemaConfiguration +{ + public ModelBuilder Configure(ModelBuilder modelBuilder) + { + return modelBuilder.AddEventSourcing(ConfigureOptions); + } + + private static void ConfigureOptions(EfCoreTracedAggregateEntityOptions options) + { + options.TableName = "OrderTimelineEvents"; + options.CompositePrimaryKeyIdColumnName = "order_id"; + options.CompositePrimaryKeyVersionColumnName = "aggregate_version"; + options.TimestampColumnName = "recorded_at"; + options.TypeColumnName = "event_type"; + options.PayloadColumnName = "event_payload"; + + Console.WriteLine($"Event table: {options.TableName}, ID column: {options.CompositePrimaryKeyIdColumnName}"); + } +} + +public sealed class OrderTimeline : TracedAggregateRoot +{ + public OrderTimeline(Guid id, string orderNumber) : base() + { + AddEvent(new OrderPlaced(id, orderNumber)); + } + + private OrderTimeline(Guid id, IEnumerable events) : base(id, events) + { + } + + protected override void RegisterDelegates(IFireForgetRegistry handler) + { + handler.Register(_ => { }); + } +} + +public sealed record OrderPlaced(Guid OrderId, string OrderNumber) : TracedDomainEvent; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntity`2.md b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntity`2.md new file mode 100644 index 0000000..a1de460 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntity`2.md @@ -0,0 +1,79 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateEntity`2 +example: +- *content +--- +`EfCoreTracedAggregateEntity` is the EF Core entity class that stores one traced domain event row in the event-store table. Each row carries the aggregate ID, version, event type, and serialized event payload. The example creates a traced entity directly from an aggregate and a domain event, then reads back the stored aggregate ID and version. + +```csharp +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using Savvyio; +using Savvyio.Domain.EventSourcing; +using Savvyio.Extensions.EFCore.Domain.EventSourcing; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class EventRecordExample +{ + public EfCoreTracedAggregateEntity CreateRecord() + { + var aggregate = new OrderTimeline(Guid.NewGuid(), "PO-6001"); + var domainEvent = aggregate.Events.Single(); + + return new EfCoreTracedAggregateEntity(aggregate, domainEvent, new SimpleMarshaller()); + } +} + +public sealed class OrderTimeline : TracedAggregateRoot +{ + public OrderTimeline(Guid id, string orderNumber) : base() + { + AddEvent(new OrderPlaced(id, orderNumber)); + } + + private OrderTimeline(Guid id, IEnumerable events) : base(id, events) + { + } + + public string OrderNumber { get; private set; } = string.Empty; + + protected override void RegisterDelegates(IFireForgetRegistry handler) + { + handler.Register(e => + { + Id = e.OrderId; + OrderNumber = e.OrderNumber; + }); + } +} + +public sealed record OrderPlaced(Guid OrderId, string OrderNumber) : TracedDomainEvent; + +public sealed class SimpleMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value)); + } + + public Stream Serialize(object value, Type inputType) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value, inputType)); + } + + public TValue Deserialize(Stream data) + { + return JsonSerializer.Deserialize(data)!; + } + + public object Deserialize(Stream data, Type returnType) + { + return JsonSerializer.Deserialize(data, returnType)!; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository`2.md b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository`2.md new file mode 100644 index 0000000..b535782 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository`2.md @@ -0,0 +1,87 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain.EventSourcing.EfCoreTracedAggregateRepository`2 +example: +- *content +--- +`EfCoreTracedAggregateRepository` stores and loads event-sourced aggregates by writing and reading individual traced domain event rows. The setup requires a context configured with `ModelBuilder.AddEventSourcing`, an `IMarshaller` for event serialization, and the aggregate type with `RegisterDelegates` implemented. The example creates a repository, appends an event, and rehydrates the aggregate from stored events. + +```csharp +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Savvyio; +using Savvyio.Domain.EventSourcing; +using Savvyio.Extensions.EFCore; +using Savvyio.Extensions.EFCore.Domain.EventSourcing; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class EventSourcingWorkflow +{ + public Task LoadAsync(Guid id) + { + var source = new EfCoreDataSource(new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableDetailedErrors(), + ModelConstructor = modelBuilder => modelBuilder.AddEventSourcing() + }); + + var repository = new EfCoreTracedAggregateRepository(source, new SimpleMarshaller()); + repository.Add(new OrderTimeline(id, "PO-8001")); + + return repository.GetByIdAsync(id); + } +} + +public sealed class OrderTimeline : TracedAggregateRoot +{ + public OrderTimeline(Guid id, string orderNumber) : base() + { + AddEvent(new OrderPlaced(id, orderNumber)); + } + + private OrderTimeline(Guid id, IEnumerable events) : base(id, events) + { + } + + public string OrderNumber { get; private set; } = string.Empty; + + protected override void RegisterDelegates(IFireForgetRegistry handler) + { + handler.Register(e => + { + Id = e.OrderId; + OrderNumber = e.OrderNumber; + }); + } +} + +public sealed record OrderPlaced(Guid OrderId, string OrderNumber) : TracedDomainEvent; + +public sealed class SimpleMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value)); + } + + public Stream Serialize(object value, Type inputType) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value, inputType)); + } + + public TValue Deserialize(Stream data) + { + return JsonSerializer.Deserialize(data)!; + } + + public object Deserialize(Stream data, Type returnType) + { + return JsonSerializer.Deserialize(data, returnType)!; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.ModelBuilderExtensions.md b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.ModelBuilderExtensions.md new file mode 100644 index 0000000..11051a6 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.ModelBuilderExtensions.md @@ -0,0 +1,61 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain.EventSourcing.ModelBuilderExtensions +example: +- *content +--- +This example shows how `AddEventSourcing` can be applied in `OnModelCreating` to register the event table for a traced aggregate. + +```csharp +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Savvyio.Domain.EventSourcing; +using Savvyio.Extensions.EFCore; +using Savvyio.Extensions.EFCore.Domain.EventSourcing; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class EventStoreContextFactory +{ + public EventStoreContext Create() + { + return new EventStoreContext(new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableDetailedErrors() + }); + } +} + +public sealed class EventStoreContext : EfCoreDbContext +{ + public EventStoreContext(EfCoreDataSourceOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.AddEventSourcing(options => options.TableName = "OrderTimelineEvents"); + } +} + +public sealed class OrderTimeline : TracedAggregateRoot +{ + public OrderTimeline(Guid id, string orderNumber) : base() + { + AddEvent(new OrderPlaced(id, orderNumber)); + } + + private OrderTimeline(Guid id, IEnumerable events) : base(id, events) + { + } + + protected override void RegisterDelegates(IFireForgetRegistry handler) + { + handler.Register(_ => { }); + } +} + +public sealed record OrderPlaced(Guid OrderId, string OrderNumber) : TracedDomainEvent; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.TracedDomainEventExtensions.md b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.TracedDomainEventExtensions.md new file mode 100644 index 0000000..9dfd6b7 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.Domain.EventSourcing.TracedDomainEventExtensions.md @@ -0,0 +1,53 @@ +--- +uid: Savvyio.Extensions.EFCore.Domain.EventSourcing.TracedDomainEventExtensions +example: +- *content +--- +`TracedDomainEventExtensions.ToByteArray` serializes an `ITracedDomainEvent` to a raw byte array using the registered marshaller, which is the format stored in the EF Core event-store table. This is called internally by the traced aggregate repository, but it can also be called directly when you need to inspect or archive event payloads. The example serializes a domain event and verifies the resulting byte array is non-empty. + +```csharp +using System; +using System.IO; +using System.Text.Json; +using Savvyio; +using Savvyio.Domain.EventSourcing; +using Savvyio.Extensions.EFCore.Domain.EventSourcing; + +namespace ExampleApp; + +public sealed class EventPayloadExample +{ + public int CreatePayloadSize() + { + var domainEvent = new OrderPlaced("PO-9001"); + var payload = domainEvent.ToByteArray(new SimpleMarshaller()); + + return payload.Length; + } +} + +public sealed record OrderPlaced(string OrderNumber) : TracedDomainEvent; + +public sealed class SimpleMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value)); + } + + public Stream Serialize(object value, Type inputType) + { + return new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(value, inputType)); + } + + public TValue Deserialize(Stream data) + { + return JsonSerializer.Deserialize(data)!; + } + + public object Deserialize(Stream data, Type returnType) + { + return JsonSerializer.Deserialize(data, returnType)!; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataSource.md b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataSource.md new file mode 100644 index 0000000..4dd313d --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataSource.md @@ -0,0 +1,73 @@ +--- +uid: Savvyio.Extensions.EFCore.EfCoreDataSource +example: +- *content +--- +This example shows how a custom `EfCoreDataSource` can wrap a dedicated `DbContext` for a catalog workflow. + +```csharp +using System; +using Microsoft.EntityFrameworkCore; +using Savvyio; +using Savvyio.Extensions.EFCore; + +namespace ExampleApp; + +public sealed class CatalogWorkflow +{ + public CatalogDataSource CreateDataSource() + { + var options = new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableDetailedErrors(), + ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(product => product.Id) + }; + + return new CatalogDataSource(new CatalogDbContext(options)); + } +} + +public sealed class CatalogDataSource : EfCoreDataSource +{ + public CatalogDataSource(CatalogDbContext dbContext) : base(dbContext) + { + } + + public DbSet Products => Set(); +} + +public sealed class CatalogDbContext : DbContext +{ + private readonly EfCoreDataSourceOptions _options; + + public CatalogDbContext(EfCoreDataSourceOptions options) + { + _options = options; + } + + public DbSet Products => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + _options.ContextConfigurator?.Invoke(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + _options.ModelConstructor?.Invoke(modelBuilder); + } +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name) + { + Id = id; + Name = name; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataSourceOptions.md b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataSourceOptions.md new file mode 100644 index 0000000..b1f26d8 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataSourceOptions.md @@ -0,0 +1,46 @@ +--- +uid: Savvyio.Extensions.EFCore.EfCoreDataSourceOptions +example: +- *content +--- +This example shows how `EfCoreDataSourceOptions` centralizes model and context configuration for an EF Core-backed source. + +```csharp +using System; +using Microsoft.EntityFrameworkCore; +using Savvyio; +using Savvyio.Extensions.EFCore; + +namespace ExampleApp; + +public sealed class CatalogConfiguration +{ + public EfCoreDataSource CreateSource() + { + var options = new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableSensitiveDataLogging(), + ModelConstructor = modelBuilder => + { + modelBuilder.Entity().HasKey(product => product.Id); + modelBuilder.Entity().Property(product => product.Name).HasMaxLength(128); + } + }; + + return new EfCoreDataSource(options); + } +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name) + { + Id = id; + Name = name; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataStore`1.md b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataStore`1.md new file mode 100644 index 0000000..68bf111 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDataStore`1.md @@ -0,0 +1,67 @@ +--- +uid: Savvyio.Extensions.EFCore.EfCoreDataStore`1 +example: +- *content +--- +`EfCoreDataStore` provides EF Core–backed create, read, update, delete, and search operations. Subclass it with the entity type and context type, then inject the matching `IEfCoreDataSource` as the constructor argument. The example creates a concrete order data store and verifies it can be instantiated with the data source. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Savvyio; +using Savvyio.Extensions.EFCore; + +namespace ExampleApp; + +public sealed class CatalogQueries +{ + public Task> LoadFeaturedProductsAsync() + { + var source = new CatalogDataSource(new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableDetailedErrors(), + ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(product => product.Id) + }); + + var store = new CatalogDataStore(source); + return store.FindFeaturedAsync(); + } +} + +public sealed class CatalogDataSource : EfCoreDataSource +{ + public CatalogDataSource(EfCoreDataSourceOptions options) : base(options) + { + } +} + +public sealed class CatalogDataStore : EfCoreDataStore +{ + public CatalogDataStore(IEfCoreDataSource source) : base(source) + { + } + + public Task> FindFeaturedAsync() + { + return FindAllAsync(options => options.Predicate = product => product.IsFeatured); + } +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name, bool isFeatured) + { + Id = id; + Name = name; + IsFeatured = isFeatured; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; + + public bool IsFeatured { get; private set; } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDbContext.md b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDbContext.md new file mode 100644 index 0000000..6f15595 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreDbContext.md @@ -0,0 +1,57 @@ +--- +uid: Savvyio.Extensions.EFCore.EfCoreDbContext +example: +- *content +--- +`EfCoreDbContext` is the Savvy I/O base context that accepts `EfCoreDataSourceOptions` for connection management and configuration. Subclass it to add `DbSet` properties for your domain entities and override `OnModelCreating` for fluent EF Core configuration. The example defines a minimal order context using an in-memory provider and verifies the context can be created and disposed. + +```csharp +using System; +using Microsoft.EntityFrameworkCore; +using Savvyio; +using Savvyio.Extensions.EFCore; + +namespace ExampleApp; + +public sealed class CatalogContextFactory +{ + public CatalogDbContext Create() + { + var options = new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableDetailedErrors(), + ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(product => product.Id) + }; + + return new CatalogDbContext(options); + } +} + +public sealed class CatalogDbContext : EfCoreDbContext +{ + public CatalogDbContext(EfCoreDataSourceOptions options) : base(options) + { + } + + public DbSet Products => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().HasKey(product => product.Id); + } +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name) + { + Id = id; + Name = name; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreQueryOptions`1.md b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreQueryOptions`1.md new file mode 100644 index 0000000..1637649 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreQueryOptions`1.md @@ -0,0 +1,60 @@ +--- +uid: Savvyio.Extensions.EFCore.EfCoreQueryOptions`1 +example: +- *content +--- +`EfCoreQueryOptions` carries the EF Core query predicate and ordering settings used by `EfCoreDataStore` read operations. Configure the `Predicate`, `Ordering`, and `MaxRows` properties to control the result set. The example sets up a filtered query for pending orders and verifies the options are applied. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Savvyio; +using Savvyio.Extensions.EFCore; + +namespace ExampleApp; + +public sealed class ProductFiltering +{ + public Task> LoadActiveProductsAsync() + { + var query = new EfCoreQueryOptions + { + Predicate = product => product.IsActive + }; + + var source = new CatalogDataSource(new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableDetailedErrors(), + ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(product => product.Id) + }); + + var store = new EfCoreDataStore(source); + return store.FindAllAsync(options => options.Predicate = query.Predicate); + } +} + +public sealed class CatalogDataSource : EfCoreDataSource +{ + public CatalogDataSource(EfCoreDataSourceOptions options) : base(options) + { + } +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name, bool isActive) + { + Id = id; + Name = name; + IsActive = isActive; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; + + public bool IsActive { get; private set; } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreRepository`2.md b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreRepository`2.md new file mode 100644 index 0000000..4552907 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.EFCore.EfCoreRepository`2.md @@ -0,0 +1,68 @@ +--- +uid: Savvyio.Extensions.EFCore.EfCoreRepository`2 +example: +- *content +--- +`EfCoreRepository` is the EF Core aggregate repository base class for types that extend `IAggregateRoot`. Subclass it, supply the aggregate type and key type, and inject the aggregate data source. The example creates a concrete repository and exercises a simple add-and-find workflow using an in-memory provider. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Savvyio; +using Savvyio.Extensions.EFCore; + +namespace ExampleApp; + +public sealed class CatalogWorkflow +{ + public Task> LoadFeaturedProductsAsync() + { + var source = new CatalogDataSource(new EfCoreDataSourceOptions + { + ContextConfigurator = builder => builder.EnableDetailedErrors(), + ModelConstructor = modelBuilder => modelBuilder.Entity().HasKey(product => product.Id) + }); + + var repository = new CatalogRepository(source); + repository.Add(new Product(Guid.NewGuid(), "Starter kit", true)); + return repository.FindFeaturedAsync(); + } +} + +public sealed class CatalogDataSource : EfCoreDataSource +{ + public CatalogDataSource(EfCoreDataSourceOptions options) : base(options) + { + } +} + +public sealed class CatalogRepository : EfCoreRepository +{ + public CatalogRepository(IEfCoreDataSource source) : base(source) + { + } + + public Task> FindFeaturedAsync() + { + return FindAllAsync(product => product.IsFeatured); + } +} + +public sealed class Product : IIdentity +{ + public Product(Guid id, string name, bool isFeatured) + { + Id = id; + Name = name; + IsFeatured = isFeatured; + } + + public Guid Id { get; } + + public string Name { get; private set; } = string.Empty; + + public bool IsFeatured { get; private set; } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Mediator.md b/.docfx/api/types/Savvyio.Extensions.Mediator.md new file mode 100644 index 0000000..c26d1b0 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Mediator.md @@ -0,0 +1,44 @@ +--- +uid: Savvyio.Extensions.Mediator +example: +- *content +--- +`Mediator` is the single-entry-point dispatcher that routes commands, queries, and events to their handlers without requiring separate dispatcher injections. Create it with a `ServiceLocator` that resolves handlers by service type. + +```csharp +using System; +using System.Collections.Generic; +using Savvyio; +using Savvyio.Commands; +using Savvyio.Dispatchers; +using Savvyio.Extensions; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class MediatorExample +{ + public void Dispatch() + { + var handler = new CreateOrderHandler(); + var locator = new ServiceLocator(serviceType => + serviceType == typeof(ICommandHandler) ? new object[] { handler } : Array.Empty()); + var mediator = new Mediator(locator); + + mediator.Commit(new CreateOrderCommand("SO-42")); + Console.WriteLine($"Processed orders: {handler.ProcessedOrders.Count}"); + } +} + +public sealed class CreateOrderHandler : ICommandHandler +{ + public List ProcessedOrders { get; } = new(); + + public IFireForgetActivator Delegates => + HandlerFactory.CreateFireForget(r => + r.Register(cmd => ProcessedOrders.Add(cmd.OrderId))); +} + +public sealed record CreateOrderCommand(string OrderId) : Request, ICommand; +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.NATS.Commands.NatsCommandQueue.md b/.docfx/api/types/Savvyio.Extensions.NATS.Commands.NatsCommandQueue.md new file mode 100644 index 0000000..77d3862 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.NATS.Commands.NatsCommandQueue.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Extensions.NATS.Commands.NatsCommandQueue +example: +- *content +--- +`NatsCommandQueue` is the NATS JetStream command queue that publishes and receives `IMessage` envelopes. Configure it with `NatsCommandQueueOptions` and `IMarshaller` to set up the connection. + +```csharp +using System; +using Savvyio.Commands; +using Savvyio.Extensions.NATS.Commands; +using Savvyio.Messaging; + +namespace ExampleApp; + +public class NatsCommandQueueConfig +{ + public static NatsCommandQueueOptions CreateOptions() + { + return new NatsCommandQueueOptions + { + NatsUrl = new Uri("nats://localhost:4222"), + Subject = "account-commands", + StreamName = "savvyio-commands", + ConsumerName = "account-command-consumer", + AutoAcknowledge = true + }; + } + + public static IPointToPointChannel AsChannel(NatsCommandQueue queue) => queue; +} +``` + + + diff --git a/.docfx/api/types/Savvyio.Extensions.NATS.Commands.NatsCommandQueueOptions.md b/.docfx/api/types/Savvyio.Extensions.NATS.Commands.NatsCommandQueueOptions.md new file mode 100644 index 0000000..4b338d0 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.NATS.Commands.NatsCommandQueueOptions.md @@ -0,0 +1,29 @@ +--- +uid: Savvyio.Extensions.NATS.Commands.NatsCommandQueueOptions +example: +- *content +--- +Configure a `NatsCommandQueueOptions` with the NATS JetStream stream, consumer, and subject settings required for command queue delivery. + +```csharp +using System; +using Savvyio.Extensions.NATS.Commands; + +namespace ExampleApp; + +public class NatsCommandSetup +{ + public static NatsCommandQueueOptions CreateOptions() + { + var options = new NatsCommandQueueOptions + { + NatsUrl = new Uri("nats://localhost:4222"), + Subject = "commands", + StreamName = "savvyio-commands", + ConsumerName = "account-command-consumer", + AutoAcknowledge = true + }; + return options; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.NATS.EventDriven.NatsEventBus.md b/.docfx/api/types/Savvyio.Extensions.NATS.EventDriven.NatsEventBus.md new file mode 100644 index 0000000..eb6df0e --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.NATS.EventDriven.NatsEventBus.md @@ -0,0 +1,32 @@ +--- +uid: Savvyio.Extensions.NATS.EventDriven.NatsEventBus +example: +- *content +--- +`NatsEventBus` is the NATS JetStream event bus that publishes and subscribes to `IMessage` envelopes. Configure it with `NatsEventBusOptions` and `IMarshaller`. + +```csharp +using System; +using Savvyio.EventDriven; +using Savvyio.Extensions.NATS.EventDriven; +using Savvyio.Messaging; + +namespace ExampleApp; + +public class NatsEventBusConfig +{ + public static NatsEventBusOptions CreateOptions() + { + return new NatsEventBusOptions + { + NatsUrl = new Uri("nats://localhost:4222"), + Subject = "integration-events" + }; + } + + public static IPublishSubscribeChannel AsChannel(NatsEventBus bus) => bus; +} +``` + + + diff --git a/.docfx/api/types/Savvyio.Extensions.NATS.EventDriven.NatsEventBusOptions.md b/.docfx/api/types/Savvyio.Extensions.NATS.EventDriven.NatsEventBusOptions.md new file mode 100644 index 0000000..e25a58b --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.NATS.EventDriven.NatsEventBusOptions.md @@ -0,0 +1,27 @@ +--- +uid: Savvyio.Extensions.NATS.EventDriven.NatsEventBusOptions +example: +- *content +--- +Configure a `NatsEventBusOptions` with the NATS server URL and subject settings required for publishing and subscribing to integration events. + +```csharp +using System; +using Savvyio.Extensions.NATS.EventDriven; + +namespace ExampleApp; + +public class NatsEventBusSetup +{ + public static NatsEventBusOptions CreateOptions() + { + var options = new NatsEventBusOptions + { + NatsUrl = new Uri("nats://localhost:4222"), + Subject = "integration-events" + }; + return options; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.NATS.NatsMessageOptions.md b/.docfx/api/types/Savvyio.Extensions.NATS.NatsMessageOptions.md new file mode 100644 index 0000000..6ebf971 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.NATS.NatsMessageOptions.md @@ -0,0 +1,26 @@ +--- +uid: Savvyio.Extensions.NATS.NatsMessageOptions +example: +- *content +--- +Configure a `NatsMessageOptions` to point at a NATS server and specify the subject for message delivery. + +```csharp +using System; +using Savvyio.Extensions.NATS; + +namespace ExampleApp; + +public class NatsSetup +{ + public static NatsMessageOptions CreateOptions() + { + var options = new NatsMessageOptions + { + NatsUrl = new Uri("nats://localhost:4222"), + Subject = "account-commands" + }; + return options; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.AggregateRootConverter.md b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.AggregateRootConverter.md new file mode 100644 index 0000000..50ba6c6 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.AggregateRootConverter.md @@ -0,0 +1,37 @@ +--- +uid: Savvyio.Extensions.Newtonsoft.Json.Converters.AggregateRootConverter`1 +example: +- *content +--- +Add `AggregateRootConverter` to `JsonSerializerSettings` to round-trip aggregate roots through Newtonsoft.Json. + +```csharp +using System; +using Newtonsoft.Json; +using Savvyio.Domain; +using Savvyio.Extensions.Newtonsoft.Json.Converters; + +namespace ExampleApp; + +public static class AggregateRootSerialization +{ + public static OrderAggregate RoundTrip(OrderAggregate order) + { + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new AggregateRootConverter()); + + var json = JsonConvert.SerializeObject(order, settings); + return JsonConvert.DeserializeObject(json, settings)!; + } +} + +public sealed class OrderAggregate : AggregateRoot +{ + public OrderAggregate(Guid id, string customerId) : base(id) + { + CustomerId = customerId; + } + + public string CustomerId { get; } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.MessageConverter.md b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.MessageConverter.md new file mode 100644 index 0000000..8709496 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.MessageConverter.md @@ -0,0 +1,37 @@ +--- +uid: Savvyio.Extensions.Newtonsoft.Json.Converters.MessageConverter +example: +- *content +--- +Add `MessageConverter` to `JsonSerializerSettings` to serialize and deserialize Savvy I/O message envelopes. + +```csharp +using Savvyio; +using System; +using Newtonsoft.Json; +using Savvyio.Extensions.Newtonsoft.Json.Converters; +using Savvyio.Messaging; + +namespace ExampleApp; + +public static class MessageSerialization +{ + public static Message RoundTrip() + { + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new MessageConverter()); + + var message = new Message( + "msg-001", + new Uri("urn:orders"), + nameof(ShipOrderCommand), + new ShipOrderCommand("SO-42"), + DateTime.UtcNow); + + var json = JsonConvert.SerializeObject(message, settings); + return JsonConvert.DeserializeObject>(json, settings)!; + } +} + +public sealed record ShipOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.RequestConverter.md b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.RequestConverter.md new file mode 100644 index 0000000..d22371e --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.RequestConverter.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.Newtonsoft.Json.Converters.RequestConverter +example: +- *content +--- +Add `RequestConverter` to `JsonSerializerSettings` to rehydrate request types with read-only properties. + +```csharp +using Savvyio; +using Newtonsoft.Json; +using Savvyio.Extensions.Newtonsoft.Json.Converters; + +namespace ExampleApp; + +public static class RequestSerialization +{ + public static CreateOrderCommand RoundTrip() + { + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new RequestConverter()); + + var json = JsonConvert.SerializeObject(new CreateOrderCommand("SO-42", 2)); + return JsonConvert.DeserializeObject(json, settings)!; + } +} + +public sealed record CreateOrderCommand(string OrderId, int Quantity) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.SingleValueObjectConverter.md b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.SingleValueObjectConverter.md new file mode 100644 index 0000000..05b64af --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.SingleValueObjectConverter.md @@ -0,0 +1,33 @@ +--- +uid: Savvyio.Extensions.Newtonsoft.Json.Converters.SingleValueObjectConverter +example: +- *content +--- +Add `SingleValueObjectConverter` to `JsonSerializerSettings` to serialize a single-value object as its wrapped value. + +```csharp +using Newtonsoft.Json; +using Savvyio.Domain; +using Savvyio.Extensions.Newtonsoft.Json.Converters; + +namespace ExampleApp; + +public static class SingleValueObjectSerialization +{ + public static OrderNumber RoundTrip(OrderNumber orderNumber) + { + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new SingleValueObjectConverter()); + + var json = JsonConvert.SerializeObject(orderNumber, settings); + return JsonConvert.DeserializeObject(json, settings)!; + } +} + +public sealed record OrderNumber : SingleValueObject +{ + public OrderNumber(string value) : base(value) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.ValueObjectConverter.md b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.ValueObjectConverter.md new file mode 100644 index 0000000..ac664e3 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.Converters.ValueObjectConverter.md @@ -0,0 +1,39 @@ +--- +uid: Savvyio.Extensions.Newtonsoft.Json.Converters.ValueObjectConverter +example: +- *content +--- +Add `ValueObjectConverter` to `JsonSerializerSettings` to round-trip richer value objects with multiple properties. + +```csharp +using Newtonsoft.Json; +using Savvyio.Domain; +using Savvyio.Extensions.Newtonsoft.Json.Converters; + +namespace ExampleApp; + +public static class ValueObjectSerialization +{ + public static Money RoundTrip(Money value) + { + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new ValueObjectConverter()); + + var json = JsonConvert.SerializeObject(value, settings); + return JsonConvert.DeserializeObject(json, settings)!; + } +} + +public sealed record Money : ValueObject +{ + public Money(string currency, decimal amount) + { + Currency = currency; + Amount = amount; + } + + public string Currency { get; } + + public decimal Amount { get; } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.JsonConverterExtensions.md b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.JsonConverterExtensions.md new file mode 100644 index 0000000..4f4efec --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.JsonConverterExtensions.md @@ -0,0 +1,46 @@ +--- +uid: Savvyio.Extensions.Newtonsoft.Json.JsonConverterExtensions +example: +- *content +--- +Use `JsonConverterExtensions` to add all Savvy I/O-aware converters to a Newtonsoft.Json serializer configuration. + +```csharp +using Savvyio; +using System; +using Newtonsoft.Json; +using Savvyio.Commands; +using Savvyio.Domain; +using Savvyio.Extensions.Newtonsoft.Json; +using Savvyio.Messaging; + +namespace ExampleApp; + +public static class JsonSettingsFactory +{ + public static JsonSerializerSettings Create() + { + var settings = new JsonSerializerSettings(); + settings.Converters + .AddMetadataDictionaryConverter() + .AddMessageConverter() + .AddRequestConverter() + .AddSingleValueObjectConverter() + .AddValueObjectConverter() + .AddAggregateRootConverter(); + + var message = new Message( + "msg-001", + new Uri("urn:orders"), + nameof(PublishOrderCommand), + new PublishOrderCommand("SO-42"), + DateTime.UtcNow); + + _ = JsonConvert.SerializeObject(message, settings); + return settings; + } +} + +public sealed record PublishOrderCommand(string OrderId) : Request; +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.JsonSerializerExtensions.md b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.JsonSerializerExtensions.md new file mode 100644 index 0000000..acdc4a6 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.JsonSerializerExtensions.md @@ -0,0 +1,38 @@ +--- +uid: Savvyio.Extensions.Newtonsoft.Json.JsonSerializerExtensions +example: +- *content +--- +Use `ResolvePropertyKeyByConvention` and `ResolveDictionaryKeyByConvention` to derive JSON property names and dictionary key names from the serializer's naming strategy. + +```csharp +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Savvyio.Extensions.Newtonsoft.Json; + +namespace ExampleApp; + +public static class NamingConventions +{ + public static (string PropertyKey, string DictionaryKey) ResolveKeys() + { + var serializer = JsonSerializer.Create(new JsonSerializerSettings + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy() + } + }); + + var propertyKey = serializer.ResolvePropertyKeyByConvention(nameof(OrderProjection.OrderId)); + var dictionaryKey = serializer.ResolveDictionaryKeyByConvention(nameof(OrderProjection.OrderId)); + return (propertyKey, dictionaryKey); + } +} + +public sealed class OrderProjection +{ + public string OrderId { get; init; } = string.Empty; +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.NewtonsoftJsonMarshaller.md b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.NewtonsoftJsonMarshaller.md new file mode 100644 index 0000000..68e76d8 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Newtonsoft.Json.NewtonsoftJsonMarshaller.md @@ -0,0 +1,31 @@ +--- +uid: Savvyio.Extensions.Newtonsoft.Json.NewtonsoftJsonMarshaller +example: +- *content +--- +Use `NewtonsoftJsonMarshaller` to serialize and deserialize Savvy I/O requests through streams. + +```csharp +using Savvyio; +using Newtonsoft.Json; +using Savvyio.Extensions.Newtonsoft.Json; + +namespace ExampleApp; + +public static class MarshallerExample +{ + public static ShipOrderCommand RoundTrip(ShipOrderCommand command) + { + var marshaller = NewtonsoftJsonMarshaller.Create(options => + { + options.Settings.Formatting = Formatting.Indented; + }); + + using var stream = marshaller.Serialize(command); + stream.Position = 0; + return marshaller.Deserialize(stream); + } +} + +public sealed record ShipOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueOptions.md b/.docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueOptions.md new file mode 100644 index 0000000..a0d93f0 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueOptions.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Extensions.QueueStorage.AzureQueueOptions +example: +- *content +--- +Configure an `AzureQueueOptions` with the Azure Storage account name and queue name required for Azure Queue Storage integration. + +```csharp +using Savvyio.Extensions.QueueStorage; + +namespace ExampleApp; + +public class AzureQueueSetup +{ + public static AzureQueueOptions CreateConnectionStringOptions() + { + var options = new AzureQueueOptions + { + ConnectionString = "UseDevelopmentStorage=true", + QueueName = "account-commands" + }; + return options; + } + + public static AzureQueueOptions CreateAccountOptions() + { + var options = new AzureQueueOptions + { + StorageAccountName = "mystorageaccount", + QueueName = "account-commands" + }; + return options; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueReceiveOptions.md b/.docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueReceiveOptions.md new file mode 100644 index 0000000..eef2372 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueReceiveOptions.md @@ -0,0 +1,29 @@ +--- +uid: Savvyio.Extensions.QueueStorage.AzureQueueReceiveOptions +example: +- *content +--- +`AzureQueueReceiveOptions` configures the receive behavior of an Azure Queue Storage consumer, including visibility timeout and message count limits. Access it through `AzureQueueOptions.ReceiveContext`. + +```csharp +using System; +using Savvyio.Extensions.QueueStorage; + +namespace ExampleApp; + +public class AzureQueueReceiveSetup +{ + public static AzureQueueOptions CreateOptions() + { + var options = new AzureQueueOptions + { + ConnectionString = "UseDevelopmentStorage=true", + QueueName = "account-commands" + }; + AzureQueueReceiveOptions receiveOptions = options.ReceiveContext; + receiveOptions.VisibilityTimeout = TimeSpan.FromMinutes(5); + return options; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueSendOptions.md b/.docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueSendOptions.md new file mode 100644 index 0000000..2f3208f --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.QueueStorage.AzureQueueSendOptions.md @@ -0,0 +1,29 @@ +--- +uid: Savvyio.Extensions.QueueStorage.AzureQueueSendOptions +example: +- *content +--- +`AzureQueueSendOptions` configures message time-to-live when sending messages to Azure Queue Storage. Access it through `AzureQueueOptions.SendContext`. + +```csharp +using System; +using Savvyio.Extensions.QueueStorage; + +namespace ExampleApp; + +public class AzureQueueSendSetup +{ + public static AzureQueueOptions CreateOptions() + { + var options = new AzureQueueOptions + { + ConnectionString = "UseDevelopmentStorage=true", + QueueName = "account-commands" + }; + AzureQueueSendOptions sendOptions = options.SendContext; + sendOptions.TimeToLive = TimeSpan.FromDays(3); + return options; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.QueueStorage.Commands.AzureCommandQueue.md b/.docfx/api/types/Savvyio.Extensions.QueueStorage.Commands.AzureCommandQueue.md new file mode 100644 index 0000000..5419a58 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.QueueStorage.Commands.AzureCommandQueue.md @@ -0,0 +1,31 @@ +--- +uid: Savvyio.Extensions.QueueStorage.Commands.AzureCommandQueue +example: +- *content +--- +`AzureCommandQueue` sends and receives commands through Azure Queue Storage. Configure it with `AzureQueueOptions` and an `IMarshaller` instance, then use it as `IPointToPointChannel`. + +```csharp +using Savvyio.Commands; +using Savvyio.Extensions.QueueStorage; +using Savvyio.Extensions.QueueStorage.Commands; +using Savvyio.Messaging; + +namespace ExampleApp; + +public class AzureCommandQueueConfig +{ + public static AzureQueueOptions CreateOptions() + { + return new AzureQueueOptions + { + ConnectionString = "UseDevelopmentStorage=true", + QueueName = "account-commands" + }; + } + + public static IPointToPointChannel AsChannel(AzureCommandQueue queue) => queue; +} +``` + + diff --git a/.docfx/api/types/Savvyio.Extensions.QueueStorage.EventDriven.AzureEventBus.md b/.docfx/api/types/Savvyio.Extensions.QueueStorage.EventDriven.AzureEventBus.md new file mode 100644 index 0000000..cf17877 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.QueueStorage.EventDriven.AzureEventBus.md @@ -0,0 +1,31 @@ +--- +uid: Savvyio.Extensions.QueueStorage.EventDriven.AzureEventBus +example: +- *content +--- +`AzureEventBus` publishes and receives integration events through Azure Queue Storage. Configure it with `AzureQueueOptions` and an `IMarshaller` instance, then use it as `IPublishSubscribeChannel`. + +```csharp +using Savvyio.EventDriven; +using Savvyio.Extensions.QueueStorage; +using Savvyio.Extensions.QueueStorage.EventDriven; +using Savvyio.Messaging; + +namespace ExampleApp; + +public class AzureEventBusConfig +{ + public static AzureQueueOptions CreateOptions() + { + return new AzureQueueOptions + { + ConnectionString = "UseDevelopmentStorage=true", + QueueName = "account-integration-events" + }; + } + + public static IPublishSubscribeChannel AsChannel(AzureEventBus bus) => bus; +} +``` + + diff --git a/.docfx/api/types/Savvyio.Extensions.QueueStorage.EventDriven.AzureEventBusOptions.md b/.docfx/api/types/Savvyio.Extensions.QueueStorage.EventDriven.AzureEventBusOptions.md new file mode 100644 index 0000000..aab4172 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.QueueStorage.EventDriven.AzureEventBusOptions.md @@ -0,0 +1,25 @@ +--- +uid: Savvyio.Extensions.QueueStorage.EventDriven.AzureEventBusOptions +example: +- *content +--- +`AzureEventBusOptions` configures the Azure Event Grid topic endpoint used to publish integration events. + +```csharp +using System; +using Savvyio.Extensions.QueueStorage.EventDriven; + +namespace ExampleApp; + +public class AzureEventBusSetup +{ + public static AzureEventBusOptions CreateOptions() + { + return new AzureEventBusOptions + { + TopicEndpoint = new Uri("https://myeventgridtopic.westeurope-1.eventgrid.azure.net/api/events") + }; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.RabbitMQ.Commands.RabbitMqCommandQueue.md b/.docfx/api/types/Savvyio.Extensions.RabbitMQ.Commands.RabbitMqCommandQueue.md new file mode 100644 index 0000000..7f5b7d9 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.RabbitMQ.Commands.RabbitMqCommandQueue.md @@ -0,0 +1,25 @@ +--- +uid: Savvyio.Extensions.RabbitMQ.Commands.RabbitMqCommandQueue +example: +- *content +--- +`RabbitMqCommandQueue` publishes and receives commands through RabbitMQ. Configure it with `RabbitMqCommandQueueOptions` and use it as `IPointToPointChannel`. + +```csharp +using System; +using Savvyio.Commands; +using Savvyio.Extensions.RabbitMQ.Commands; +using Savvyio.Messaging; + +namespace ExampleApp; + +public class RabbitMqCommandQueueConfig +{ + public static RabbitMqCommandQueueOptions CreateOptions() + { + return new RabbitMqCommandQueueOptions { AmqpUrl = new Uri("amqp://guest:guest@localhost:5672"), QueueName = "account-commands", Durable = true }; + } + + public static IPointToPointChannel AsChannel(RabbitMqCommandQueue queue) => queue; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.RabbitMQ.Commands.RabbitMqCommandQueueOptions.md b/.docfx/api/types/Savvyio.Extensions.RabbitMQ.Commands.RabbitMqCommandQueueOptions.md new file mode 100644 index 0000000..8417b6c --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.RabbitMQ.Commands.RabbitMqCommandQueueOptions.md @@ -0,0 +1,29 @@ +--- +uid: Savvyio.Extensions.RabbitMQ.Commands.RabbitMqCommandQueueOptions +example: +- *content +--- +Configure a `RabbitMqCommandQueueOptions` with the AMQP URL and queue settings required for durable RabbitMQ command delivery. + +```csharp +using System; +using Savvyio.Extensions.RabbitMQ.Commands; + +namespace ExampleApp; + +public class RabbitMqCommandSetup +{ + public static RabbitMqCommandQueueOptions CreateOptions() + { + var options = new RabbitMqCommandQueueOptions + { + AmqpUrl = new Uri("amqp://guest:guest@localhost:5672"), + QueueName = "account-commands", + Durable = true, + Persistent = true, + AutoAcknowledge = false + }; + return options; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.RabbitMQ.EventDriven.RabbitMqEventBus.md b/.docfx/api/types/Savvyio.Extensions.RabbitMQ.EventDriven.RabbitMqEventBus.md new file mode 100644 index 0000000..612de44 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.RabbitMQ.EventDriven.RabbitMqEventBus.md @@ -0,0 +1,21 @@ +--- +uid: Savvyio.Extensions.RabbitMQ.EventDriven.RabbitMqEventBus +example: +- *content +--- +`RabbitMqEventBus` publishes and subscribes to integration events through RabbitMQ. Configure it with `RabbitMqEventBusOptions` and use it as `IPublishSubscribeChannel`. + +```csharp +using System; +using Savvyio.EventDriven; +using Savvyio.Extensions.RabbitMQ.EventDriven; +using Savvyio.Messaging; + +namespace ExampleApp; + +public class RabbitMqEventBusConfig +{ + public static RabbitMqEventBusOptions CreateOptions() => new RabbitMqEventBusOptions { AmqpUrl = new Uri("amqp://guest:guest@localhost:5672"), ExchangeName = "events" }; + public static IPublishSubscribeChannel AsChannel(RabbitMqEventBus bus) => bus; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.RabbitMQ.EventDriven.RabbitMqEventBusOptions.md b/.docfx/api/types/Savvyio.Extensions.RabbitMQ.EventDriven.RabbitMqEventBusOptions.md new file mode 100644 index 0000000..296e3f5 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.RabbitMQ.EventDriven.RabbitMqEventBusOptions.md @@ -0,0 +1,27 @@ +--- +uid: Savvyio.Extensions.RabbitMQ.EventDriven.RabbitMqEventBusOptions +example: +- *content +--- +Configure a `RabbitMqEventBusOptions` with the AMQP URL and exchange settings required for RabbitMQ event publishing and subscription. + +```csharp +using System; +using Savvyio.Extensions.RabbitMQ.EventDriven; + +namespace ExampleApp; + +public class RabbitMqEventSetup +{ + public static RabbitMqEventBusOptions CreateOptions() + { + var options = new RabbitMqEventBusOptions + { + AmqpUrl = new Uri("amqp://guest:guest@localhost:5672"), + ExchangeName = "account-events", + Persistent = true + }; + return options; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.RabbitMQ.RabbitMqMessageOptions.md b/.docfx/api/types/Savvyio.Extensions.RabbitMQ.RabbitMqMessageOptions.md new file mode 100644 index 0000000..79c0935 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.RabbitMQ.RabbitMqMessageOptions.md @@ -0,0 +1,26 @@ +--- +uid: Savvyio.Extensions.RabbitMQ.RabbitMqMessageOptions +example: +- *content +--- +Configure a `RabbitMqMessageOptions` with the AMQP URL and message persistence settings shared by all RabbitMQ message types in Savvy I/O. + +```csharp +using System; +using Savvyio.Extensions.RabbitMQ; + +namespace ExampleApp; + +public class RabbitMqSetup +{ + public static RabbitMqMessageOptions CreateOptions() + { + var options = new RabbitMqMessageOptions + { + AmqpUrl = new Uri("amqp://guest:guest@localhost:5672"), + Persistent = true + }; + return options; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.SavvyioOptionsExtensions.md b/.docfx/api/types/Savvyio.Extensions.SavvyioOptionsExtensions.md new file mode 100644 index 0000000..dd62c19 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.SavvyioOptionsExtensions.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.SavvyioOptionsExtensions +example: +- *content +--- +`SavvyioOptionsExtensions` adds `AddMediator` and the discovery switches `UseAutomaticDispatcherDiscovery` and `UseAutomaticHandlerDiscovery` to `SavvyioOptions`. Chain them to configure the framework for automatic assembly scanning. + +```csharp +using System; +using Savvyio; +using Savvyio.Extensions; + +namespace ExampleApp; + +public sealed class SavvyioOptionsConfiguration +{ + public void Configure() + { + var options = new SavvyioOptions() + .AddMediator() + .UseAutomaticDispatcherDiscovery() + .UseAutomaticHandlerDiscovery(); + + Console.WriteLine($"HandlerDiscovery: {options.AllowHandlerDiscovery}, DispatcherDiscovery: {options.AllowDispatcherDiscovery}"); + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonMessageOptions.md b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonMessageOptions.md new file mode 100644 index 0000000..f9ee7c6 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonMessageOptions.md @@ -0,0 +1,29 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.AmazonMessageOptions +example: +- *content +--- +Configure an `AmazonMessageOptions` with the AWS credentials, endpoint region, and SQS queue URL required for Amazon SQS message delivery. + +```csharp +using System; +using Amazon; +using Amazon.Runtime; +using Savvyio.Extensions.SimpleQueueService; + +namespace ExampleApp; + +public class AmazonSetup +{ + public static AmazonMessageOptions CreateOptions() + { + var options = new AmazonMessageOptions + { + Credentials = new AnonymousAWSCredentials(), + Endpoint = RegionEndpoint.EUWest1, + SourceQueue = new Uri("https://sqs.eu-west-1.amazonaws.com/123456789012/account-commands") + }; + return options; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonMessageReceiveOptions.md b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonMessageReceiveOptions.md new file mode 100644 index 0000000..23acbc4 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonMessageReceiveOptions.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.AmazonMessageReceiveOptions +example: +- *content +--- +`AmazonMessageReceiveOptions` controls SQS polling behavior — number of messages, visibility timeout, polling timeout, and whether to remove processed messages. Access it through `AmazonMessageOptions.ReceiveContext`. + +```csharp +using System; +using Amazon; +using Amazon.Runtime; +using Savvyio.Extensions.SimpleQueueService; + +namespace ExampleApp; + +public class AmazonReceiveSetup +{ + public static AmazonMessageOptions CreateOptions() + { + var options = new AmazonMessageOptions + { + Credentials = new AnonymousAWSCredentials(), + Endpoint = RegionEndpoint.EUWest1, + SourceQueue = new Uri("https://sqs.eu-west-1.amazonaws.com/123456789012/account-commands") + }; + AmazonMessageReceiveOptions receiveOptions = options.ReceiveContext; + receiveOptions.NumberOfMessagesToTakePerRequest = 5; + receiveOptions.VisibilityTimeout = TimeSpan.FromSeconds(30); + receiveOptions.PollingTimeout = TimeSpan.FromSeconds(20); + receiveOptions.RemoveProcessedMessages = true; + return options; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonResourceNameOptions.md b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonResourceNameOptions.md new file mode 100644 index 0000000..83bc4b3 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.AmazonResourceNameOptions.md @@ -0,0 +1,26 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.AmazonResourceNameOptions +example: +- *content +--- +`AmazonResourceNameOptions` builds Amazon Resource Names (ARNs) for SQS queues and SNS topics using the configured partition, region, and account ID. + +```csharp +using Savvyio.Extensions.SimpleQueueService; + +namespace ExampleApp; + +public class AmazonResourceNameSetup +{ + public static string BuildQueueArn(string queueName) + { + var options = new AmazonResourceNameOptions + { + Partition = "aws", + Region = "eu-west-1", + AccountId = "123456789012" + }; + return $"arn:{options.Partition}:sqs:{options.Region}:{options.AccountId}:{queueName}"; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.ClientConfigExtensions.md b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.ClientConfigExtensions.md new file mode 100644 index 0000000..f63b189 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.ClientConfigExtensions.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.ClientConfigExtensions +example: +- *content +--- +Use `ClientConfigExtensions` to validate and access the typed AWS client configurations for SQS and SNS from a `ClientConfig[]` array. + +```csharp +using Amazon.Runtime; +using Amazon.SQS; +using Savvyio.Extensions.SimpleQueueService; + +namespace ExampleApp; + +public class AwsClientConfigExample +{ + public static void CheckConfigurations(ClientConfig[] configurations) + { + if (!configurations.IsValid()) + { + throw new System.InvalidOperationException("AWS client configurations are invalid."); + } + + var sqsConfig = configurations.SimpleQueueService(); + var snsConfig = configurations.SimpleNotificationService(); + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.Commands.AmazonCommandQueue.md b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.Commands.AmazonCommandQueue.md new file mode 100644 index 0000000..2a323cc --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.Commands.AmazonCommandQueue.md @@ -0,0 +1,23 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.Commands.AmazonCommandQueue +example: +- *content +--- +`AmazonCommandQueue` sends and receives commands through Amazon SQS. Configure it with `AmazonCommandQueueOptions` and use it as `IPointToPointChannel`. + +```csharp +using System; +using Amazon; +using Amazon.Runtime; +using Savvyio.Commands; +using Savvyio.Extensions.SimpleQueueService.Commands; +using Savvyio.Messaging; + +namespace ExampleApp; + +public class SqsCommandQueueConfig +{ + public static AmazonCommandQueueOptions CreateOptions() => new AmazonCommandQueueOptions { Credentials = new AnonymousAWSCredentials(), Endpoint = RegionEndpoint.EUWest1, SourceQueue = new Uri("https://sqs.eu-west-1.amazonaws.com/123456789012/commands") }; + public static IPointToPointChannel AsChannel(AmazonCommandQueue q) => q; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.Commands.AmazonCommandQueueOptions.md b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.Commands.AmazonCommandQueueOptions.md new file mode 100644 index 0000000..948e5ec --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.Commands.AmazonCommandQueueOptions.md @@ -0,0 +1,32 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.Commands.AmazonCommandQueueOptions +example: +- *content +--- +Configure `AmazonCommandQueueOptions` for a command queue by setting AWS credentials, the source queue URL, and fine-tuning the receive behavior through `ReceiveContext`. + +```csharp +using System; +using Amazon; +using Amazon.Runtime; +using Savvyio.Extensions.SimpleQueueService.Commands; + +namespace ExampleApp; + +public class AmazonCommandQueueConfig +{ + public static AmazonCommandQueueOptions CreateOptions() + { + var options = new AmazonCommandQueueOptions + { + Credentials = new AnonymousAWSCredentials(), + Endpoint = RegionEndpoint.EUWest1, + SourceQueue = new Uri("https://sqs.eu-west-1.amazonaws.com/123456789012/account-commands") + }; + options.ReceiveContext.NumberOfMessagesToTakePerRequest = 5; + options.ReceiveContext.RemoveProcessedMessages = true; + return options; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.AmazonEventBus.md b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.AmazonEventBus.md new file mode 100644 index 0000000..aa498c4 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.AmazonEventBus.md @@ -0,0 +1,23 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.EventDriven.AmazonEventBus +example: +- *content +--- +`AmazonEventBus` publishes and subscribes to integration events through Amazon SNS/SQS. Configure it with `AmazonEventBusOptions` and use it as `IPublishSubscribeChannel`. + +```csharp +using System; +using Amazon; +using Amazon.Runtime; +using Savvyio.EventDriven; +using Savvyio.Extensions.SimpleQueueService.EventDriven; +using Savvyio.Messaging; + +namespace ExampleApp; + +public class SnsEventBusConfig +{ + public static AmazonEventBusOptions CreateOptions() => new AmazonEventBusOptions { Credentials = new AnonymousAWSCredentials(), Endpoint = RegionEndpoint.EUWest1, SourceQueue = new Uri("https://sqs.eu-west-1.amazonaws.com/123456789012/events") }; + public static IPublishSubscribeChannel AsChannel(AmazonEventBus b) => b; +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.AmazonEventBusOptions.md b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.AmazonEventBusOptions.md new file mode 100644 index 0000000..ffbb12a --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.AmazonEventBusOptions.md @@ -0,0 +1,33 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.EventDriven.AmazonEventBusOptions +example: +- *content +--- +Configure `AmazonEventBusOptions` for an SNS/SQS event bus by providing AWS credentials, endpoint, and source queue URL. Adjust the polling timeout and visibility timeout via `ReceiveContext`. + +```csharp +using System; +using Amazon; +using Amazon.Runtime; +using Savvyio.Extensions.SimpleQueueService.EventDriven; + +namespace ExampleApp; + +public class AmazonEventBusConfig +{ + public static AmazonEventBusOptions CreateOptions() + { + var options = new AmazonEventBusOptions + { + Credentials = new AnonymousAWSCredentials(), + Endpoint = RegionEndpoint.EUWest1, + SourceQueue = new Uri("https://sqs.eu-west-1.amazonaws.com/123456789012/account-events") + }; + options.ReceiveContext.PollingTimeout = TimeSpan.FromSeconds(20); + options.ReceiveContext.VisibilityTimeout = TimeSpan.FromSeconds(30); + return options; + } +} +``` + + diff --git a/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.StringExtensions.md b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.StringExtensions.md new file mode 100644 index 0000000..1a13211 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.SimpleQueueService.EventDriven.StringExtensions.md @@ -0,0 +1,27 @@ +--- +uid: Savvyio.Extensions.SimpleQueueService.EventDriven.StringExtensions +example: +- *content +--- +Use `StringExtensions.ToSnsUri` to convert an SNS topic name to a `Uri` formatted as an Amazon Resource Name (ARN) for use as the event bus source. + +```csharp +using System; +using Savvyio.Extensions.SimpleQueueService.EventDriven; + +namespace ExampleApp; + +public class SnsUriExample +{ + public static Uri BuildSnsTopicUri() + { + var topicArn = "account-events".ToSnsUri(options => + { + options.Partition = "aws"; + options.Region = "eu-west-1"; + options.AccountId = "123456789012"; + }); + return topicArn; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.DateTimeConverter.md b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.DateTimeConverter.md new file mode 100644 index 0000000..9f70b95 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.DateTimeConverter.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.Text.Json.Converters.DateTimeConverter +example: +- *content +--- +Add `DateTimeConverter` to `JsonSerializerOptions` to keep `DateTime` values in ISO8601 format. + +```csharp +using System; +using System.Text.Json; +using Savvyio.Extensions.Text.Json.Converters; + +namespace ExampleApp; + +public static class DateTimeSerialization +{ + public static SchedulingWindow RoundTrip(SchedulingWindow value) + { + var options = new JsonSerializerOptions(); + options.Converters.Add(new DateTimeConverter()); + + var json = JsonSerializer.Serialize(value, options); + return JsonSerializer.Deserialize(json, options)!; + } +} + +public sealed record SchedulingWindow(DateTime StartsAt); +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.DateTimeOffsetConverter.md b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.DateTimeOffsetConverter.md new file mode 100644 index 0000000..d0c1397 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.DateTimeOffsetConverter.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.Text.Json.Converters.DateTimeOffsetConverter +example: +- *content +--- +Add `DateTimeOffsetConverter` to `JsonSerializerOptions` to preserve offset-aware timestamps in ISO8601 format. + +```csharp +using System; +using System.Text.Json; +using Savvyio.Extensions.Text.Json.Converters; + +namespace ExampleApp; + +public static class DateTimeOffsetSerialization +{ + public static Appointment RoundTrip(Appointment value) + { + var options = new JsonSerializerOptions(); + options.Converters.Add(new DateTimeOffsetConverter()); + + var json = JsonSerializer.Serialize(value, options); + return JsonSerializer.Deserialize(json, options)!; + } +} + +public sealed record Appointment(DateTimeOffset ScheduledAt); +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.MessageConverter.md b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.MessageConverter.md new file mode 100644 index 0000000..1184b65 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.MessageConverter.md @@ -0,0 +1,40 @@ +--- +uid: Savvyio.Extensions.Text.Json.Converters.MessageConverter +example: +- *content +--- +`MessageConverter` is the System.Text.Json converter that serializes and deserializes `IMessage` envelopes including their source URI, type discriminator, creation time, and typed payload. Add it to `JsonSerializerOptions.Converters` before serializing any message. The example serializes an order command wrapped in `Message` and rounds it back to an `IMessage`. + +```csharp +using Savvyio; +using System; +using System.Text.Json; +using Savvyio.Extensions.Text.Json.Converters; +using Savvyio.Messaging; + +namespace ExampleApp; + +public static class MessageSerialization +{ + public static Message RoundTrip() + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + options.Converters.Add(new MessageConverter()); + + var message = new Message( + "msg-001", + new Uri("urn:orders"), + nameof(ShipOrderCommand), + new ShipOrderCommand("SO-42"), + DateTime.UtcNow); + + var json = JsonSerializer.Serialize(message, options); + return JsonSerializer.Deserialize>(json, options)!; + } +} + +public sealed record ShipOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.MetadataDictionaryConverter.md b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.MetadataDictionaryConverter.md new file mode 100644 index 0000000..f85b97e --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.MetadataDictionaryConverter.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Extensions.Text.Json.Converters.MetadataDictionaryConverter +example: +- *content +--- +`MetadataDictionaryConverter` handles `IMetadataDictionary` serialization and deserialization, preserving the string-keyed metadata that commands, events, and requests carry through the pipeline. Register it alongside `MessageConverter` so metadata survives a full message round-trip. The example serializes a command with causation and correlation metadata and confirms the values survive deserialization. + +```csharp +using System.Text.Json; +using Savvyio; +using Savvyio.Extensions.Text.Json.Converters; + +namespace ExampleApp; + +public static class MetadataSerialization +{ + public static IMetadataDictionary RoundTrip() + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + options.Converters.Add(new MetadataDictionaryConverter()); + + IMetadataDictionary metadata = new MetadataDictionary + { + ["tenant"] = "northwind", + ["attempts"] = 3L + }; + + var json = JsonSerializer.Serialize(metadata, options); + return JsonSerializer.Deserialize(json, options)!; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.RequestConverter.md b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.RequestConverter.md new file mode 100644 index 0000000..0608c0a --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.RequestConverter.md @@ -0,0 +1,31 @@ +--- +uid: Savvyio.Extensions.Text.Json.Converters.RequestConverter +example: +- *content +--- +`RequestConverter` is the System.Text.Json converter for `IRequest` implementations, enabling polymorphic deserialization of command and query payloads without explicit type discriminators. Add it to `JsonSerializerOptions.Converters` whenever commands or queries are serialized independently of a message envelope. The example serializes a command and deserializes it back through the interface. + +```csharp +using Savvyio; +using System.Text.Json; +using Savvyio.Extensions.Text.Json.Converters; + +namespace ExampleApp; + +public static class RequestSerialization +{ + public static CreateOrderCommand RoundTrip() + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + options.Converters.Add(new RequestConverter()); + + var json = JsonSerializer.Serialize(new CreateOrderCommand("SO-42", 2), options); + return JsonSerializer.Deserialize(json, options)!; + } +} + +public sealed record CreateOrderCommand(string OrderId, int Quantity) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.SingleValueObjectConverter.md b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.SingleValueObjectConverter.md new file mode 100644 index 0000000..320d511 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Text.Json.Converters.SingleValueObjectConverter.md @@ -0,0 +1,36 @@ +--- +uid: Savvyio.Extensions.Text.Json.Converters.SingleValueObjectConverter +example: +- *content +--- +`SingleValueObjectConverter` supports `ISingleValueObject` serialization, writing the underlying primitive value as a JSON value rather than a nested object. This allows value objects like `EmailAddress` or `Money` to round-trip as plain JSON strings or numbers. The example serializes a typed value object and verifies the JSON representation is the raw value. + +```csharp +using System.Text.Json; +using Savvyio.Domain; +using Savvyio.Extensions.Text.Json.Converters; + +namespace ExampleApp; + +public static class SingleValueObjectSerialization +{ + public static OrderNumber RoundTrip(OrderNumber orderNumber) + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + options.Converters.Add(new SingleValueObjectConverter()); + + var json = JsonSerializer.Serialize(orderNumber, options); + return JsonSerializer.Deserialize(json, options)!; + } +} + +public sealed record OrderNumber : SingleValueObject +{ + public OrderNumber(string value) : base(value) + { + } +} +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Text.Json.JsonConverterExtensions.md b/.docfx/api/types/Savvyio.Extensions.Text.Json.JsonConverterExtensions.md new file mode 100644 index 0000000..5aabd77 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Text.Json.JsonConverterExtensions.md @@ -0,0 +1,47 @@ +--- +uid: Savvyio.Extensions.Text.Json.JsonConverterExtensions +example: +- *content +--- +`JsonConverterExtensions` provides fluent methods for adding the complete set of Savvy I/O converters to `JsonSerializerOptions.Converters`. The prerequisite is an existing `JsonSerializerOptions` instance; each extension method returns the same `ICollection` so calls can be chained. The example chains all available registration methods and serializes an `IMessage` to verify the converters compose correctly. + +```csharp +using Savvyio; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Savvyio.Extensions.Text.Json; +using Savvyio.Messaging; + +namespace ExampleApp; + +public static class JsonOptionsFactory +{ + public static JsonSerializerOptions Create() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + options.Converters.RemoveAllOf(typeof(JsonConverter)); + options.Converters.RemoveAllOf(); + options.Converters.AddMetadataDictionaryConverter(); + options.Converters.AddMessageConverter(); + options.Converters.AddRequestConverter(); + options.Converters.AddSingleValueObjectConverter(); + options.Converters.AddDateTimeConverter(); + options.Converters.AddDateTimeOffsetConverter(); + + var message = new Message( + "msg-001", + new Uri("urn:orders"), + nameof(PlaceOrderCommand), + new PlaceOrderCommand("SO-42"), + DateTime.UtcNow); + + _ = JsonSerializer.Serialize(message, options); + return options; + } +} + +public sealed record PlaceOrderCommand(string OrderId) : Request; +``` + diff --git a/.docfx/api/types/Savvyio.Extensions.Text.Json.JsonMarshaller.md b/.docfx/api/types/Savvyio.Extensions.Text.Json.JsonMarshaller.md new file mode 100644 index 0000000..6c72375 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Text.Json.JsonMarshaller.md @@ -0,0 +1,32 @@ +--- +uid: Savvyio.Extensions.Text.Json.JsonMarshaller +example: +- *content +--- +Use `JsonMarshaller` to serialize and deserialize Savvy I/O requests with System.Text.Json. + +```csharp +using Savvyio; +using System.Text.Json; +using Savvyio.Extensions.Text.Json; + +namespace ExampleApp; + +public static class MarshallerExample +{ + public static ShipOrderCommand RoundTrip(ShipOrderCommand command) + { + var marshaller = JsonMarshaller.Create(options => + { + options.Settings.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.Settings.WriteIndented = true; + }); + + using var stream = marshaller.Serialize(command); + stream.Position = 0; + return marshaller.Deserialize(stream); + } +} + +public sealed record ShipOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Extensions.Text.Json.JsonSerializerOptionsExtensions.md b/.docfx/api/types/Savvyio.Extensions.Text.Json.JsonSerializerOptionsExtensions.md new file mode 100644 index 0000000..ba29c94 --- /dev/null +++ b/.docfx/api/types/Savvyio.Extensions.Text.Json.JsonSerializerOptionsExtensions.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.Extensions.Text.Json.JsonSerializerOptionsExtensions +example: +- *content +--- +Use `Clone` to copy `JsonSerializerOptions` before applying additional serializer settings. + +```csharp +using System.Text.Json; +using Savvyio.Extensions.Text.Json; +using Savvyio.Extensions.Text.Json.Converters; + +namespace ExampleApp; + +public static class JsonSerializerOptionsFactory +{ + public static JsonSerializerOptions CreateIndentedClone() + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + options.Converters.Add(new DateTimeConverter()); + + return options.Clone(copy => copy.WriteIndented = true); + } +} +``` diff --git a/.docfx/api/types/Savvyio.HandlerDiscoveryModel.md b/.docfx/api/types/Savvyio.HandlerDiscoveryModel.md new file mode 100644 index 0000000..e5b57e5 --- /dev/null +++ b/.docfx/api/types/Savvyio.HandlerDiscoveryModel.md @@ -0,0 +1,36 @@ +--- +uid: Savvyio.HandlerDiscoveryModel +example: +- *content +--- +This example shows how to create a handler discovery model from the grouped service output that a descriptor consumes. + +```csharp +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Cuemon.Extensions.Runtime; +using Savvyio; +using Savvyio.Commands; + +namespace ExampleApp; + +public sealed class HandlerDiscoveryModelExample +{ + public HandlerDiscoveryModel Create() + { + var group = new Grouping>>>(typeof(ICommandHandler), new List>>>()); + return new HandlerDiscoveryModel(typeof(ICommandHandler), typeof(ICommand), group); + } +} + +internal sealed class Grouping : IGrouping +{ + private readonly IEnumerable _elements; + public Grouping(TKey key, IEnumerable elements) { Key = key; _elements = elements; } + public TKey Key { get; } + public IEnumerator GetEnumerator() => _elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} +``` diff --git a/.docfx/api/types/Savvyio.HandlerFactory.md b/.docfx/api/types/Savvyio.HandlerFactory.md new file mode 100644 index 0000000..9dfb94c --- /dev/null +++ b/.docfx/api/types/Savvyio.HandlerFactory.md @@ -0,0 +1,31 @@ +--- +uid: Savvyio.HandlerFactory +example: +- *content +--- +`HandlerFactory` creates the activator delegates used internally by Savvy I/O handlers to bind request types to handler methods. Use `HandlerFactory.CreateFireForget` to build fire-and-forget delegates or `CreateRequestReply` for delegates that return a response. + +```csharp +using System; +using System.Threading.Tasks; +using Savvyio; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class HandlerFactoryExample +{ + public async Task DispatchAsync() + { + var notifications = HandlerFactory.CreateFireForget( + r => r.Register(cmd => Console.WriteLine("Processing: " + cmd.OrderId))); + + var command = new CreateOrderCommand("ORD-42"); + notifications.TryInvoke(command); + await notifications.TryInvokeAsync(command).ConfigureAwait(false); + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` + diff --git a/.docfx/api/types/Savvyio.HandlerServiceAssemblyModel.md b/.docfx/api/types/Savvyio.HandlerServiceAssemblyModel.md new file mode 100644 index 0000000..01c271c --- /dev/null +++ b/.docfx/api/types/Savvyio.HandlerServiceAssemblyModel.md @@ -0,0 +1,33 @@ +--- +uid: Savvyio.HandlerServiceAssemblyModel +example: +- *content +--- +`HandlerServiceAssemblyModel` exposes the assembly name and namespace of a discovered handler implementation, populated during the handler discovery phase at startup. Create one by passing the handler implementation type to the constructor. The example creates a model from a concrete handler type and prints the assembly name and namespace. + +```csharp +using System; +using Savvyio; +using Savvyio.Commands; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class HandlerServiceAssemblyModelExample +{ + public void Describe() + { + var model = new HandlerServiceAssemblyModel(typeof(CreateOrderHandler)); + Console.WriteLine($"Assembly: {model.Name}, Namespace: {model.Namespace}"); + } +} + +public sealed class CreateOrderHandler : ICommandHandler +{ + public IFireForgetActivator Delegates => + HandlerFactory.CreateFireForget(r => r.Register(_ => { })); +} + +public sealed record CreateOrderCommand(string OrderId) : Request, ICommand; +``` + diff --git a/.docfx/api/types/Savvyio.HandlerServiceTypeImplementationDelegatesModel.md b/.docfx/api/types/Savvyio.HandlerServiceTypeImplementationDelegatesModel.md new file mode 100644 index 0000000..e9658af --- /dev/null +++ b/.docfx/api/types/Savvyio.HandlerServiceTypeImplementationDelegatesModel.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.HandlerServiceTypeImplementationDelegatesModel +example: +- *content +--- +`HandlerServiceTypeImplementationDelegatesModel` represents one delegate entry in handler discovery output — the request type handled and the name of the registered method delegate. + +```csharp +using System; +using Savvyio; + +namespace ExampleApp; + +public sealed class HandlerDelegateInspection +{ + public static void PrintDelegates() + { + var entry = new HandlerServiceTypeImplementationDelegatesModel( + typeof(CreateOrderCommand).Name, + "HandleCreateOrderAsync"); + + Console.WriteLine($"Request: {entry.Type}, Handler method: {entry.Handler}"); + } +} + +public sealed record CreateOrderCommand(string OrderId); +``` + diff --git a/.docfx/api/types/Savvyio.HandlerServiceTypeImplementationModel.md b/.docfx/api/types/Savvyio.HandlerServiceTypeImplementationModel.md new file mode 100644 index 0000000..26e9245 --- /dev/null +++ b/.docfx/api/types/Savvyio.HandlerServiceTypeImplementationModel.md @@ -0,0 +1,37 @@ +--- +uid: Savvyio.HandlerServiceTypeImplementationModel +example: +- *content +--- +This example shows how to read implementation entries from the discovery report a handler descriptor produces. + +```csharp +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Cuemon.Extensions.Runtime; +using Savvyio; +using Savvyio.Commands; + +namespace ExampleApp; + +public sealed class HandlerServiceTypeImplementationModelExample +{ + public IEnumerable ReadImplementationNames() + { + var descriptor = new HandlerServicesDescriptor(new[] { new Grouping>>>(typeof(ICommandHandler), new List>>>()) }, new[] { typeof(ICommandHandler) }); + IEnumerable implementations = descriptor.GenerateHandlerDiscoveries().SelectMany(model => model.Assemblies ?? Array.Empty()).SelectMany(assembly => assembly.Implementations ?? Array.Empty()); + return implementations.Select(implementation => implementation.Name); + } +} + +internal sealed class Grouping : IGrouping +{ + private readonly IEnumerable _elements; + public Grouping(TKey key, IEnumerable elements) { Key = key; _elements = elements; } + public TKey Key { get; } + public IEnumerator GetEnumerator() => _elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} +``` diff --git a/.docfx/api/types/Savvyio.HandlerServicesDescriptor.md b/.docfx/api/types/Savvyio.HandlerServicesDescriptor.md new file mode 100644 index 0000000..3c51d36 --- /dev/null +++ b/.docfx/api/types/Savvyio.HandlerServicesDescriptor.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.HandlerServicesDescriptor +example: +- *content +--- +`HandlerServicesDescriptor` produces a human-readable handler discovery report. Create it with the service groups and types from the DI container or empty collections for diagnostics, then call `ToString()` to get the formatted report. + +```csharp +using System; +using System.Collections.Generic; +using System.Linq; +using Savvyio; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class HandlerServicesDescriptorExample +{ + public static void PrintReport() + { + var emptyGroups = Enumerable.Empty>>>>(); + var descriptor = new HandlerServicesDescriptor(emptyGroups, new[] { typeof(IHandler) }); + Console.WriteLine(descriptor.ToString()); + } +} +``` + + diff --git a/.docfx/api/types/Savvyio.Handlers.FireForgetRegistryExtensions.md b/.docfx/api/types/Savvyio.Handlers.FireForgetRegistryExtensions.md new file mode 100644 index 0000000..75b10d3 --- /dev/null +++ b/.docfx/api/types/Savvyio.Handlers.FireForgetRegistryExtensions.md @@ -0,0 +1,45 @@ +--- +uid: Savvyio.Handlers.FireForgetRegistryExtensions +example: +- *content +--- +`FireForgetRegistryExtensions.RegisterAsync` simplifies async fire-and-forget handler registration. Instead of passing the full `Func` delegate with a cancellation token, you pass a simpler `Func` and the extension wraps it for the registry. The example creates a registry stub that records whether the async path was taken, registers a handler for `CreateOrderCommand`, and verifies the registration result. + +```csharp +using System; +using System.Threading; +using System.Threading.Tasks; +using Savvyio; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class FireForgetRegistryExtensionsExample +{ + public bool Register() + { + var registry = new RecordingFireForgetRegistry(); + registry.RegisterAsync(async cmd => + { + Console.WriteLine("Processing: " + cmd.OrderId); + await Task.CompletedTask; + }); + return registry.RegisterAsyncCalled; + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; + +public sealed class RecordingFireForgetRegistry : IFireForgetRegistry +{ + public bool RegisterAsyncCalled { get; private set; } + + public void Register(Action handler) where T : class, IRequest { } + + public void RegisterAsync(Func handler) where T : class, IRequest + { + RegisterAsyncCalled = true; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Handlers.OrphanedHandlerException.md b/.docfx/api/types/Savvyio.Handlers.OrphanedHandlerException.md new file mode 100644 index 0000000..398e076 --- /dev/null +++ b/.docfx/api/types/Savvyio.Handlers.OrphanedHandlerException.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Handlers.OrphanedHandlerException +example: +- *content +--- +Throw an `OrphanedHandlerException` when a dispatcher receives a request type with no registered handler. Use `OrphanedHandlerException.Create` to produce the exception with a formatted message. + +```csharp +using System; +using Savvyio; +using Savvyio.Dispatchers; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class DispatcherWithValidation +{ + public void ValidateHandlerPresence(IRequest request) + where THandler : IHandler + { + try + { + throw OrphanedHandlerException.Create(request, "request"); + } + catch (OrphanedHandlerException exception) + { + Console.WriteLine($"No handler found: {exception.Message}"); + } + } +} + +public sealed record PlaceOrderRequest(string OrderId) : Request; +``` + + diff --git a/.docfx/api/types/Savvyio.Handlers.RequestReplyRegistryExtensions.md b/.docfx/api/types/Savvyio.Handlers.RequestReplyRegistryExtensions.md new file mode 100644 index 0000000..3ad2466 --- /dev/null +++ b/.docfx/api/types/Savvyio.Handlers.RequestReplyRegistryExtensions.md @@ -0,0 +1,45 @@ +--- +uid: Savvyio.Handlers.RequestReplyRegistryExtensions +example: +- *content +--- +`RequestReplyRegistryExtensions.RegisterAsync` simplifies async request-reply handler registration. Instead of the full `Func>` delegate, you pass a `Func>` and the extension wraps it. The example registers a handler that returns an order status string, exercises the async path, and confirms it was selected over the synchronous overload. + +```csharp +using System; +using System.Threading; +using System.Threading.Tasks; +using Savvyio; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class RequestReplyRegistryExtensionsExample +{ + public bool Register() + { + var registry = new RecordingRequestReplyRegistry(); + registry.RegisterAsync(async query => + { + await Task.CompletedTask; + return "Order " + query.OrderId; + }); + return registry.RegisterAsyncCalled; + } +} + +public sealed record GetOrderQuery(string OrderId) : Request; + +public sealed class RecordingRequestReplyRegistry : IRequestReplyRegistry +{ + public bool RegisterAsyncCalled { get; private set; } + + public void Register(Func handler) where T : class, IRequest { } + + public void RegisterAsync(Func> handler) where T : class, IRequest + { + RegisterAsyncCalled = true; + } +} +``` + diff --git a/.docfx/api/types/Savvyio.Messaging.AcknowledgedEventArgs.md b/.docfx/api/types/Savvyio.Messaging.AcknowledgedEventArgs.md new file mode 100644 index 0000000..c8239b2 --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.AcknowledgedEventArgs.md @@ -0,0 +1,35 @@ +--- +uid: Savvyio.Messaging.AcknowledgedEventArgs +example: +- *content +--- +This example shows how to capture the AcknowledgedEventArgs payload that a message publishes after successful processing. The handler promotes transport properties into the event args so later pipeline steps can inspect the acknowledgement state. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Savvyio; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class AcknowledgedEventArgsExample +{ + public async Task> CaptureAsync() + { + var message = new Message("msg-42", new Uri("urn:orders"), "orders.created", new CreateOrderCommand("ORD-42")); + message.Properties["tenant"] = "eu-west"; + AcknowledgedEventArgs? observed = null; + message.Acknowledged += (_, args) => + { + observed = args; + return Task.CompletedTask; + }; + await message.AcknowledgeAsync().ConfigureAwait(false); + return observed?.Properties ?? new Dictionary(); + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Messaging.Cryptography.MessageExtensions.md b/.docfx/api/types/Savvyio.Messaging.Cryptography.MessageExtensions.md new file mode 100644 index 0000000..0f1fd64 --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.Cryptography.MessageExtensions.md @@ -0,0 +1,39 @@ +--- +uid: Savvyio.Messaging.Cryptography.MessageExtensions +example: +- *content +--- +This example shows how to sign a message before handing it to a transport that requires tamper detection. + +```csharp +using System; +using Savvyio; +using Savvyio.Messaging; +using Savvyio.Messaging.Cryptography; + +namespace ExampleApp; + +using System; +using System.IO; +using System.Text; +using Savvyio; + +public sealed class DemoMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) => new MemoryStream(Encoding.UTF8.GetBytes(value?.ToString() ?? string.Empty)); + public Stream Serialize(object value, Type inputType) => Serialize(value?.ToString() ?? string.Empty); + public TValue Deserialize(Stream data) => throw new NotSupportedException(); + public object Deserialize(Stream data, Type returnType) => throw new NotSupportedException(); +} + +public sealed class CryptographicMessageExtensionsExample +{ + public ISignedMessage Sign() + { + var message = new Message("msg-42", new Uri("urn:orders"), "orders.created", new CreateOrderCommand("ORD-42")); + return message.Sign(new DemoMarshaller(), options => options.SignatureSecret = new byte[] { 1, 2, 3 }); + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessageExtensions.md b/.docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessageExtensions.md new file mode 100644 index 0000000..0329b3e --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessageExtensions.md @@ -0,0 +1,40 @@ +--- +uid: Savvyio.Messaging.Cryptography.SignedMessageExtensions +example: +- *content +--- +This example shows how to verify a signed message before a consumer trusts the request payload. + +```csharp +using System; +using Savvyio; +using Savvyio.Messaging; +using Savvyio.Messaging.Cryptography; + +namespace ExampleApp; + +using System; +using System.IO; +using System.Text; +using Savvyio; + +public sealed class DemoMarshaller : IMarshaller +{ + public Stream Serialize(TValue value) => new MemoryStream(Encoding.UTF8.GetBytes(value?.ToString() ?? string.Empty)); + public Stream Serialize(object value, Type inputType) => Serialize(value?.ToString() ?? string.Empty); + public TValue Deserialize(Stream data) => throw new NotSupportedException(); + public object Deserialize(Stream data, Type returnType) => throw new NotSupportedException(); +} + +public sealed class SignedMessageExtensionsExample +{ + public void Verify() + { + var message = new Message("msg-42", new Uri("urn:orders"), "orders.created", new CreateOrderCommand("ORD-42")); + var signed = message.Sign(new DemoMarshaller(), options => options.SignatureSecret = new byte[] { 1, 2, 3 }); + signed.CheckSignature(new DemoMarshaller(), options => options.SignatureSecret = new byte[] { 1, 2, 3 }); + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessageOptions.md b/.docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessageOptions.md new file mode 100644 index 0000000..a89d04a --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessageOptions.md @@ -0,0 +1,22 @@ +--- +uid: Savvyio.Messaging.Cryptography.SignedMessageOptions +example: +- *content +--- +This example shows how to configure the secret and algorithm used when producing verifiable signed messages. + +```csharp +using Savvyio.Messaging.Cryptography; + +namespace ExampleApp; + +public sealed class SignedMessageOptionsExample +{ + public SignedMessageOptions Configure() + { + var options = new SignedMessageOptions { SignatureSecret = new byte[] { 1, 2, 3 } }; + options.ValidateOptions(); + return options; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessage`1.md b/.docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessage`1.md new file mode 100644 index 0000000..1ae83ca --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.Cryptography.SignedMessage`1.md @@ -0,0 +1,33 @@ +--- +uid: Savvyio.Messaging.Cryptography.SignedMessage`1 +example: +- *content +--- +Attach a signature to an existing `IMessage` envelope using `MessageExtensions.Sign`. `SignedMessage` carries both the original message and the computed signature so the consumer can verify integrity before dispatching. + +```csharp +using System; +using Savvyio; +using Savvyio.Messaging; +using Savvyio.Messaging.Cryptography; + +namespace ExampleApp; + +public sealed class SignedMessageExample +{ + public void SignAndVerify() + { + var message = new Message( + "msg-42", + new Uri("urn:orders"), + "orders.create", + new CreateOrderCommand("ORD-42")); + + var signed = new SignedMessage(message, "hmac-signature-value"); + Console.WriteLine($"Message {signed.Id} signed with: {signed.Signature}"); + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` + diff --git a/.docfx/api/types/Savvyio.Messaging.MessageAsyncEnumerableOptions`1.md b/.docfx/api/types/Savvyio.Messaging.MessageAsyncEnumerableOptions`1.md new file mode 100644 index 0000000..e4f61f6 --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.MessageAsyncEnumerableOptions`1.md @@ -0,0 +1,31 @@ +--- +uid: Savvyio.Messaging.MessageAsyncEnumerableOptions`1 +example: +- *content +--- +This example shows how to configure callbacks that observe each streamed message and the properties acknowledged at the end of the sequence. + +```csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Savvyio; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class MessageAsyncEnumerableOptionsExample +{ + public MessageAsyncEnumerableOptions Configure() + { + var options = new MessageAsyncEnumerableOptions + { + MessageCallback = async message => await message.AcknowledgeAsync().ConfigureAwait(false), + AcknowledgedPropertiesCallback = async acknowledged => await Task.CompletedTask.ConfigureAwait(false) + }; + options.ValidateOptions(); + return options; + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Messaging.MessageAsyncEnumerable`1.md b/.docfx/api/types/Savvyio.Messaging.MessageAsyncEnumerable`1.md new file mode 100644 index 0000000..2668e17 --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.MessageAsyncEnumerable`1.md @@ -0,0 +1,43 @@ +--- +uid: Savvyio.Messaging.MessageAsyncEnumerable`1 +example: +- *content +--- +`MessageAsyncEnumerable` enables async enumeration over a stream of `IMessage` envelopes from a queue or bus. To use it, pass an async callback and options to its constructor; the callback is invoked for each page of messages. The example creates an enumerator over a small in-memory sequence to show the enumeration contract. + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Savvyio; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class MessageAsyncEnumerableExample +{ + public async Task ProcessAsync() + { + var messages = new[] + { + new Message("msg-42", new Uri("urn:orders"), "orders.created", new CreateOrderCommand("ORD-42")) + }; + + var stream = new MessageAsyncEnumerable(messages, options => + { + options.MessageCallback = async message => await message.AcknowledgeAsync().ConfigureAwait(false); + options.AcknowledgedPropertiesCallback = async acknowledged => await Task.CompletedTask.ConfigureAwait(false); + }); + + var count = 0; + await foreach (var message in stream.ConfigureAwait(false)) + { + count++; + } + + return count; + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Messaging.MessageExtensions.md b/.docfx/api/types/Savvyio.Messaging.MessageExtensions.md new file mode 100644 index 0000000..fb603d8 --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.MessageExtensions.md @@ -0,0 +1,25 @@ +--- +uid: Savvyio.Messaging.MessageExtensions +example: +- *content +--- +This example shows how to clone a message envelope before adding transport-specific state to the copy. + +```csharp +using System; +using Savvyio; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class MessageExtensionsExample +{ + public IMessage Clone() + { + var original = new Message("msg-42", new Uri("urn:orders"), "orders.created", new CreateOrderCommand("ORD-42")); + return original.Clone(); + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Messaging.MessageOptions.md b/.docfx/api/types/Savvyio.Messaging.MessageOptions.md new file mode 100644 index 0000000..7e63ffe --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.MessageOptions.md @@ -0,0 +1,23 @@ +--- +uid: Savvyio.Messaging.MessageOptions +example: +- *content +--- +This example shows how to configure message envelope metadata before a request is promoted to an outbound message. + +```csharp +using System; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class MessageOptionsExample +{ + public MessageOptions Configure() + { + var options = new MessageOptions { MessageId = "msg-42", Time = new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc) }; + options.ValidateOptions(); + return options; + } +} +``` diff --git a/.docfx/api/types/Savvyio.Messaging.Message`1.md b/.docfx/api/types/Savvyio.Messaging.Message`1.md new file mode 100644 index 0000000..427b814 --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.Message`1.md @@ -0,0 +1,31 @@ +--- +uid: Savvyio.Messaging.Message`1 +example: +- *content +--- +Wrap a request in a `Message` envelope to prepare it for transport to another subsystem. The envelope pairs the payload with a source URI, a type discriminator, and a unique ID. + +```csharp +using System; +using Savvyio; +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class MessageExample +{ + public void Send() + { + var message = new Message( + "msg-42", + new Uri("urn:orders"), + "orders.create", + new CreateOrderCommand("ORD-42")); + + Console.WriteLine($"Sending message {message.Id} from {message.Source} with type {message.Type}"); + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` + diff --git a/.docfx/api/types/Savvyio.Messaging.SubscribeAsyncOptions.md b/.docfx/api/types/Savvyio.Messaging.SubscribeAsyncOptions.md new file mode 100644 index 0000000..299640b --- /dev/null +++ b/.docfx/api/types/Savvyio.Messaging.SubscribeAsyncOptions.md @@ -0,0 +1,20 @@ +--- +uid: Savvyio.Messaging.SubscribeAsyncOptions +example: +- *content +--- +This example shows how to configure subscription behavior so a cancelled receive loop surfaces an OperationCanceledException when needed. + +```csharp +using Savvyio.Messaging; + +namespace ExampleApp; + +public sealed class SubscribeAsyncOptionsExample +{ + public SubscribeAsyncOptions Configure() + { + return new SubscribeAsyncOptions { ThrowIfCancellationWasRequested = true }; + } +} +``` diff --git a/.docfx/api/types/Savvyio.MetadataDictionary.md b/.docfx/api/types/Savvyio.MetadataDictionary.md new file mode 100644 index 0000000..2eb51a5 --- /dev/null +++ b/.docfx/api/types/Savvyio.MetadataDictionary.md @@ -0,0 +1,21 @@ +--- +uid: Savvyio.MetadataDictionary +example: +- *content +--- +This example shows how to store reserved metadata keys and custom values in a case-insensitive metadata dictionary. + +```csharp +using Savvyio; + +namespace ExampleApp; + +public sealed class MetadataDictionaryExample +{ + public bool HasCorrelationId() + { + var metadata = new MetadataDictionary { ["correlationid"] = "corr-42", ["tenant"] = "north-europe" }; + return metadata.ContainsKey(MetadataDictionary.CorrelationId) && metadata["tenant"].ToString() == "north-europe"; + } +} +``` diff --git a/.docfx/api/types/Savvyio.MetadataExtensions.md b/.docfx/api/types/Savvyio.MetadataExtensions.md new file mode 100644 index 0000000..37cf690 --- /dev/null +++ b/.docfx/api/types/Savvyio.MetadataExtensions.md @@ -0,0 +1,37 @@ +--- +uid: Savvyio.MetadataExtensions +example: +- *content +--- +This example shows how a request can collect reserved metadata, merge values from an upstream request, and read the normalized values back again. The workflow exercises the metadata helpers that are typically used by command, event, and integration-message pipelines. + +```csharp +using System; +using Savvyio; + +namespace ExampleApp; + +public sealed class MetadataExtensionsExample +{ + public (string CorrelationId, string MemberType, string Tenant) Enrich() + { + var parent = new CreateOrderCommand("ORD-41").SetCorrelationId("corr-41").SaveMetadata("tenant", "eu-west"); + var command = new CreateOrderCommand("ORD-42") + .SetCorrelationId("corr-42") + .SetCausationId("checkout") + .SetRequestId("req-42") + .SetEventId("evt-42") + .SetMemberType(typeof(CreateOrderCommand)) + .SetTimestamp(new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc)) + .MergeMetadata(parent); + + var correlationId = command.GetCorrelationId(); + var causationId = command.GetCausationId(); + var requestId = command.GetRequestId(); + var memberType = command.GetMemberType(); + return (correlationId, memberType, (string)command.Metadata["tenant"]); + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.MetadataFactory.md b/.docfx/api/types/Savvyio.MetadataFactory.md new file mode 100644 index 0000000..9db0a48 --- /dev/null +++ b/.docfx/api/types/Savvyio.MetadataFactory.md @@ -0,0 +1,24 @@ +--- +uid: Savvyio.MetadataFactory +example: +- *content +--- +This example shows how to persist a custom metadata value on a request and retrieve it later in the pipeline. + +```csharp +using Savvyio; + +namespace ExampleApp; + +public sealed class MetadataFactoryExample +{ + public string GetTenant() + { + var command = new CreateOrderCommand("ORD-42"); + MetadataFactory.Set(command, "tenant", "eu-west"); + return (string)MetadataFactory.Get(command, "tenant"); + } +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` diff --git a/.docfx/api/types/Savvyio.Queries.QueryDispatcher.md b/.docfx/api/types/Savvyio.Queries.QueryDispatcher.md new file mode 100644 index 0000000..fd12a16 --- /dev/null +++ b/.docfx/api/types/Savvyio.Queries.QueryDispatcher.md @@ -0,0 +1,32 @@ +--- +uid: Savvyio.Queries.QueryDispatcher +example: +- *content +--- +This example shows how to route a request-reply query through the built-in query dispatcher. The handler returns a computed order total, which gives the caller an observable outcome from the dispatcher workflow. + +```csharp +using Savvyio; +using Savvyio.Dispatchers; +using Savvyio.Handlers; +using Savvyio.Queries; + +namespace ExampleApp; + +public sealed class QueryDispatcherExample +{ + public decimal Query() + { + var handler = new GetOrderTotalHandler(); + var dispatcher = new QueryDispatcher(new ServiceLocator(serviceType => serviceType == typeof(IQueryHandler) ? new object[] { handler } : [])); + return dispatcher.Query(new GetOrderTotalQuery("ORD-42")); + } +} + +public sealed class GetOrderTotalHandler : IQueryHandler +{ + public IRequestReplyActivator Delegates => HandlerFactory.CreateRequestReply(registry => registry.Register(query => query.OrderId.Length * 10m)); +} + +public sealed record GetOrderTotalQuery(string OrderId) : Request, IQuery; +``` diff --git a/.docfx/api/types/Savvyio.Queries.SavvyioOptionsExtensions.md b/.docfx/api/types/Savvyio.Queries.SavvyioOptionsExtensions.md new file mode 100644 index 0000000..b6e46ac --- /dev/null +++ b/.docfx/api/types/Savvyio.Queries.SavvyioOptionsExtensions.md @@ -0,0 +1,30 @@ +--- +uid: Savvyio.Queries.SavvyioOptionsExtensions +example: +- *content +--- +This example shows how to configure SavvyioOptions for a query-driven application service. The options register both the query handler implementation and the default query dispatcher so request-reply operations can resolve through the same configuration object. + +```csharp +using Savvyio; +using Savvyio.Handlers; +using Savvyio.Queries; + +namespace ExampleApp; + +public sealed class QueryOptionsExtensionsExample +{ + public int Configure() + { + var options = new SavvyioOptions().AddQueryHandler().AddQueryDispatcher(); + return options.HandlerImplementationTypes.Count + options.DispatcherImplementationTypes.Count; + } +} + +public sealed class GetOrderTotalHandler : IQueryHandler +{ + public IRequestReplyActivator Delegates => HandlerFactory.CreateRequestReply(registry => registry.Register(_ => 42m)); +} + +public sealed record GetOrderTotalQuery(string OrderId) : Request, IQuery; +``` diff --git a/.docfx/api/types/Savvyio.Reflection.AssemblyContext.md b/.docfx/api/types/Savvyio.Reflection.AssemblyContext.md new file mode 100644 index 0000000..247ebb4 --- /dev/null +++ b/.docfx/api/types/Savvyio.Reflection.AssemblyContext.md @@ -0,0 +1,25 @@ +--- +uid: Savvyio.Reflection.AssemblyContext +example: +- *content +--- +This example shows how to narrow assembly discovery to application assemblies before scanning for Savvy I/O handlers. + +```csharp +using System; +using System.Linq; +using Savvyio.Reflection; + +namespace ExampleApp; + +public sealed class AssemblyContextExample +{ + public string[] GetApplicationAssemblies() + { + AssemblyContext.AssemblyFilterCallback = assembly => assembly.GetName().Name?.StartsWith("ExampleApp", StringComparison.Ordinal) == true; + AssemblyContext.AssemblyDependenciesFilterCallback = assemblyName => assemblyName.Name?.StartsWith("ExampleApp", StringComparison.Ordinal) == true; + AssemblyContext.AssemblyDependenciesCallback = assembly => new[] { assembly }; + return AssemblyContext.CurrentDomainAssemblies.Select(a => a.GetName().Name ?? string.Empty).ToArray(); + } +} +``` diff --git a/.docfx/api/types/Savvyio.SavvyioOptions.md b/.docfx/api/types/Savvyio.SavvyioOptions.md new file mode 100644 index 0000000..547ea15 --- /dev/null +++ b/.docfx/api/types/Savvyio.SavvyioOptions.md @@ -0,0 +1,41 @@ +--- +uid: Savvyio.SavvyioOptions +example: +- *content +--- +Configure `SavvyioOptions` to enable handler and dispatcher discovery, add specific handler and dispatcher registrations, and inspect the resulting counts. + +```csharp +using System; +using Savvyio; +using Savvyio.Dispatchers; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class SavvyioOptionsExample +{ + public void Configure() + { + var options = new SavvyioOptions() + .EnableHandlerDiscovery() + .EnableDispatcherDiscovery() + .EnableHandlerServicesDescriptor() + .AddHandler, IRequest, CreateOrderHandler>() + .AddDispatcher(); + + Console.WriteLine($"Handlers: {options.HandlerImplementationTypes.Count}, Dispatchers: {options.DispatcherImplementationTypes.Count}"); + Console.WriteLine($"HandlerDiscovery: {options.AllowHandlerDiscovery}, DispatcherDiscovery: {options.AllowDispatcherDiscovery}"); + } +} + +public sealed class CreateOrderHandler : IFireForgetHandler +{ + public IFireForgetActivator Delegates => + HandlerFactory.CreateFireForget(r => r.Register(_ => { })); +} + +public sealed record CreateOrderCommand(string OrderId) : Request; +``` + + diff --git a/.docfx/api/types/Savvyio.SavvyioOptionsExtensions.md b/.docfx/api/types/Savvyio.SavvyioOptionsExtensions.md new file mode 100644 index 0000000..11755db --- /dev/null +++ b/.docfx/api/types/Savvyio.SavvyioOptionsExtensions.md @@ -0,0 +1,28 @@ +--- +uid: Savvyio.SavvyioOptionsExtensions +example: +- *content +--- +This example shows how to scan an assembly for handler and dispatcher contracts so Savvy I/O can register them automatically. + +```csharp +using Savvyio; +using Savvyio.Commands; +using Savvyio.Dispatchers; +using Savvyio.Handlers; + +namespace ExampleApp; + +public sealed class SavvyioOptionsExtensionsExample +{ + public SavvyioOptions Configure() => new SavvyioOptions().AddHandlers(typeof(CreateOrderHandler).Assembly).AddDispatchers(typeof(CheckoutDispatcher).Assembly); +} + +public interface ICheckoutDispatcher : IDispatcher { } +public sealed class CheckoutDispatcher : ICheckoutDispatcher { } +public sealed class CreateOrderHandler : ICommandHandler +{ + public IFireForgetActivator Delegates => HandlerFactory.CreateFireForget(registry => registry.Register(_ => { })); +} +public sealed record CreateOrderCommand(string OrderId) : Request, ICommand; +``` diff --git a/.docfx/api/types/Savvyio.TaskExtensions.md b/.docfx/api/types/Savvyio.TaskExtensions.md new file mode 100644 index 0000000..67e1a3b --- /dev/null +++ b/.docfx/api/types/Savvyio.TaskExtensions.md @@ -0,0 +1,25 @@ +--- +uid: Savvyio.TaskExtensions +example: +- *content +--- +This example shows how to await a task that returns a sequence and keep only the single matching result. + +```csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Savvyio; + +namespace ExampleApp; + +public sealed class TaskExtensionsExample +{ + public Task LoadAsync() + { + Task> query = Task.FromResult>(new[] { new OrderProjection("ORD-42") }); + return query.SingleOrDefaultAsync(); + } +} + +public sealed record OrderProjection(string OrderId); +``` From e76150acc2f1ba4e671c2d2decc3df3506513c5f Mon Sep 17 00:00:00 2001 From: gimlichael Date: Wed, 1 Jul 2026 23:15:18 +0200 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=93=9D=20update=20changelog=20for=205?= =?UTF-8?q?.0.9=20with=20documentation=20and=20dependency=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9a93c9..f3413a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder. -## [5.0.9] - 2026-06-30 +## [5.0.9] - 2026-07-01 -This is a service update that focuses on package dependencies. +This is a patch release focused on API documentation expansion with comprehensive namespace and type examples, DocFX infrastructure restructuring, clear documentation maintenance standards for agents, and multiple NuGet package updates to latest stable versions. + +### Added + +- Comprehensive API namespace documentation with usage examples, entry points, and Extension Members tables guiding developers to key factories and DI registration methods, +- Per-type DocFX overwrite pages for all public API types (enums, structs, records, classes, static extensions) with realistic, copy/paste-ready code examples sourced from unit and functional tests, +- Detailed DocFX documentation maintenance standards in AGENTS.md covering namespace/type page structure, example requirements, availability documentation, verification workflows, and quality gates. + +### Changed + +- DocFX build system restructured to separate namespace overwrite files (`api/namespaces/**/*.md`) and type overwrite files (`api/types/**/*.md`) in distinct build.overwrite sections, preventing Markdown files from being processed as conceptual content, +- NGINX version updated to 1.31.2 for docs publishing container, +- AWSSDK.SQS and AWSSDK.SimpleNotificationService upgraded from 4.0.3.1 to 4.0.100, +- Azure.Storage.Queues upgraded from 12.27.0 to 12.27.1, +- Microsoft.Data.Sqlite upgraded from 10.0.8 to 10.0.9, +- Microsoft.Extensions.Logging.Abstractions upgraded from 10.0.8 to 10.0.9, +- Microsoft.NET.Test.Sdk upgraded from 18.6.0 to 18.7.0, +- NATS.Client packages (Core, JetStream, Simplified) upgraded from 2.8.1 to 2.8.2, +- EntityFrameworkCore packages for net9 upgraded from 9.0.16 to 9.0.17, +- EntityFrameworkCore packages for net10 upgraded from 10.0.8 to 10.0.9. + +### Fixed + +- CI deploy job condition now properly handles skipped optional jobs (such as disabled macOS matrix runs) by using `always()` guard and explicitly checking success status of all upstream jobs to ensure deployment only runs when all required jobs succeed. ## [5.0.8] - 2026-06-06 @@ -1037,7 +1060,9 @@ Noticeable highlights: - QueryHandler class in the Savvyio.Queries namespace that defines a generic and consistent way of handling Query objects that implements the IQuery interface - SavvyioOptionsExtensions class in the Savvyio.Queries namespace that consist of extension methods for the SavvyioOptions class: AddQueryHandler, AddQueryDispatcher -[Unreleased]: https://github.com/codebeltnet/savvyio/compare/v5.0.7...HEAD +[Unreleased]: https://github.com/codebeltnet/savvyio/compare/v5.0.9...HEAD +[5.0.9]: https://github.com/codebeltnet/savvyio/compare/v5.0.8...v5.0.9 +[5.0.8]: https://github.com/codebeltnet/savvyio/compare/v5.0.7...v5.0.8 [5.0.7]: https://github.com/codebeltnet/savvyio/compare/v5.0.6...v5.0.7 [5.0.6]: https://github.com/codebeltnet/savvyio/compare/v5.0.5...v5.0.6 [5.0.5]: https://github.com/codebeltnet/savvyio/compare/v5.0.4...v5.0.5