Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New [AlwaysPublishResponse] attribute usage for selective, explicit b… #1029

Merged
merged 1 commit into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<ImplicitUsings>true</ImplicitUsings>
<Nullable>enable</Nullable>
<VersionPrefix>3.0.0</VersionPrefix>
<VersionSuffix>alpha-3</VersionSuffix>
<VersionSuffix>beta-1</VersionSuffix>
<RepositoryUrl>$(PackageProjectUrl)</RepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
Expand Down
5 changes: 5 additions & 0 deletions docs/guide/messaging/message-bus.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ Note that if you execute the `Numbers` message from above with `InvokeAsync<Resu
returned as the response and will not be published as a message. This was a breaking change in Wolverine 3.0. We think (hope)
that this will be less confusing.

You can explicitly override this behavior on a handler by handler basis with the `[AlwaysPublishResponse]` attribute
as shown below:

snippet: sample_using_AlwaysPublishResponse

## Sending or Publishing Messages

[Publish/Subscribe](https://docs.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber) is a messaging pattern where the senders of messages do not need to specifically know what the specific subscribers are for a given message. In this case, some kind of middleware or infrastructure is responsible for either allowing subscribers to express interest in what messages they need to receive or apply routing rules to send the published messages to the right places. Wolverine's messaging support was largely built to support the publish/subscribe messaging pattern.
Expand Down
7 changes: 6 additions & 1 deletion docs/guide/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,9 @@ Also for Wolverine.Http users, the `[Document]` attribute behavior in the Marten

The behavior of `IMessageBus.InvokeAsync<T>(message)` changed in 3.0 such that the `T` response **is not also published as a
message** at the same time when the initial message is sent with request/response semantics. Wolverine has gone back and forth
in this behavior in its life, but at this point, the Wolverine thinks that this is the least confusing behavioral rule.
in this behavior in its life, but at this point, the Wolverine thinks that this is the least confusing behavioral rule.

You can selectively override this behavior and tell Wolverine to publish the response as a message no matter what
by using the new 3.0 `[AlwaysPublishResponse]` attribute like this:

snippet: sample_using_AlwaysPublishResponse
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ public async Task map_post_with_request_response()
});

result.ReadAsJson<CustomResponse>().Name.ShouldBe("Alan Alda");

tracked.Sent.SingleMessage<CustomResponse>();
}

[Fact]
Expand All @@ -65,8 +63,6 @@ public async Task map_put_with_request_response()
});

result.ReadAsJson<CustomResponse>().Name.ShouldBe("FDR");

tracked.Sent.SingleMessage<CustomResponse>();
}

[Fact]
Expand All @@ -78,7 +74,5 @@ public async Task map_delete_with_request_response()
});

result.ReadAsJson<CustomResponse>().Name.ShouldBe("LBJ");

tracked.Sent.SingleMessage<CustomResponse>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Wolverine.Marten;
using Wolverine.Tracking;
using Shouldly;
using Wolverine.Attributes;

namespace MartenTests.Bugs;

Expand Down Expand Up @@ -35,8 +36,13 @@ public class CreateItemCommand
public string Name { get; set; } = string.Empty;
}

#region sample_using_AlwaysPublishResponse

public class CreateItemCommandHandler
{
// Using this attribute will force Wolverine to also publish the ItemCreated event even if
// this is called by IMessageBus.InvokeAsync<ItemCreated>()
[AlwaysPublishResponse]
public async Task<(ItemCreated, SecondItemCreated)> Handle(CreateItemCommand command, IDocumentSession session)
{
var item = new Item
Expand All @@ -51,6 +57,8 @@ public class CreateItemCommandHandler
}
}

#endregion

public record ItemCreated(Guid Id, string Name);

public record SecondItemCreated(Guid Id, string Name);
Expand Down
4 changes: 0 additions & 4 deletions src/Persistence/MartenTests/aggregate_handler_workflow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ public async Task events_then_response_invoke_with_return()
response.BCount.ShouldBe(1);
response.CCount.ShouldBe(1);

tracked.Sent.SingleMessage<Response>().ACount.ShouldBe(1);

await OnAggregate(a =>
{
a.ACount.ShouldBe(1);
Expand Down Expand Up @@ -131,8 +129,6 @@ [Fact] public async Task response_then_events_invoke_with_return()
response.BCount.ShouldBe(1);
response.CCount.ShouldBe(2);

tracked.Sent.SingleMessage<Response>().ACount.ShouldBe(2);

await OnAggregate(a =>
{
a.ACount.ShouldBe(2);
Expand Down
37 changes: 37 additions & 0 deletions src/Wolverine/Attributes/AlwaysPublishResponseAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using Wolverine.Runtime.Handlers;

namespace Wolverine.Attributes;

/// <summary>
/// Decorate any handler method or class if you always want any response
/// (the "T" in IMessageBus.InvokeAsync<T>()) to be *also* published as a
/// message in addition to being the response object
/// </summary>
public class AlwaysPublishResponseAttribute : ModifyHandlerChainAttribute
{
public override void Modify(HandlerChain chain, GenerationRules rules)
{
chain.Middleware.Add(new AlwaysPublishResponseFrame());
}
}

internal class AlwaysPublishResponseFrame : SyncFrame
{
private Variable _envelope;

public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
writer.WriteComment("Always publish the response as a message due to the [AlwaysPublishResponse] usage");
writer.WriteLine($"{_envelope.Usage}.{nameof(Envelope.DoNotCascadeResponse)} = false;");
Next?.GenerateCode(method, writer);
}

public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
{
_envelope = chain.FindVariable(typeof(Envelope));
yield return _envelope;
}
}
2 changes: 1 addition & 1 deletion src/Wolverine/Envelope.Internals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal Envelope(object message, IMessageSerializer writer)
/// message as a cascading message. Originally added for the
/// Http transport request/reply
/// </summary>
internal bool DoNotCascadeResponse { get; set; }
public bool DoNotCascadeResponse { get; set; }

/// <summary>
/// Status according to the message persistence
Expand Down
Loading