Skip to content

Commit

Permalink
Adjust docs (#204)
Browse files Browse the repository at this point in the history
Adjust outbox and asyncapi plugin docs

Signed-off-by: Tomasz Maruszak <[email protected]>
  • Loading branch information
zarusz authored Dec 23, 2023
1 parent eede46b commit fdf4552
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 75 deletions.
97 changes: 97 additions & 0 deletions build/md-preprocessor.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
function ExtractFileNamedFragment($file, $fragmentName) {
$fragmentLines = New-Object System.Collections.Generic.List[object]
$fileLines = Get-Content -Path $file
$markerName = "doc:fragment:$fragmentName";
$markerStarted = $false
foreach ($fileLine in $fileLines) {
if ($fileLine.Contains($markerName)) {
$markerStarted = !$markerStarted;
}
else {
if ($markerStarted) {
$fragmentLines.Add($fileLine)
}
}
}

return $fragmentLines
}

function CountLeadingTabsOrSpaces($lines) {
$count = -1;
foreach ($line in $lines) {
for ($i = 0; $i -lt $line.Length; $i++) {
$c = $line[$i]
if (($c -ne "`.") -and ($c -ne " ")) {
if (($count -eq -1) -or ($i -lt $count)) {
$count = $i
}
break;
}
}
}
if ($count -eq -1) {
return 0;
}
return $count
}

function IdentFragment($lines) {
$i = CountLeadingTabsOrSpaces $lines
if ($i -gt 0) {
return $lines | ForEach-Object { if ($_.Length -gt $i) { $_.Substring($i) } else { $_ } }
}
return $lines
}

function ProcessMarkdownFileIncludeLine($file, $line, $transformedLines) {
$found = $line -match '.*\@\[\:cs\]\((.+)\,(.+)\).*'
if (!$found) {
throw "Cannot parse fragment include in file $file on line $line"
}

$fragmentFilePath = $matches[1]
$fragmentName = $matches[2]

$fileFolder = Split-Path -parent $file
Write-Host "$fileFolder of $file"

$fragmentLines = ExtractFileNamedFragment "$fileFolder/$fragmentFilePath" $fragmentName
if ($fragmentLines.Count -gt 0) {

$fragmentLines = IdentFragment $fragmentLines

$transformedLines.Add("``````cs")
$transformedLines.AddRange($fragmentLines);
$transformedLines.Add("``````")
}
}

function ProcessMarkdown($file, $targetFile) {
Write-Host "Processing $file into $targetFile ..."

$templateLines = Get-Content -Path $file

# Write-Host $templateLines[0]
$transformedLines = New-Object System.Collections.Generic.List[object]
foreach ($line in $templateLines) {
if ($line.Contains("@[:cs](")) {
ProcessMarkdownFileIncludeLine $file $line $transformedLines
}
else {
$transformedLines.Add($line);
}
}

Set-Content -Path $targetFile -Value ($transformedLines -join "`n") -NoNewline
}

# Find files ending in .t.md
$files = Get-ChildItem -Path '../' -Filter '*.t.md' -Recurse -ErrorAction SilentlyContinue | Select-Object -Property FullName -ExpandProperty FullName

# For each found file run processing
foreach ($file in $files) {
$targetFile = $file.replace('.t.md', '.md');

ProcessMarkdown $file $targetFile
}
11 changes: 6 additions & 5 deletions docs/plugin_asyncapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Please read the [Introduction](intro.md) before reading this provider documentat
- [Configuration](#configuration)
- [Sample AsyncAPI document](#sample-asyncapi-document)
- [Documentation](#documentation)

## Introduction

The [`SlimMessageBus.Host.AsyncApi`](https://www.nuget.org/packages/SlimMessageBus.Host.AsyncApi) introduces a document generator for [Saunter](https://github.com/tehmantra/saunter), which enables to generate an [AsyncAPI specification](https://www.asyncapi.com/) from SlimMessageBus.
Expand All @@ -17,7 +17,7 @@ On the SMB setup, use the `mbb.AddAsyncApiDocumentGenerator()` to add the `IDocu

```cs
services.AddSlimMessageBus(mbb =>
{
{
// Register the IDocumentGenerator for Saunter library
mbb.AddAsyncApiDocumentGenerator();
});
Expand All @@ -26,7 +26,8 @@ services.AddSlimMessageBus(mbb =>
Then register the Saunter services (in that order):

```cs
services.AddAsyncApiSchemaGeneration(options =>
// Add Saunter to the application services.
builder.Services.AddAsyncApiSchemaGeneration(options =>
{
options.AsyncApi = new AsyncApiDocument
{
Expand All @@ -45,7 +46,7 @@ services.AddAsyncApiSchemaGeneration(options =>
Saunter also requires to add the following endpoints (consult the Saunter docs):

```cs
// Register AsyncAPI docs via Sauter
// Register AsyncAPI docs via Sauter
app.MapAsyncApiDocuments();
app.MapAsyncApiUi();
```
Expand Down Expand Up @@ -79,7 +80,7 @@ public class CustomerCreatedEventConsumer : IConsumer<CustomerCreatedEvent>
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public Task OnHandle(CustomerCreatedEvent message) { }
public Task OnHandle(CustomerCreatedEvent message) { }
}
```

Expand Down
77 changes: 77 additions & 0 deletions docs/plugin_asyncapi.t.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# AsyncAPI Specification Plugin for SlimMessageBus <!-- omit in toc -->

Please read the [Introduction](intro.md) before reading this provider documentation.

- [Introduction](#introduction)
- [Configuration](#configuration)
- [Sample AsyncAPI document](#sample-asyncapi-document)
- [Documentation](#documentation)

## Introduction

The [`SlimMessageBus.Host.AsyncApi`](https://www.nuget.org/packages/SlimMessageBus.Host.AsyncApi) introduces a document generator for [Saunter](https://github.com/tehmantra/saunter), which enables to generate an [AsyncAPI specification](https://www.asyncapi.com/) from SlimMessageBus.

## Configuration

On the SMB setup, use the `mbb.AddAsyncApiDocumentGenerator()` to add the `IDocumentGenerator` for Saunter library:

```cs
services.AddSlimMessageBus(mbb =>
{
// Register the IDocumentGenerator for Saunter library
mbb.AddAsyncApiDocumentGenerator();
});
```

Then register the Saunter services (in that order):

@[:cs](../src/Samples/Sample.AsyncApi.Service/Program.cs,ExampleStartup2)

Saunter also requires to add the following endpoints (consult the Saunter docs):

```cs
// Register AsyncAPI docs via Sauter
app.MapAsyncApiDocuments();
app.MapAsyncApiUi();
```

See the [Sample.AsyncApi.Service](../src/Samples/Sample.AsyncApi.Service/) for a complete setup.

## Sample AsyncAPI document

When running the mentioned sample, the AsyncAPI document can be obtained via the following link:
https://localhost:7252/asyncapi/asyncapi.json

The generated document for the sample is available [here](../src/Samples/Sample.AsyncApi.Service/asyncapi.json) as well.

## Documentation

The comment and remarks are being taken from the code (for the consumer method and message type):

```cs
/// <summary>
/// Event when a customer is created within the domain.
/// </summary>
/// <param name="Id"></param>
/// <param name="Firstname"></param>
/// <param name="Lastname"></param>
public record CustomerCreatedEvent(Guid Id, string Firstname, string Lastname) : CustomerEvent(Id);

public class CustomerCreatedEventConsumer : IConsumer<CustomerCreatedEvent>
{
/// <summary>
/// Upon the <see cref="CustomerCreatedEvent"/> will store it with the database.
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public Task OnHandle(CustomerCreatedEvent message) { }
}
```

Ensure that your project has the `GenerateDocumentationFile` enabled (more [here](https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-7.0&tabs=visual-studio#xml-comments)):

```xml
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
```
54 changes: 29 additions & 25 deletions docs/plugin_outbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,72 +11,76 @@ Please read the [Introduction](intro.md) before reading this provider documentat
- [UseTransactionScope](#usetransactionscope)
- [UseSqlTransaction](#usesqltransaction)
- [How it works](#how-it-works)

## Introduction

The [`Host.Outbox`](https://www.nuget.org/packages/SlimMessageBus.Host.Outbox) introduces [Transactional Outbox](https://microservices.io/patterns/data/transactional-outbox.html) pattern to the SlimMessageBus. It comes in two flavors:

- [`Host.Outbox.Sql`](https://www.nuget.org/packages/SlimMessageBus.Host.Outbox.Sql) as integration with the System.Data.Sql client
- [`Host.Outbox.DbContext`](https://www.nuget.org/packages/SlimMessageBus.Host.Outbox.DbContext) as integration with Entity Framework Core

Outbox plugin can work with any transport provider.
Outbox plugin can work with in combination with any transport provider.

## Configuration

### Entity Framework

> Required: [`SlimMessageBus.Host.Outbox.DbContext`](https://www.nuget.org/packages/SlimMessageBus.Host.Outbox.DbContext)
Consider the following example:
Consider the following example (from [Samples](../src/Samples/Sample.OutboxWebApi/Program.cs)):

- `services.AddOutboxUsingDbContext<CustomerContext>(...)` is used to add the [Outbox.DbContext](https://www.nuget.org/packages/SlimMessageBus.Host.Outbox.DbContext) plugin to the container.
- `CustomerContext` is the application specific Entity Framework `DbContext`.
- `CustomerCreatedEvent` is produced on the `AzureSB` child bus, the bus will deliver these events via outbox - see `.UseOutbox()`
- `CreateCustomerCommand` is consumed on the `Memory` child bus, each command is wrapped in an SQL transaction - see `UseSqlTransaction()`

Startup setup:

```cs
// Configure the Bus
builder.Services.AddSlimMessageBus(mbb =>
{
mbb
.WithProviderHybrid()
.AddChildBus("Memory", mbb =>
{
mbb.WithProviderMemory()
.AutoDeclareFrom(Assembly.GetExecutingAssembly(), consumerTypeFilter: t => t.Name.Contains("Command"))
.UseSqlTransaction(); // Consumers/Handlers will be wrapped in a SqlTransaction
//.UseTransactionScope(); // Consumers/Handlers will be wrapped in a TransactionScope
.AutoDeclareFrom(Assembly.GetExecutingAssembly(), consumerTypeFilter: t => t.Name.Contains("Command"))
//.UseTransactionScope(); // Consumers/Handlers will be wrapped in a TransactionScope
.UseSqlTransaction(); // Consumers/Handlers will be wrapped in a SqlTransaction
})
.AddChildBus("AzureSB", mbb =>
{
var serviceBusConnectionString = ""; // some connection string
mbb.WithProviderServiceBus(new ServiceBusMessageBusSettings(serviceBusConnectionString))
.Produce<CustomerCreatedEvent>(x =>
{
x.DefaultTopic("samples.outbox/customer-events");
// OR if you want just this producer to sent via outbox
// x.UseOutbox();
})
.UseOutbox(); // All outgoing messages from this bus will go out via an outbox
mbb.WithProviderServiceBus(cfg => cfg.ConnectionString = Secrets.Service.PopulateSecrets(configuration["Azure:ServiceBus"]))
.Produce<CustomerCreatedEvent>(x =>
{
x.DefaultTopic("samples.outbox/customer-events");
// OR if you want just this producer to sent via outbox
// x.UseOutbox();
})
.UseOutbox(); // All outgoing messages from this bus will go out via an outbox
})
.AddJsonMessageSerializer()
.AddServicesFromAssembly(Assembly.GetExecutingAssembly())
// Register the Outbox plugin, and let it use DbConnection and manage SqlTransaction using the CustomerContext DbContext
.AddJsonSerializer()
.AddAspNet()
.AddOutboxUsingDbContext<CustomerContext>(opts =>
{
opts.PollBatchSize = 100;
opts.MessageCleanup.Interval = TimeSpan.FromSeconds(10);
opts.MessageCleanup.Age = TimeSpan.FromMinutes(1);
//opts.TransactionIsolationLevel = System.Data.IsolationLevel.RepeatableRead;
//opts.Dialect = SqlDialect.SqlServer;
});
});
```

// Command handler for CreateCustomerCommand
Command handler:

```cs
public record CreateCustomerCommandHandler(IMessageBus Bus, CustomerContext CustomerContext) : IRequestHandler<CreateCustomerCommand, Guid>
{
public async Task<Guid> OnHandle(CreateCustomerCommand request)
{
// Note: This handler will be already wrapped in a transaction: see Program.cs and .UseTransactionScope() / .UseSqlTransaction()
var customer = new Customer(request.Firstname, request.Lastname);
await CustomerContext.Customers.AddAsync(customer);
await CustomerContext.SaveChangesAsync();
Expand Down Expand Up @@ -105,7 +109,7 @@ builder.Services.AddSlimMessageBus(mbb =>
mbb.AddOutboxUsingSql(opts => { opts.PollBatchSize = 100; });
});

// Register in the the container the SqlConnection
// SMB requires the SqlConnection to be registered in the container
builder.Services.AddTransient(svp =>
{
var configuration = svp.GetRequiredService<IConfiguration>();
Expand Down Expand Up @@ -135,7 +139,7 @@ When applied on the (child) bus level then all consumers (or handlers) will inhe

> Required: [`SlimMessageBus.Host.Outbox.Sql`](https://www.nuget.org/packages/SlimMessageBus.Host.Outbox.Sql) or [`SlimMessageBus.Host.Outbox.DbContext`](https://www.nuget.org/packages/SlimMessageBus.Host.Outbox.DbContext)
`.UseSqlTransaction()` can be used on consumers (or handlers) declaration to force the consumer to start a `SqlTransaction` prior the message `OnHandle` and to complete that transaction after it. Any exception raised by the consumer would cause the transaction to be rolled back.
`.UseSqlTransaction()` can be used on consumers (or handlers) declaration to force the consumer to start a `SqlTransaction` prior the message `OnHandle` and to complete that transaction after it. Any exception raised by the consumer would cause the transaction to be rolled back.

When applied on the (child) bus level then all consumers (or handlers) will inherit that option.

Expand All @@ -148,7 +152,7 @@ When applied on the (child) bus level then all consumers (or handlers) will inhe
- Upon bus start the `Outbox` SQL table is created (if does not exist). The name of the table can be adjusted via settings.

- When a message is sent via a bus or producer maked with `.UseOutbox()` then such message will be inserted into the `Outbox` table.
It is important that message publish happens in the context of an transaction to ensure consistency.
It is important that message publish happens in the context of an transaction to ensure consistency.

- When the message publication happens in the context of a consumer (or handler) of another message, the `.UseTransactionScope()`, `.UseSqlTransaction()` can be used to start a transaction.

Expand All @@ -162,4 +166,4 @@ It is important that message publish happens in the context of an transaction to

- Once a message is picked from outbox and succesfully delivered then it is marked as sent in the outbox table.

- At configured intervals and after a certain time span the sent messages are removed from the outbox table.
- At configured intervals and after a certain time span the sent messages are removed from the outbox table.
Loading

0 comments on commit fdf4552

Please sign in to comment.