在 Rust 中实现 Repository 仓储模式
前言
单位上有个 Rust 项目,orm 选型很长时间都没定下来,故先设计了抽象的仓储层方便写业务逻辑。
设计抽象接口
抽象只读接口,仅读取使用,目前需求仅用查询 id、查询全部和按名称搜索,当然理应设计上分页。
//! read_only_repository.rs
/// 只读仓储,对仅限读取的仓储进行抽象
#[async_trait::async_trait]
pub trait IReadOnlyRepository<T>
where
T: std::marker::Send,
{
/// 根据 id 获取唯一对象
async fn get_by_id(&self, id: &str) -> anyhow::Result<T>;
/// 获取所有对象
async fn get_all(&self) -> anyhow::Result<Vec<T>>;
/// 根据名称搜索
async fn search_by_name(&self, &str) -> anyhow::Result<Vec<T>>;
}
抽象可变接口,目前仅考虑了插入、修改、删除以及事务提交。
//! mutable_repository.rs
/// 可变仓储,对修改数据的仓储进行抽象
#[async_trait::async_trait]
pub trait IMutableRepository<T>
where
T: std::marker::Send,
{
/// 更新数据
async fn update(&self, entity: T) -> anyhow::Result<T>;
/// 插入数据
async fn insert(&self, entity: T) -> anyhow::Result<T>;
/// 删除数据
async fn delete(&self, entity: T) -> anyhow::Result<bool>;
/// 使用 uuid 删除数据,`entity` 是用于指示当前实现类型的泛型模板,防止 Rust 产生方法重载的问题,
/// 但对于大多数数据库可尝试使用以下代码:
/// ``` no_run
/// // 建立一个空的枚举用于指示类型
/// let n: Option<TYPE> = None;
/// self.delete_by_id(entity.id.as_str(), n).await?;
/// ```
async fn delete_by_id(&self, uuid: &str, entity: Option<T>) -> anyhow::Result<bool>;
/// 提交变更,在带有事务的数据库将提交事务,否则该方法应该仅返回 `Ok(true)`
///
async fn save_changed(&self) -> anyhow::Result<bool>;
}
租约仓储,为了支持非关系型数据库用的,或许会用到租约(生存时间)。
//! lease_repository.rs
/// 租约仓储,对带有租约的仓储进行抽象
#[async_trait::async_trait]
pub trait ILeaseRepository<T>
where
T: std::marker::Send,
{
/// 更新数据并更新租约
async fn update_with_lease(&self, key: &str, entity: T, ttl: i64) -> anyhow::Result<T>;
/// 插入数据并设定租约
async fn insert_with_lease(&self, key: &str, entity: T, ttl: i64) -> anyhow::Result<T>;
/// 延长特定数据的租约
async fn keep_alive(&self, key: &str) -> anyhow::Result<bool>;
}
最终整合的接口。
//! mod.rs
/// 对使用数据库仓储的抽象,带有可读仓储和可写仓储
#[async_trait::async_trait]
pub trait IDBRepository<T>: IReadOnlyRepository<T> + IMutableRepository<T>
where
T: std::marker::Send,
{
}
/// 对使用带有租约的数据库进行抽象,带有租约仓储、可读仓储和可写仓储
#[async_trait::async_trait]
pub trait ILeaseDBRepository<T>: IDBRepository<T> + ILeaseRepository<T>
where
T: std::marker::Send,
{
}
简单实现
泛型具体用起来有一定的生命周期的问题,解决问题的方法也并不难,加控制生命周期的标记。但我目前的实现方案为使用 marco 自动为每个实体类型生成代码。在这里我个人本地暂且先用了 etcd 数据库作为基础实现。
可变仓储的实现:
/// 针对 Etcd 数据库实现只读仓储 `repository::IMutableRepository`
///
/// struct 要求带有字段 `client: std::sync::Arc<etcd_client::Client>`
#[macro_export]
macro_rules! impl_etcd_mutable_repository {
($base_struct: ty, $domain: ty) => {
#[async_trait::async_trait]
impl IMutableRepository<$domain> for $base_struct {
async fn update(&self, entity: $domain) -> anyhow::Result<$domain> {
let mut kv_client = self.client.kv_client();
let key = format!("test_{}_{}", stringify!($domain), entity.id);
kv_client
.put(
key,
Into::<Vec<u8>>::into(serde_json::to_vec(&entity).unwrap()),
None,
)
.await?;
Ok(entity)
}
async fn insert(&self, entity: $domain) -> anyhow::Result<$domain> {
self.update(entity).await
}
async fn delete(&self, entity: $domain) -> anyhow::Result<bool> {
let n: Option<$domain> = None;
self.delete_by_id(entity.id.as_str(), n).await
}
async fn delete_by_id(
&self,
uuid: &str,
entity: Option<$domain>,
) -> anyhow::Result<bool> {
let mut kv_client = self.client.kv_client();
let key = format!("test_{}_{}", stringify!($domain), uuid);
match kv_client.delete(key, None).await {
Ok(x) => Ok(true),
Err(e) => anyhow::bail!(e),
}
}
async fn save_changed(&self) -> anyhow::Result<bool> {
Ok(true)
}
}
};
}
具体应用:
use crate::repository::*;
pub struct EtcdRepository {
client: std::sync::Arc<etcd_client::Client>,
}
impl EtcdRepository {
pub fn new(client: std::sync::Arc<etcd_client::Client>) -> Self {
Self { client }
}
}
impl_etcd_mutable_repository!(
EtcdRepository,
crate::models::UserInfo
);
调用
use crate::models::*;
use crate::repository::IMutableDBRepository;
pub struct UserInfoService {
user_info_repository: std::sync::Arc<dyn IMutableRepository<UserInfo> + Send + Sync>,
}
impl HeartbeatService {
pub fn new(
user_info_repository: std::sync::Arc<dyn IMutableRepository<UserInfo> + Send + Sync>,
) -> Self {
return Self { user_info_repository };
}
}
#[async_trait::async_trait]
pub trait IPluginManagementService {
async fn list_user_infos(&self) -> Result<Vec<UserInfo>>;
}
#[async_trait::async_trait]
impl IPluginManagementService for PluginManagementService {
async fn list_user_infos(&self) -> Result<Vec<UserInfo>> {
self.user_info_repository.get_all().await
}
}
参考
在 Rust 中实现 Repository 仓储模式的更多相关文章
- 从Entity Framework的实现方式来看DDD中的repository仓储模式运用
一:最普通的数据库操作 static void Main(string[] args) { using (SchoolDBEntities db = new SchoolDBEntities()) { ...
- 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改
原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...
- 5.在MVC中使用泛型仓储模式和工作单元来进行增删查改
原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...
- MVC中使用泛型仓储模式和依赖注入
在ASP.NET MVC中使用泛型仓储模式和依赖注入,实现增删查改 原文链接:http://www.codeproject.com/Articles/838097/CRUD-Operations-Us ...
- 在MVC中使用泛型仓储模式和工作单元来进行增删查改
原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...
- 在MVC中使用泛型仓储模式和依赖注入实现增删查改
标签: 原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository ...
- DDD之:Repository仓储模式
在DDD设计中大家都会使用Repository pattern来获取domain model所需要的数据. 1.什么事Repository? "A Repository mediates b ...
- 4.在MVC中使用仓储模式进行增删查改
原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-using-the-repository-pattern-in-mvc/ 系列目录: ...
- MVC5+EF6 入门完整教程十一:细说MVC中仓储模式的应用
摘要: 第一阶段1~10篇已经覆盖了MVC开发必要的基本知识. 第二阶段11-20篇将会侧重于专题的讲解,一篇文章解决一个实际问题. 根据园友的反馈, 本篇文章将会先对呼声最高的仓储模式进行讲解. 文 ...
- MVC5+EF6 入门完整教程11--细说MVC中仓储模式的应用
摘要: 第一阶段1~10篇已经覆盖了MVC开发必要的基本知识. 第二阶段11-20篇将会侧重于专题的讲解,一篇文章解决一个实际问题. 根据园友的反馈, 本篇文章将会先对呼声最高的仓储模式进行讲解. 文 ...
随机推荐
- React后台管理系统 04 配置路径别名、全局样式设置、模块化scss
ts中对于@符号指定的路径不支持,同时vite中也是不支持的,所以我们需要在vite.config.ts中进行指定配置,path是node中自带的一个模块这里爆红的原因是没有进行声明: 我们使用命令对 ...
- ASP.NET MVC4 学习笔记-3
创建一个简单的数据录入程序--Create a Simple Data-Entry Application 在这篇博客中,我们将通过创建一个简单的数据录入程序来探索MVC的其他特点.在这一节中我们要跟 ...
- async-await Rust: 200 多行代码实现一个极简 runtime
What I cannot create, I do not understand Rust 中的 runtime 到底是咋回事, 为了彻底搞懂它, 我在尽量不借助第三方 crate 的情况下实现了一 ...
- ISP图像处理之Demosaic算法及相关
CFA及Demosaic介绍 1.Bayer(拜耳滤波器得到彩色) 图像在将实际的景物转换为图像数据时, 通常是将传感器分别接收红. 绿. 蓝三个分量的信息, 然后将红. 绿. 蓝三个分量的信息合成彩 ...
- Kerberos、黄金票据与白银票据
kerberos Kerberos是一个网络认证协议,用于验证用户和服务之间的身份,解决分布式计算环境中的身份验证问题.它使用加密技术来提供安全的身份验证,并防止网络中的身份欺骗攻击.Kerberos ...
- 浅析本地缓存技术-Guava Cache
1 引言 作为java开发工作者,相信大家对于guava这个工具包都不会太陌生,而对于本地缓存技术guava cache,大家在日常的工作开发中也都有所了解,接下来本文就从各个角度入手来对于Googl ...
- quarkus实战之四:远程热部署
将本地的改动极速同步到远程服务端,并自动生效,掌握此技能,开发调试会更高效 欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/ ...
- Angular: 点击一次按钮,增加一个元素
解决方案 思路 在组件的typesscript文件中,创建一个数组来存储每个按钮的信息 在模板中使用 *ngFor 指令来循环渲染按钮列表 在按钮事件的处理函数中,每次点击按钮时向按钮数组添加一个新的 ...
- 开源.NetCore通用工具库Xmtool使用连载 - XML操作篇
[Github源码] <上一篇> 介绍了Xmtool工具库中的发送短信类库,今天我们继续为大家介绍其中的XML操作类库. XML操作是软件开发过程中经常会遇到的情况:包括XML内容的遍历解 ...
- C#.NET 国密SM2 签名验签 与JAVA互通 ver:20230807
C#.NET 国密SM2 签名验签 与JAVA互通 ver:20230807 .NET 环境:.NET6 控制台程序(.net core). JAVA 环境:JAVA8(JDK8,JAVA 1.8), ...