AsyncAPI 3.x: the driver contract for publishing a message - #1738
Open
LautaroPetaccio wants to merge 5 commits into
Open
AsyncAPI 3.x: the driver contract for publishing a message#1738LautaroPetaccio wants to merge 5 commits into
LautaroPetaccio wants to merge 5 commits into
Conversation
arcuri82
force-pushed
the
feature/asyncapi-driver-contract
branch
from
September 8, 2026 11:35
755b0a4 to
513d76a
Compare
arcuri82
force-pushed
the
feature/asyncapi-driver-contract
branch
from
September 8, 2026 11:41
513d76a to
45344be
Compare
arcuri82
force-pushed
the
feature/asyncapi-driver-contract
branch
from
September 8, 2026 11:42
45344be to
cbaae4c
Compare
arcuri82
force-pushed
the
feature/asyncapi-driver-contract
branch
from
September 8, 2026 11:45
cbaae4c to
cbea234
Compare
jgaleotti
requested changes
Sep 8, 2026
| /** | ||
| * Headers to publish alongside the body, for a transport that has them. | ||
| */ | ||
| public Map<String, String> headers = new LinkedHashMap<>(); |
Collaborator
There was a problem hiding this comment.
add a description of what the keys and values are in maps. This programming discipline is specified in docs/for_developers.md
| */ | ||
| public Long replyTimeoutMs; | ||
|
|
||
| public static final String CORRELATION_IN_HEADER = "HEADER"; |
Collaborator
There was a problem hiding this comment.
all constants should be at the beginning of the class
| /** | ||
| * The index of the action this answers, echoing what was asked. | ||
| */ | ||
| public Integer index; |
| * so this is a tuning parameter with no equivalent in a synchronous protocol. It is set | ||
| * generously and reported with the result. | ||
| */ | ||
| public Long replyTimeoutMs; |
Collaborator
Author
There was a problem hiding this comment.
Yes, when no reply is expected (replyAddress null), there is nothing to wait for. I've written it in the javadoc.
| /** | ||
| * The reply's headers, for a transport that has them. | ||
| */ | ||
| public Map<String, String> replyHeaders = new LinkedHashMap<>(); |
Collaborator
There was a problem hiding this comment.
describe what key,value are as specified in docs/for_developers.md
| * How long the driver waited, in milliseconds, whether or not anything arrived. Reported | ||
| * because the verdict on silence is only meaningful alongside how long it was waited for. | ||
| */ | ||
| public Long waitedMs; |
Collaborator
Author
There was a problem hiding this comment.
Yes, when the driver did not wait at all, that is a fire and forget operation, or a message that could not be published. I've written it in the javadoc.
LautaroPetaccio
force-pushed
the
feature/asyncapi-driver-contract
branch
from
September 8, 2026 16:28
cbea234 to
7750cb6
Compare
LautaroPetaccio
force-pushed
the
feature/asyncapi-driver-contract
branch
from
September 8, 2026 17:19
b59d655 to
0596618
Compare
Everything so far is in core and reads a document. This is the other side: what a driver must provide so that the core can drive an AsyncAPI service without knowing anything about brokers. AsyncApiProblem declares that the SUT is driven by messages. It carries only the document -- a location to fetch it from, or the text itself. The connection to the broker stays in the driver and never crosses, which is what keeps the core free of any broker library. Note it may be declared with the document inline, which REST has no real need for. A REST service usually serves its own contract over HTTP; a service that speaks only Kafka has no endpoint to serve anything from, so its document is far more likely to be a file shipped beside it. SutController.executeAsyncApiAction is where a driver publishes one message and, when a reply is expected, waits for the one that answers it. It is the counterpart of executeAction for RPC. It has a default that throws rather than being abstract, so that adding it does not break every existing driver; the message says what to override. Only publish and await are protocol-specific, and they never leave the driver. The driver is asked to report what happened, not to judge it: AsyncApiReplyDto distinguishes published-with-no-reply-expected, a reply that arrived, silence within the window, and a failure to publish at all. The last of those is a broken setup rather than a finding about the service, which is why it is kept apart from silence. Deciding what an outcome means is the core's job, so that it means the same thing whatever the transport. Also carried in the reply is whether the correlation id came back. Whether correlation works cannot be read off a contract, since echoing the id is the service's own behaviour, so it is established by watching for it. Wiring: a field on SutInfoDto, a field on ActionDto, and two branches in EMController.
Review feedback: CORRELATION_IN_HEADER and CORRELATION_IN_PAYLOAD closed the class, after every instance field. docs/for_developers.md puts constants first.
Review feedback, and docs/for_developers.md: a Map field says what its key and value are. Both header maps go from header name to its text.
A null list failed inside addAll anyway; the check now sits where the method starts, named, as docs/for_developers.md asks of public methods.
Review feedback: replyTimeoutMs is null when no reply is expected, and waitedMs when the driver did not wait at all, which the javadoc left unsaid.
arcuri82
force-pushed
the
feature/asyncapi-driver-contract
branch
from
September 9, 2026 10:52
61ca390 to
018bf2b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ninth in the AsyncAPI stack, on top of #7. This one is entirely in
client-java— no core changes, so it reviews as controller-side plumbing on its own.Everything so far reads a document. This is the other side: what a driver must provide so the core can drive an AsyncAPI service without knowing anything about brokers.
Why a driver at all, even for black-box
Unlike REST and GraphQL, there is no universal wire to point at — Kafka, AMQP, MQTT and WebSocket share nothing at the API level. So something has to hold a client and move the bytes, and that something is the driver. This is the one way AsyncAPI is unlike every other black-box mode EvoMaster ships, and it is the same reason RPC needs a driver.
AsyncApiProblemCarries only the document — a location to fetch it from, or the text itself. The connection to the broker stays in the driver and never crosses, which is what keeps core free of any broker dependency.
The inline-text option is worth noting, since REST has little need for it: a REST service usually serves its own contract over HTTP, whereas a service that speaks only Kafka has no endpoint to serve anything from. Its document is far more likely to be a file shipped beside it.
SutController.executeAsyncApiActionWhere a driver publishes one message and, when a reply is expected, waits for the one that answers it — the counterpart of
executeActionfor RPC.It has a default that throws rather than being abstract, so adding it does not break any existing driver; the message names what to override. Only publish and await are protocol-specific, and they never leave the driver.
The driver reports, it does not judge
AsyncApiReplyDtodistinguishes four outcomes, because they mean different things and only one is a fault:Deciding what an outcome means is the core's job, so that it means the same thing whatever the transport.
The reply also carries whether the correlation id came back. Whether correlation works cannot be read off a contract — echoing the id is the service's own behaviour — so it is established by watching for it rather than assumed.
Wiring
A field on
SutInfoDto, a field onActionDto, and two branches inEMController.Testing
Six tests in
AsyncApiProblemTest, including one asserting that a driver pointed at an AsyncAPI service without implementing the hook fails with a message telling it what to override.client-java/controllerruns 740 tests, 0 failures, 34 errors — identical with and without this change. Those 34 are testcontainers suites failing because Docker is not reachable in my environment; I verified the same counts on a pristine tree before claiming that.