Audience: storage-plugin authors and reviewers. The capability matrix is the single source of truth for which plugin implements which capability.
This document is the single source of truth for which storage plugin implements which capability, and how callers detect a capability at runtime. Plugin authors update this matrix when they add or drop a capability; reviewers check it on every PR that touches a storage plugin.
MeshStoragePlugin (in org.apache.eventmesh.api.storage) is the unified storage SPI every backend implements. The 7 capabilities are sub-interfaces of StorageCapabilities (same package). A plugin declares a capability by listing it in its implements clause. A caller detects a capability with instanceof:
if (storage instanceof StorageCapabilities.EndOffsetQuery) { long end = ((StorageCapabilities.EndOffsetQuery) storage) .endOffset(topic, partition); }
The 3 universal capabilities (TopicManagement, PartitionAssignment, ExplicitOffsetCommit) MUST be implemented by every backend. The 4 backend-specific capabilities (EndOffsetQuery, AlignPullOffset, DeferredPopAck, LiteTopic) are declared only by the backends that actually implement them.
| Capability | Universal? | Kafka | RocketMQ 4.x | RocketMQ 5.x | Memory | Why |
|---|---|---|---|---|---|---|
| TopicManagement | yes (U) | Y | Y | Y | Y | All 3 MQs expose createTopic (Kafka Admin / RocketMQ Admin). |
| PartitionAssignment | yes (U) | Y | Y | Y | Y | All 3 MQs treat the topic as a partitionable stream. |
| ExplicitOffsetCommit | yes (U) | Y | Y | Y | Y | All 3 MQs persist an explicit commitOffset on demand. |
| EndOffsetQuery | no (Kafka) | Y | N | N | Y | Only Kafka exposes a high-watermark via endOffsets. |
| AlignPullOffset | mixed | Y | Y | N | Y | Kafka + RocketMQ 4.x manage a client-side pull cursor; R5 POP is broker-managed. |
| DeferredPopAck | R5 only | N | N | Y | Y | R5 POP holds messages invisible until client ACK. |
| LiteTopic | R5 only | N | N | Y | Y | R5 RIP-83 lite sub-topics; not in Kafka or R4. |
U = Universal (every backend MUST implement).
StorageCapabilities is a marker interface system, not a Java SPI attribute. There is no plugin-load-time capability declaration (currently). The contract is:
instanceof to detect and dispatchMeshStoragePluginTCK (JUnit 5 abstract base class) verifies the plugin actually implements every capability its test class declares in expectedCapabilities()Why no load-time check: the current EventMeshSPI loader reads the META-INF/eventmesh/... registry file; capabilities are an interface-implements property, not a metadata property. Adding a load-time capability gate would require changing the SPI loader to reflect over implements clauses -- possible but not yet implemented (see “Future work” below).
Each backend ships a *MeshStoragePluginTCKTest class that extends MeshStoragePluginTCK and runs:
pluginDeclaresExpectedCapabilities -- every capability listed in expectedCapabilities() is actually instanceof-trueinitWithMinimalPropsDoesNotThrow -- init is lazy / safeshutdownAfterInitIsIdempotent -- shutdown can be called twicecreateTopicIsCallable_whenDeclared -- TopicManagement contractcommitOffsetIsCallable_whenDeclared -- ExplicitOffsetCommit contractassignPartitionsIsCallable_whenDeclared -- PartitionAssignment contractThe TCK is part of every plugin module's src/test/java and is included in the standard ./gradlew :eventmesh-storage-plugin:test build (and CI).
implements at SPI load time, log a WARN if a plugin's metadata lists a capability it does not implement, log an ERROR if a caller requires a capability the plugin does not list. Requires an SPI loader change.*MeshStoragePlugin.class for implements StorageCapabilities.X and updates this doc on every build. Until then, plugin authors must update the table by hand on capability changes.maxOffset).