use std::future::{Future, IntoFuture};
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use super::kafka::{KafkaPublisher, KafkaSource};
use super::{
run_source, Bus, BusConsumer, BusTopologyConfig, MessagePublisher, MessageRouter, RunOptions,
TransportError,
};
use super::{Message, MessageKind};
const DEFAULT_FETCH_TIMEOUT: Duration = Duration::from_secs(8);
#[derive(Clone)]
pub struct KafkaBus {
brokers: String,
publisher: Arc<KafkaPublisher>,
topology: BusTopologyConfig,
fetch_timeout: Duration,
}
pub struct KafkaBusConnect {
brokers: String,
topology: BusTopologyConfig,
fetch_timeout: Duration,
}
impl KafkaBusConnect {
pub fn group(mut self, group: impl Into<String>) -> Self {
self.topology = self.topology.group(group);
self
}
pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
self.topology = self.topology.namespace(namespace);
self
}
pub fn with_fetch_timeout(mut self, timeout: Duration) -> Self {
self.fetch_timeout = timeout;
self
}
async fn connect(self) -> Result<KafkaBus, TransportError> {
let topology = self.topology.validate_for("kafka")?;
let publisher = KafkaPublisher::connect(&self.brokers).await?;
Ok(KafkaBus {
brokers: self.brokers,
publisher: Arc::new(publisher),
topology,
fetch_timeout: self.fetch_timeout,
})
}
}
impl IntoFuture for KafkaBusConnect {
type Output = Result<KafkaBus, TransportError>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(self.connect())
}
}
impl KafkaBus {
pub fn connect(brokers: &str) -> KafkaBusConnect {
KafkaBusConnect {
brokers: brokers.to_string(),
topology: BusTopologyConfig::default(),
fetch_timeout: DEFAULT_FETCH_TIMEOUT,
}
}
pub async fn connect_with(
brokers: &str,
group: impl Into<String>,
namespace: impl Into<String>,
) -> Result<Self, TransportError> {
Self::connect(brokers)
.group(group)
.namespace(namespace)
.await
}
pub fn group(mut self, group: impl Into<String>) -> Self {
self.topology = self.topology.group(group);
self
}
pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
self.topology = self.topology.namespace(namespace);
self
}
pub fn with_fetch_timeout(mut self, timeout: Duration) -> Self {
self.fetch_timeout = timeout;
self
}
fn validated_namespace(&self) -> Result<String, TransportError> {
self.topology.namespace_for("kafka")
}
fn command_prefix(&self) -> Result<String, TransportError> {
Ok(format!("{}.cmd.", self.validated_namespace()?))
}
fn event_prefix(&self) -> Result<String, TransportError> {
Ok(format!("{}.evt.", self.validated_namespace()?))
}
async fn run<R: MessageRouter>(
&self,
router: Arc<R>,
topics: Vec<String>,
group_id: String,
strip_prefix: String,
options: RunOptions,
) -> Result<(), TransportError> {
if topics.is_empty() {
return Ok(());
}
let topic_refs: Vec<&str> = topics.iter().map(String::as_str).collect();
let source = KafkaSource::connect(&self.brokers, &group_id, &topic_refs)
.await?
.with_fetch_timeout(self.fetch_timeout)
.with_strip_prefix(strip_prefix);
run_source(router, source, options).await
}
}
impl Bus for KafkaBus {
async fn send(&self, name: &str, payload: Vec<u8>) -> Result<(), TransportError> {
self.send_message(Message::new(name, MessageKind::Command, payload))
.await
}
async fn publish(&self, name: &str, payload: Vec<u8>) -> Result<(), TransportError> {
self.publish_message(Message::new(name, MessageKind::Event, payload))
.await
}
async fn send_message(&self, mut message: Message) -> Result<(), TransportError> {
message.name = format!("{}{}", self.command_prefix()?, message.name);
self.publisher.publish(message).await
}
async fn publish_message(&self, mut message: Message) -> Result<(), TransportError> {
message.name = format!("{}{}", self.event_prefix()?, message.name);
self.publisher.publish(message).await
}
}
impl BusConsumer for KafkaBus {
async fn listen<R: MessageRouter>(
&self,
router: Arc<R>,
options: RunOptions,
) -> Result<(), TransportError> {
let plan = router.subscription_plan();
if plan.commands.is_empty() {
return Ok(());
}
let prefix = self.command_prefix()?;
let topics: Vec<String> = plan
.commands
.iter()
.map(|name| format!("{prefix}{name}"))
.collect();
let group = self
.topology
.resolve_consumer_group(router.as_ref(), "kafka")?;
let namespace = self.validated_namespace()?;
let group_id = format!("{namespace}.{group}.cmd");
self.run(router, topics, group_id, prefix, options).await
}
async fn subscribe<R: MessageRouter>(
&self,
router: Arc<R>,
options: RunOptions,
) -> Result<(), TransportError> {
let plan = router.subscription_plan();
if plan.events.is_empty() {
return Ok(());
}
let prefix = self.event_prefix()?;
let topics: Vec<String> = plan
.events
.iter()
.map(|name| format!("{prefix}{name}"))
.collect();
let group = self
.topology
.resolve_consumer_group(router.as_ref(), "kafka")?;
let namespace = self.validated_namespace()?;
let group_id = format!("{namespace}.{group}.evt");
self.run(router, topics, group_id, prefix, options).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bus::SubscriptionPlan;
use rdkafka::config::ClientConfig;
use rdkafka::producer::FutureProducer;
struct EmptyRouter;
impl MessageRouter for EmptyRouter {
fn handles(&self, _kind: MessageKind, _name: &str) -> bool {
false
}
fn subscription_plan(&self) -> SubscriptionPlan {
SubscriptionPlan::default()
}
async fn dispatch(&self, _message: &Message) -> Result<(), TransportError> {
Ok(())
}
}
fn test_bus() -> KafkaBus {
let producer: FutureProducer = ClientConfig::new()
.set("bootstrap.servers", "localhost:1")
.create()
.unwrap();
KafkaBus {
brokers: "localhost:1".to_string(),
publisher: Arc::new(KafkaPublisher::new(producer)),
topology: BusTopologyConfig::default(),
fetch_timeout: Duration::from_millis(1),
}
}
#[tokio::test]
async fn listen_returns_ok_for_empty_plan_without_group() {
let bus = test_bus();
let router = Arc::new(EmptyRouter);
bus.listen(router, RunOptions::idempotent()).await.unwrap();
}
#[tokio::test]
async fn subscribe_returns_ok_for_empty_plan_without_group() {
let bus = test_bus();
let router = Arc::new(EmptyRouter);
bus.subscribe(router, RunOptions::idempotent())
.await
.unwrap();
}
}