chore(infra): add gRPC layer, update protobufs, remove immediate module
- Add gRPC service modules: auth, channel, channel settings, member, permission - Update protobuf definitions and generated code - Remove immediate/ real-time module (superseded by IM service) - Update etcd discovery and registration - Update cache, error, config, and build infrastructure - Add ADR documentation - Update OpenAPI spec
This commit is contained in:
Vendored
+63
-22
@@ -18,10 +18,10 @@ pub struct AppCache {
|
||||
}
|
||||
|
||||
impl AppCache {
|
||||
pub fn from_config(config: &AppConfig) -> AppResult<Self> {
|
||||
pub async fn from_config(config: &AppConfig) -> AppResult<Self> {
|
||||
let cap = config.lru_default_capacity()?;
|
||||
let ttl = Duration::from_secs(config.lru_default_ttl_secs()?);
|
||||
let l2 = AppRedis::from_config(config)?;
|
||||
let l2 = AppRedis::from_config(config).await?;
|
||||
let key_prefix = config.redis_key_prefix()?;
|
||||
Ok(Self {
|
||||
l1: LruTtlCache::new(cap, ttl),
|
||||
@@ -31,17 +31,18 @@ impl AppCache {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
|
||||
pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
|
||||
if let Some(json) = self.l1.get(&key.to_string()) {
|
||||
return serde_json::from_str(&json).ok();
|
||||
}
|
||||
|
||||
let full_key = self.full_key(key);
|
||||
let mut conn = self.l2.get_connection().ok()?;
|
||||
let mut conn = self.l2.get_connection();
|
||||
let json: String = Cmd::new()
|
||||
.arg("GET")
|
||||
.arg(&full_key)
|
||||
.query::<Option<String>>(&mut *conn.inner_mut())
|
||||
.query_async::<Option<String>>(&mut conn)
|
||||
.await
|
||||
.ok()??;
|
||||
|
||||
let value: T = serde_json::from_str(&json).ok()?;
|
||||
@@ -49,46 +50,86 @@ impl AppCache {
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn set<T: Serialize>(&self, key: &str, value: &T, ttl: Option<Duration>) -> AppResult<()> {
|
||||
pub async fn get_l2_only<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
|
||||
let full_key = self.full_key(key);
|
||||
let mut conn = self.l2.get_connection();
|
||||
let json: String = Cmd::new()
|
||||
.arg("GET")
|
||||
.arg(&full_key)
|
||||
.query_async::<Option<String>>(&mut conn)
|
||||
.await
|
||||
.ok()??;
|
||||
|
||||
serde_json::from_str(&json).ok()
|
||||
}
|
||||
|
||||
pub async fn set<T: Serialize>(
|
||||
&self,
|
||||
key: &str,
|
||||
value: &T,
|
||||
ttl: Option<Duration>,
|
||||
) -> AppResult<()> {
|
||||
let json = serde_json::to_string(value)?;
|
||||
let full_key = self.full_key(key);
|
||||
let ttl_duration = ttl.unwrap_or(self.default_ttl);
|
||||
let ttl_secs = ttl_duration.as_secs() as usize;
|
||||
let mut conn = self.l2.get_connection()?;
|
||||
let mut conn = self.l2.get_connection();
|
||||
Cmd::new()
|
||||
.arg("SETEX")
|
||||
.arg(&full_key)
|
||||
.arg(ttl_secs)
|
||||
.arg(&json)
|
||||
.query::<()>(&mut *conn.inner_mut())?;
|
||||
.query_async::<()>(&mut conn)
|
||||
.await?;
|
||||
self.l1.insert_with_ttl(key.to_string(), json, ttl_duration);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete(&self, key: &str) -> AppResult<()> {
|
||||
self.l1.remove(&key.to_string());
|
||||
pub async fn set_l2_only<T: Serialize>(
|
||||
&self,
|
||||
key: &str,
|
||||
value: &T,
|
||||
ttl: Option<Duration>,
|
||||
) -> AppResult<()> {
|
||||
let json = serde_json::to_string(value)?;
|
||||
let full_key = self.full_key(key);
|
||||
let mut conn = self.l2.get_connection()?;
|
||||
let ttl_duration = ttl.unwrap_or(self.default_ttl);
|
||||
let ttl_secs = ttl_duration.as_secs() as usize;
|
||||
let mut conn = self.l2.get_connection();
|
||||
Cmd::new()
|
||||
.arg("DEL")
|
||||
.arg("SETEX")
|
||||
.arg(&full_key)
|
||||
.query::<()>(&mut *conn.inner_mut())?;
|
||||
.arg(ttl_secs)
|
||||
.arg(&json)
|
||||
.query_async::<()>(&mut conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exists(&self, key: &str) -> bool {
|
||||
pub async fn delete(&self, key: &str) -> AppResult<()> {
|
||||
self.l1.remove(&key.to_string());
|
||||
let full_key = self.full_key(key);
|
||||
let mut conn = self.l2.get_connection();
|
||||
Cmd::new()
|
||||
.arg("DEL")
|
||||
.arg(&full_key)
|
||||
.query_async::<()>(&mut conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn exists(&self, key: &str) -> bool {
|
||||
if self.l1.get(&key.to_string()).is_some() {
|
||||
return true;
|
||||
}
|
||||
let full_key = self.full_key(key);
|
||||
if let Ok(mut conn) = self.l2.get_connection() {
|
||||
return Cmd::new()
|
||||
.arg("EXISTS")
|
||||
.arg(&full_key)
|
||||
.query(&mut *conn.inner_mut())
|
||||
.unwrap_or(false);
|
||||
}
|
||||
false
|
||||
let mut conn = self.l2.get_connection();
|
||||
Cmd::new()
|
||||
.arg("EXISTS")
|
||||
.arg(&full_key)
|
||||
.query_async::<bool>(&mut conn)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn full_key(&self, key: &str) -> String {
|
||||
|
||||
Vendored
+53
-68
@@ -1,15 +1,13 @@
|
||||
use crate::config::AppConfig;
|
||||
use crate::error::AppError;
|
||||
use crate::error::AppResult;
|
||||
use r2d2::Pool;
|
||||
use crate::error::{AppError, AppResult};
|
||||
use futures_util::future::BoxFuture;
|
||||
use redis::cluster::ClusterClient;
|
||||
use redis::{Client, ConnectionLike, RedisError};
|
||||
use std::time::Duration;
|
||||
use redis::{Client, FromRedisValue};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum RedisBackend {
|
||||
Single(Pool<Client>),
|
||||
Cluster(Pool<ClusterClient>),
|
||||
Single(redis::aio::ConnectionManager),
|
||||
Cluster(redis::cluster_async::ClusterConnection),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -18,100 +16,87 @@ pub struct AppRedis {
|
||||
}
|
||||
|
||||
impl AppRedis {
|
||||
pub fn from_config(config: &AppConfig) -> AppResult<Self> {
|
||||
pub async fn from_config(config: &AppConfig) -> AppResult<Self> {
|
||||
let backend = if config.redis_cluster_enabled()? {
|
||||
let nodes = config.redis_cluster_nodes()?;
|
||||
let cluster_client =
|
||||
ClusterClient::new(nodes.iter().map(|s| s.as_str()).collect::<Vec<_>>())?;
|
||||
let pool = Self::build_pool(config, cluster_client)?;
|
||||
RedisBackend::Cluster(pool)
|
||||
let conn = cluster_client.get_async_connection().await?;
|
||||
RedisBackend::Cluster(conn)
|
||||
} else {
|
||||
let url = config
|
||||
.redis_url()?
|
||||
.ok_or_else(|| AppError::Config("APP_REDIS_URL is not set".into()))?;
|
||||
let client = Client::open(url.as_str())?;
|
||||
let pool = Self::build_pool(config, client)?;
|
||||
RedisBackend::Single(pool)
|
||||
let conn = client.get_connection_manager().await?;
|
||||
RedisBackend::Single(conn)
|
||||
};
|
||||
|
||||
Ok(Self { backend })
|
||||
}
|
||||
|
||||
fn build_pool<M: r2d2::ManageConnection>(config: &AppConfig, manager: M) -> AppResult<Pool<M>> {
|
||||
let max_conn = config.redis_max_connections()?;
|
||||
let min_conn = config.redis_min_connections()?;
|
||||
let idle_timeout = config.redis_idle_timeout()?;
|
||||
let conn_timeout = config.redis_connection_timeout()?;
|
||||
|
||||
Ok(r2d2::Builder::new()
|
||||
.max_size(max_conn)
|
||||
.min_idle(Some(min_conn))
|
||||
.idle_timeout(Some(Duration::from_secs(idle_timeout)))
|
||||
.connection_timeout(Duration::from_secs(conn_timeout))
|
||||
.build(manager)?)
|
||||
}
|
||||
|
||||
pub fn get_connection(&self) -> Result<PooledRedisConnection, r2d2::Error> {
|
||||
pub fn get_connection(&self) -> RedisConnection {
|
||||
match &self.backend {
|
||||
RedisBackend::Single(pool) => pool.get().map(PooledRedisConnection::Single),
|
||||
RedisBackend::Cluster(pool) => pool.get().map(PooledRedisConnection::Cluster),
|
||||
RedisBackend::Single(cm) => RedisConnection::Single(cm.clone()),
|
||||
RedisBackend::Cluster(cc) => RedisConnection::Cluster(cc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum PooledRedisConnection {
|
||||
Single(r2d2::PooledConnection<Client>),
|
||||
Cluster(r2d2::PooledConnection<ClusterClient>),
|
||||
pub enum RedisConnection {
|
||||
Single(redis::aio::ConnectionManager),
|
||||
Cluster(redis::cluster_async::ClusterConnection),
|
||||
}
|
||||
|
||||
impl PooledRedisConnection {
|
||||
pub fn inner_mut(&mut self) -> &mut dyn ConnectionLike {
|
||||
impl redis::aio::ConnectionLike for RedisConnection {
|
||||
fn req_packed_command<'a>(
|
||||
&'a mut self,
|
||||
cmd: &'a redis::Cmd,
|
||||
) -> BoxFuture<'a, redis::RedisResult<redis::Value>> {
|
||||
match self {
|
||||
PooledRedisConnection::Single(conn) => conn,
|
||||
PooledRedisConnection::Cluster(conn) => conn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionLike for PooledRedisConnection {
|
||||
fn req_packed_command(&mut self, cmd: &[u8]) -> Result<redis::Value, RedisError> {
|
||||
match self {
|
||||
PooledRedisConnection::Single(conn) => conn.req_packed_command(cmd),
|
||||
PooledRedisConnection::Cluster(conn) => conn.req_packed_command(cmd),
|
||||
Self::Single(c) => Box::pin(c.req_packed_command(cmd)),
|
||||
Self::Cluster(c) => Box::pin(c.req_packed_command(cmd)),
|
||||
}
|
||||
}
|
||||
|
||||
fn req_packed_commands(
|
||||
&mut self,
|
||||
cmd: &[u8],
|
||||
fn req_packed_commands<'a>(
|
||||
&'a mut self,
|
||||
cmd: &'a redis::Pipeline,
|
||||
offset: usize,
|
||||
count: usize,
|
||||
) -> Result<Vec<redis::Value>, RedisError> {
|
||||
) -> BoxFuture<'a, redis::RedisResult<Vec<redis::Value>>> {
|
||||
match self {
|
||||
PooledRedisConnection::Single(conn) => conn.req_packed_commands(cmd, offset, count),
|
||||
PooledRedisConnection::Cluster(conn) => conn.req_packed_commands(cmd, offset, count),
|
||||
Self::Single(c) => Box::pin(c.req_packed_commands(cmd, offset, count)),
|
||||
Self::Cluster(c) => Box::pin(c.req_packed_commands(cmd, offset, count)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_db(&self) -> i64 {
|
||||
match self {
|
||||
PooledRedisConnection::Single(conn) => conn.get_db(),
|
||||
PooledRedisConnection::Cluster(conn) => conn.get_db(),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_connection(&mut self) -> bool {
|
||||
match self {
|
||||
PooledRedisConnection::Single(conn) => conn.check_connection(),
|
||||
PooledRedisConnection::Cluster(conn) => conn.check_connection(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_open(&self) -> bool {
|
||||
match self {
|
||||
PooledRedisConnection::Single(conn) => conn.is_open(),
|
||||
PooledRedisConnection::Cluster(conn) => conn.is_open(),
|
||||
Self::Single(c) => c.get_db(),
|
||||
Self::Cluster(c) => c.get_db(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RedisConnection {
|
||||
pub async fn query_async<T: FromRedisValue>(
|
||||
&mut self,
|
||||
cmd: &mut redis::Cmd,
|
||||
) -> redis::RedisResult<T> {
|
||||
match self {
|
||||
Self::Single(c) => cmd.query_async(c).await,
|
||||
Self::Cluster(c) => cmd.query_async(c).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_pipeline_async<T: FromRedisValue>(
|
||||
&mut self,
|
||||
pipe: &mut redis::Pipeline,
|
||||
) -> redis::RedisResult<T> {
|
||||
match self {
|
||||
Self::Single(c) => pipe.query_async(c).await,
|
||||
Self::Cluster(c) => pipe.query_async(c).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user