Skip to content

Commit 9680ef3

Browse files
committed
New [AlwaysPublishResponse] attribute usage for selective, explicit backward compatibility with Wolverine <3 behavior
1 parent 071e8fa commit 9680ef3

8 files changed

+58
-13
lines changed

Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<ImplicitUsings>true</ImplicitUsings>
1313
<Nullable>enable</Nullable>
1414
<VersionPrefix>3.0.0</VersionPrefix>
15-
<VersionSuffix>alpha-3</VersionSuffix>
15+
<VersionSuffix>beta-1</VersionSuffix>
1616
<RepositoryUrl>$(PackageProjectUrl)</RepositoryUrl>
1717
<PublishRepositoryUrl>true</PublishRepositoryUrl>
1818
<EmbedUntrackedSources>true</EmbedUntrackedSources>

docs/guide/messaging/message-bus.md

+5
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ Note that if you execute the `Numbers` message from above with `InvokeAsync<Resu
137137
returned as the response and will not be published as a message. This was a breaking change in Wolverine 3.0. We think (hope)
138138
that this will be less confusing.
139139

140+
You can explicitly override this behavior on a handler by handler basis with the `[AlwaysPublishResponse]` attribute
141+
as shown below:
142+
143+
snippet: sample_using_AlwaysPublishResponse
144+
140145
## Sending or Publishing Messages
141146

142147
[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.

docs/guide/migration.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,9 @@ Also for Wolverine.Http users, the `[Document]` attribute behavior in the Marten
5050

5151
The behavior of `IMessageBus.InvokeAsync<T>(message)` changed in 3.0 such that the `T` response **is not also published as a
5252
message** at the same time when the initial message is sent with request/response semantics. Wolverine has gone back and forth
53-
in this behavior in its life, but at this point, the Wolverine thinks that this is the least confusing behavioral rule.
53+
in this behavior in its life, but at this point, the Wolverine thinks that this is the least confusing behavioral rule.
54+
55+
You can selectively override this behavior and tell Wolverine to publish the response as a message no matter what
56+
by using the new 3.0 `[AlwaysPublishResponse]` attribute like this:
57+
58+
snippet: sample_using_AlwaysPublishResponse

src/Http/Wolverine.Http.Tests/mapping_routes_to_wolverine_handlers.cs

-6
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ public async Task map_post_with_request_response()
5252
});
5353

5454
result.ReadAsJson<CustomResponse>().Name.ShouldBe("Alan Alda");
55-
56-
tracked.Sent.SingleMessage<CustomResponse>();
5755
}
5856

5957
[Fact]
@@ -65,8 +63,6 @@ public async Task map_put_with_request_response()
6563
});
6664

6765
result.ReadAsJson<CustomResponse>().Name.ShouldBe("FDR");
68-
69-
tracked.Sent.SingleMessage<CustomResponse>();
7066
}
7167

7268
[Fact]
@@ -78,7 +74,5 @@ public async Task map_delete_with_request_response()
7874
});
7975

8076
result.ReadAsJson<CustomResponse>().Name.ShouldBe("LBJ");
81-
82-
tracked.Sent.SingleMessage<CustomResponse>();
8377
}
8478
}

src/Persistence/MartenTests/Bugs/Bug_305_invoke_async_with_return_not_publishing_with_tuple_return_value.cs

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Wolverine.Marten;
66
using Wolverine.Tracking;
77
using Shouldly;
8+
using Wolverine.Attributes;
89

910
namespace MartenTests.Bugs;
1011

@@ -35,8 +36,13 @@ public class CreateItemCommand
3536
public string Name { get; set; } = string.Empty;
3637
}
3738

39+
#region sample_using_AlwaysPublishResponse
40+
3841
public class CreateItemCommandHandler
3942
{
43+
// Using this attribute will force Wolverine to also publish the ItemCreated event even if
44+
// this is called by IMessageBus.InvokeAsync<ItemCreated>()
45+
[AlwaysPublishResponse]
4046
public async Task<(ItemCreated, SecondItemCreated)> Handle(CreateItemCommand command, IDocumentSession session)
4147
{
4248
var item = new Item
@@ -51,6 +57,8 @@ public class CreateItemCommandHandler
5157
}
5258
}
5359

60+
#endregion
61+
5462
public record ItemCreated(Guid Id, string Name);
5563

5664
public record SecondItemCreated(Guid Id, string Name);

src/Persistence/MartenTests/aggregate_handler_workflow.cs

-4
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,6 @@ public async Task events_then_response_invoke_with_return()
9696
response.BCount.ShouldBe(1);
9797
response.CCount.ShouldBe(1);
9898

99-
tracked.Sent.SingleMessage<Response>().ACount.ShouldBe(1);
100-
10199
await OnAggregate(a =>
102100
{
103101
a.ACount.ShouldBe(1);
@@ -131,8 +129,6 @@ [Fact] public async Task response_then_events_invoke_with_return()
131129
response.BCount.ShouldBe(1);
132130
response.CCount.ShouldBe(2);
133131

134-
tracked.Sent.SingleMessage<Response>().ACount.ShouldBe(2);
135-
136132
await OnAggregate(a =>
137133
{
138134
a.ACount.ShouldBe(2);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using JasperFx.CodeGeneration;
2+
using JasperFx.CodeGeneration.Frames;
3+
using JasperFx.CodeGeneration.Model;
4+
using Wolverine.Runtime.Handlers;
5+
6+
namespace Wolverine.Attributes;
7+
8+
/// <summary>
9+
/// Decorate any handler method or class if you always want any response
10+
/// (the "T" in IMessageBus.InvokeAsync<T>()) to be *also* published as a
11+
/// message in addition to being the response object
12+
/// </summary>
13+
public class AlwaysPublishResponseAttribute : ModifyHandlerChainAttribute
14+
{
15+
public override void Modify(HandlerChain chain, GenerationRules rules)
16+
{
17+
chain.Middleware.Add(new AlwaysPublishResponseFrame());
18+
}
19+
}
20+
21+
internal class AlwaysPublishResponseFrame : SyncFrame
22+
{
23+
private Variable _envelope;
24+
25+
public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
26+
{
27+
writer.WriteComment("Always publish the response as a message due to the [AlwaysPublishResponse] usage");
28+
writer.WriteLine($"{_envelope.Usage}.{nameof(Envelope.DoNotCascadeResponse)} = false;");
29+
Next?.GenerateCode(method, writer);
30+
}
31+
32+
public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
33+
{
34+
_envelope = chain.FindVariable(typeof(Envelope));
35+
yield return _envelope;
36+
}
37+
}

src/Wolverine/Envelope.Internals.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ internal Envelope(object message, IMessageSerializer writer)
6363
/// message as a cascading message. Originally added for the
6464
/// Http transport request/reply
6565
/// </summary>
66-
internal bool DoNotCascadeResponse { get; set; }
66+
public bool DoNotCascadeResponse { get; set; }
6767

6868
/// <summary>
6969
/// Status according to the message persistence

0 commit comments

Comments
 (0)