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 consume<R: MessageRouter>(
&self,
router: Arc<R>,
options: RunOptions,
kind: MessageKind,
) -> Result<(), TransportError> {
let plan = router.subscription_plan();
let (names, suffix) = match kind {
MessageKind::Command => (plan.commands, "cmd"),
MessageKind::Event => (plan.events, "evt"),
};
if names.is_empty() {
return Ok(());
}
let namespace = self.validated_namespace()?;
let prefix = format!("{namespace}.{suffix}.");
let topics: Vec<String> = names.iter().map(|name| format!("{prefix}{name}")).collect();
let group = self
.topology
.resolve_consumer_group(router.as_ref(), "kafka")?;
let group_id = format!("{namespace}.{group}.{suffix}");
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(prefix);
run_source(router, source, options).await
}
}
impl Bus for KafkaBus {
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> {
self.consume(router, options, MessageKind::Command).await
}
async fn subscribe<R: MessageRouter>(
&self,
router: Arc<R>,
options: RunOptions,
) -> Result<(), TransportError> {
self.consume(router, options, MessageKind::Event).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();
}
}