Service
Event Hubs (AMQP data plane + management plane)
API Action / Feature
Partition support: the $management READ operations (com.microsoft:eventhub, com.microsoft:partition) and the partitioned consumer path they feed.
Azure Documentation
Why is this needed?
Consuming from Event Hubs does not work at all today, for any SDK. Every consumer starts by asking the management node which partitions exist, and nothing answers:
get_eventhub_properties failed: Transport Implementation Error: StatusCode is nor found
With #237 (CBS + the Artemis library patches) publishing works end to end, so this is the last thing between floci-az and a working Event Hubs round-trip.
There is also a smaller correctness point: the entity config syntax accepts a partition count — floci.az.services.event-hub.entities is documented as "name:partitions", e.g. eh1:4 — and ArtemisConfigGenerator.parseEntities then discards it:
"The partition count in the entities string is accepted but ignored (Artemis handles routing)."
So a hub configured with four partitions is emulated as one undivided stream, silently.
Current state
grep -ri partition src/main/java/io/floci/az/services/eventhub/ returns exactly one hit — the comment above. There are no partition ids, no partition addresses, no partition properties. A hub becomes an Artemis address with one durable queue per consumer group.
The consumer path needs three things that do not exist yet:
- Management replies.
com.microsoft:eventhub READ must return name, created_at, partition_count and partition_ids; com.microsoft:partition READ must return begin_sequence_number, last_enqueued_sequence_number, last_enqueued_offset, last_enqueued_time_utc, is_partition_empty. A missing field fails the call outright.
- Per-partition addresses. SDKs attach the receiver to
amqps://{namespace}/{hub}/ConsumerGroups/{group}/Partitions/{id}. The generated topology has no /Partitions/ segment. Because Event Hubs sets auto-create-addresses=true, an attach today succeeds against an empty auto-created address and the consumer reads nothing — a silent wrong answer rather than an error.
- Start positions. Earliest / latest / from-offset / from-sequence-number / from-enqueued-time arrive as AMQP selector filters over
x-opt-* annotations, so those values have to exist on messages and be filterable.
Proposed implementation
Real N-partition emulation, using machinery that is already in the repo.
Topology — a durable queue per (consumer group, partition), named to match the address SDKs attach to:
<hub>/ConsumerGroups/<group>/Partitions/<n>
Routing and stamping — an Artemis broker plugin, the same mechanism as the Service Bus plugins already shipped in servicebus-artemis-extension.jar. On route it would:
- choose the partition — an explicitly pinned partition id if present, else a stable hash of the partition key, else round-robin;
- assign per-partition monotonic
x-opt-sequence-number and x-opt-offset, and stamp x-opt-enqueued-time;
- set the partition as an ordinary message property so each partition queue can select its own messages with a native Artemis filter.
Management responder — the $cbs pattern reused: a $management → $management-intercept divert plus a proton-j responder modelled on ServiceBusCbsResponder, answering both READ types from the counters the plugin maintains.
Start positions — once the annotations are real, filterable properties, Artemis selectors can serve all five forms.
Config — stop discarding the count in parseEntities and thread it through to topology and management.
This also lines the AMQP arm up with the Kafka arm, where Redpanda already has native partitions: a hub declared eh1:4 would mean the same thing over both protocols.
Design questions
Worth settling before any code, since these change the shape of it:
- Is real multi-partition the direction you want? The cheaper option is to emulate a single partition and report
partition_ids: ["0"], following the floci-az is always single-partition precedent in CosmosHandler. It would unblock consumers for far less work, but partitions are central to Event Hubs' consumer model — partition keys, per-partition ordering, per-partition checkpoints, EventProcessor distributing partitions across instances — so a single-partition emulator would make all of that pass against behaviour that cannot occur in production. My preference is real partitions for exactly that reason, but it is your call.
- Validation of the configured count. It becomes load-bearing, and today it has no bounds at all —
parseEntities splits on : and discards the number, so eh1:0, eh1:abc and eh1:10000 are silently equivalent. Reject zero and non-numeric (Azure requires at least one), and what upper bound? Each partition costs a durable queue per consumer group, so the count multiplies: eh1:10000 with five groups would be 50,000 queues. Azure caps at 32 per hub outside dedicated clusters, which seems a reasonable limit to borrow — cap, warn, or reject beyond it?
- Broker plugin vs. an AMQP proxy for routing and stamping — the plugin looks right given the Service Bus precedent, but is there a reason you would avoid one here?
- Persistence — in-memory per-namespace counters, consistent with
storage: memory, or must sequence numbers and offsets survive a restart?
- Partition assignment — does the hash need to match Azure's real assignment for a given partition key, or is any stable hash acceptable?
Are you willing to contribute a PR?
Happy to implement this once the direction above is settled — I would rather agree the semantics first than send a few hundred lines of Java that encodes the wrong model. Context: #227 reported the CBS gap, and #237 fixes CBS plus the max-message-size library patches, which is what made publishing work; this is the remaining half.
Service
Event Hubs (AMQP data plane + management plane)
API Action / Feature
Partition support: the
$managementREAD operations (com.microsoft:eventhub,com.microsoft:partition) and the partitioned consumer path they feed.Azure Documentation
Why is this needed?
Consuming from Event Hubs does not work at all today, for any SDK. Every consumer starts by asking the management node which partitions exist, and nothing answers:
With #237 (CBS + the Artemis library patches) publishing works end to end, so this is the last thing between floci-az and a working Event Hubs round-trip.
There is also a smaller correctness point: the entity config syntax accepts a partition count —
floci.az.services.event-hub.entitiesis documented as"name:partitions", e.g.eh1:4— andArtemisConfigGenerator.parseEntitiesthen discards it:So a hub configured with four partitions is emulated as one undivided stream, silently.
Current state
grep -ri partition src/main/java/io/floci/az/services/eventhub/returns exactly one hit — the comment above. There are no partition ids, no partition addresses, no partition properties. A hub becomes an Artemis address with one durable queue per consumer group.The consumer path needs three things that do not exist yet:
com.microsoft:eventhubREAD must returnname,created_at,partition_countandpartition_ids;com.microsoft:partitionREAD must returnbegin_sequence_number,last_enqueued_sequence_number,last_enqueued_offset,last_enqueued_time_utc,is_partition_empty. A missing field fails the call outright.amqps://{namespace}/{hub}/ConsumerGroups/{group}/Partitions/{id}. The generated topology has no/Partitions/segment. Because Event Hubs setsauto-create-addresses=true, an attach today succeeds against an empty auto-created address and the consumer reads nothing — a silent wrong answer rather than an error.x-opt-*annotations, so those values have to exist on messages and be filterable.Proposed implementation
Real N-partition emulation, using machinery that is already in the repo.
Topology — a durable queue per (consumer group, partition), named to match the address SDKs attach to:
Routing and stamping — an Artemis broker plugin, the same mechanism as the Service Bus plugins already shipped in
servicebus-artemis-extension.jar. On route it would:x-opt-sequence-numberandx-opt-offset, and stampx-opt-enqueued-time;Management responder — the
$cbspattern reused: a$management→$management-interceptdivert plus a proton-j responder modelled onServiceBusCbsResponder, answering both READ types from the counters the plugin maintains.Start positions — once the annotations are real, filterable properties, Artemis selectors can serve all five forms.
Config — stop discarding the count in
parseEntitiesand thread it through to topology and management.This also lines the AMQP arm up with the Kafka arm, where Redpanda already has native partitions: a hub declared
eh1:4would mean the same thing over both protocols.Design questions
Worth settling before any code, since these change the shape of it:
partition_ids: ["0"], following thefloci-az is always single-partitionprecedent inCosmosHandler. It would unblock consumers for far less work, but partitions are central to Event Hubs' consumer model — partition keys, per-partition ordering, per-partition checkpoints,EventProcessordistributing partitions across instances — so a single-partition emulator would make all of that pass against behaviour that cannot occur in production. My preference is real partitions for exactly that reason, but it is your call.parseEntitiessplits on:and discards the number, soeh1:0,eh1:abcandeh1:10000are silently equivalent. Reject zero and non-numeric (Azure requires at least one), and what upper bound? Each partition costs a durable queue per consumer group, so the count multiplies:eh1:10000with five groups would be 50,000 queues. Azure caps at 32 per hub outside dedicated clusters, which seems a reasonable limit to borrow — cap, warn, or reject beyond it?storage: memory, or must sequence numbers and offsets survive a restart?Are you willing to contribute a PR?
Happy to implement this once the direction above is settled — I would rather agree the semantics first than send a few hundred lines of Java that encodes the wrong model. Context: #227 reported the CBS gap, and #237 fixes CBS plus the
max-message-sizelibrary patches, which is what made publishing work; this is the remaining half.