Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions src/Azure/src/Eventuous.Azure.Storage.Blobs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,29 @@ Note, this means the idempotency is weaker as only the last message ID is checke

### Custom blob naming

By default, blob names are generated using `GetBlobName(string id)` which creates names in the format `{id}/{T}.json`, where `id` defaults to the stream ID from `context.Stream.GetId()`.
By default, blob names are generated using `GetBlobName(string id)` which creates names in the format `{id}/{StateType}.json`, where `id` defaults to the stream ID from `context.Stream.GetId()`.

You can customize blob naming in two ways:

**1. Override the virtual methods globally for all events:**

```csharp
protected override string GetBlobName(string id, IMessageConsumeContext context) {
// Use stream name and type in the path
var streamName = context.Stream.ToString();
return $"projections/{streamName}/{id}.json";
}
public class BookingProjection : BlobStorageProjector<BookingState> {
// ...

protected override string GetBlobName(string id) {
return $"{id}/{typeof(T).Name}.json";
protected override string GetBlobName(string id) => $"bookings/{id}.json";
}
```

When the blob name depends on the event, override the overload that takes the consume context instead:

```csharp
protected override string GetBlobName(string id, IMessageConsumeContext context)
=> $"projections/{context.Stream}/{id}.json";
```

The default implementation of the two-argument overload calls the one-argument overload, so overriding the two-argument version replaces the naming completely — a one-argument override is then never called.

**2. Override blob ID per event handler using `getBlobId`:**

```csharp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ BlobContainerClient GetContainer(string containerName) =>
async Task SetupExistingBlob<TState>(string containerName, string blobName, TState initialState) {
var blobClient = GetContainer(containerName).GetBlobClient(blobName);
var json = JsonSerializer.SerializeToUtf8Bytes(initialState);
await blobClient.UploadAsync(new MemoryStream(json), overwrite: true);
using var stream = new MemoryStream(json);
await blobClient.UploadAsync(stream, overwrite: true);
Comment on lines +40 to +41

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. uploadasync awaits missing nocontext() 📘 Rule violation ☼ Reliability

The newly modified await blobClient.UploadAsync(...) calls don't use .NoContext(), which breaks
the repo convention for avoiding captured synchronization context on I/O awaits. This can increase
deadlock risk and reduce scalability in async flows.
Agent Prompt
## Issue description
The modified `UploadAsync` awaits do not use `.NoContext()`.

## Issue Context
Compliance requires async I/O awaits to use `.NoContext()` (`ConfigureAwait(false)`) per repository convention.

## Fix Focus Areas
- src/Azure/test/Eventuous.Tests.Azure.Storage.Blobs/BlobStorageProjectorTests.cs[37-42]
- src/Azure/test/Eventuous.Tests.Azure.Storage.Blobs/BlobStorageProjectorTests.cs[71-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}

/// <summary>
Expand Down Expand Up @@ -71,7 +72,8 @@ Func<Task> OverwriteBlob(string containerName, string blobName) => async () => {
var modifiedState = new ConcurrentState { Value = 999 };
var modifiedJson = JsonSerializer.SerializeToUtf8Bytes(modifiedState);
var blobClient = GetContainer(containerName).GetBlobClient(blobName);
await blobClient.UploadAsync(new MemoryStream(modifiedJson), overwrite: true);
using var stream = new MemoryStream(modifiedJson);
await blobClient.UploadAsync(stream, overwrite: true);
};

// ========== SYNC STATE HANDLER TESTS ==========
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ public class ElasticSerializer(IElasticsearchSerializer builtIn, JsonSerializerO
readonly ITypeMapper _typeMapper = typeMapper ?? TypeMap.Instance;

public object Deserialize(Type type, Stream stream) {
var reader = new BinaryReader(stream);
var obj = JsonSerializer.Deserialize(reader.ReadBytes((int)stream.Length), type, _options);
// Read the stream directly: a BinaryReader here would either close the caller's stream when
// disposed, or leak its buffers when not, and it only added a full copy of the payload
var obj = JsonSerializer.Deserialize(stream, type, _options);
Comment on lines +14 to +16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. elasticserializer does sync stream i/o 📘 Rule violation ➹ Performance

ElasticSerializer.Deserialize and Serialize perform synchronous read/write operations against
Stream using JsonSerializer.Deserialize(...) and Utf8JsonWriter, despite async alternatives
existing. This violates the async-I/O compliance requirement and can cause thread blocking under
load.
Agent Prompt
## Issue description
The serializer performs synchronous stream I/O (`JsonSerializer.Deserialize(stream, ...)` and `Utf8JsonWriter`-based serialization), which can block threads.

## Issue Context
Compliance requires I/O to be asynchronous where async alternatives exist.

## Fix Focus Areas
- src/Experimental/src/Eventuous.ElasticSearch/Store/ElasticSerializer.cs[13-26]
- src/Experimental/src/Eventuous.ElasticSearch/Store/ElasticSerializer.cs[28-38]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


if (type != typeof(PersistedEvent)) return obj!;

Expand All @@ -31,7 +32,8 @@ public void Serialize<T>(T data, Stream stream, SerializationFormatting formatti
return;
}

var writer = new Utf8JsonWriter(stream);
// Disposing the writer returns its pooled buffers and flushes; it doesn't close the caller's stream
using var writer = new Utf8JsonWriter(stream);
JsonSerializer.Serialize(writer, data, _options);
}

Expand Down
Loading