blob: ee95b357c60c99ecd6bbda842ffb8abd3d5767f4 [file] [view]
# EventMesh Storage SPI
> **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.
## SPI surface
`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`:
```java
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 matrix
| 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).
## Plugin load-time contract
`StorageCapabilities` is a **marker interface system**, not a Java SPI
attribute. There is no plugin-load-time capability declaration
(currently). The contract is:
- A plugin **implements** a sub-interface -> it has the method
- A caller uses `instanceof` to detect and dispatch
- The `MeshStoragePluginTCK` (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).
## TCK coverage
Each backend ships a `*MeshStoragePluginTCKTest` class that extends
`MeshStoragePluginTCK` and runs:
- `pluginDeclaresExpectedCapabilities` -- every capability listed in
`expectedCapabilities()` is actually `instanceof`-true
- `initWithMinimalPropsDoesNotThrow` -- init is lazy / safe
- `shutdownAfterInitIsIdempotent` -- shutdown can be called twice
- `createTopicIsCallable_whenDeclared` -- TopicManagement contract
- `commitOffsetIsCallable_whenDeclared` -- ExplicitOffsetCommit contract
- `assignPartitionsIsCallable_whenDeclared` -- PartitionAssignment contract
- Backend-specific tests gated on the corresponding capability
The 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).
## Future work (deliberately out of scope for #5342)
1. **Load-time capability gate** -- reflect over `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.
2. **Capability matrix auto-generator** -- a Gradle task that
reflection-scans each `*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.
3. **Kafka LiteTopic / R4 EndOffsetQuery shims** -- if a future use
case needs these, the missing capabilities can be implemented via
a thin wrapper (Kafka's lite-topic emulation via compacted topic +
key-prefix; R4's endOffset via `maxOffset`).