The same idea, now for events
The through-line of this whole story is one move repeated: separate the interface from the implementation, and decide the rest by configuration. We did it for services, for policies, and for tests. chenile-messaging does it for messaging.
A service that publishes or consumes events should not know — or care — whether the bytes travel over MQTT, Kafka, an Azure service, or never leave the JVM. It should depend on a messaging interface. The broker is an implementation detail chosen at deployment time.
chenile-pub-sub interface. The transport — MQTT, Kafka, Azure, in-JVM — is a swappable implementation selected by configuration. Change the broker without changing a line of business code.
The interface — chenile-pub-sub
The abstraction is deliberately tiny. Publishing is one interface:
public interface ChenilePub {
void publish(String topic, String payload, Map<String,Object> properties);
void asyncPublish(String topic, String payload, Map<String,Object> properties);
// publish addressed to a service operation, resolved via the service registry
void publishToOperation(String service, String operationName,
String payload, Map<String,Object> properties);
}
Consuming is another:
public interface ChenileSub {
void messageArrived(String topic, String message, Map<String,Object> headers);
}
That is the entire contract a service sees. Notice publishToOperation — publishing can be addressed to a service operation rather than a raw topic, with the binding resolved through the service registry (the same registry that carries a service’s policies). Messaging is a first-class entry point, not a side channel.
The implementations — pick your transport
Each transport is a separate module that implements the same interface. Nothing in your service changes when you swap one for another:
chenile-mqtt
MqttPublisher / MqttSubscriber — MQTT brokers, ideal for IoT and edge fan-out. Also used internally by the cloud-edge switch.
chenile-kafka
KafkaPublisher — high-throughput, durable event streaming for backbone pipelines.
chenile-azure
AzurePublisher — managed Azure messaging for cloud-native deployments.
chenile-jvm-pub-sub
JvmPublisher — an in-process implementation of the same interface. Pub/sub with no broker at all — perfect for a modulith, for tests, or for early stages before you externalize the bus.
The in-JVM implementation is the clearest proof that the abstraction is real: the identical publishing code runs against a Kafka cluster or entirely inside one process, decided by which module you deploy.
Why this complements the rest of Chenile
The payoff is bigger than transport portability. Because chenile-core normalizes every trigger into a ChenileExchange and runs it through the same interception pipeline, an inbound message is processed exactly like an HTTP request:
- The same service policies apply — security, multi-tenancy, i18n, logging, idempotency (part 3). You don’t write a second authorization path for events.
- The same service implementation can be reached by REST, by an in-process proxy, or by a message — without rewriting it. A Chenile service is, in effect, protocol-agnostic; Chenile acts as an in-VM message bus that also speaks HTTP.
cloud-edge-switchbuilds on this to route the same messages between edge devices and a cloud broker, so an event can originate at the edge and be handled centrally with identical logic.
org.chenile.pubsub.ChenilePub and ChenileSub in chenile-pub-sub. Implementations: chenile-mqtt, chenile-kafka, chenile-azure, chenile-jvm-pub-sub. Routing: cloud-edge-switch. All ship in the chenile-messaging repository.