Skip to content

Commit

Permalink
New [AlwaysPublishResponse] attribute usage for selective, explicit b…
Browse files Browse the repository at this point in the history
…ackward compatibility with Wolverine <3 behavior
  • Loading branch information
jeremydmiller committed Sep 4, 2024
1 parent 071e8fa commit 9680ef3
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 13 deletions.
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

0 comments on commit 9680ef3

Please sign in to comment.