1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! # dtmrs
//!
//! Rust 写的分布式事务管理器,对标 [DTM](https://github.com/dtm-labs/dtm)。
//! 支持 **SAGA / TCC / 二阶段消息 / XA / workflow** 五种模式,
//! 存储可跑 sqlite / postgres / mysql,对外有 HTTP 和 gRPC 两套等价接口。
//!
//! 这个 crate 是**门面**:把各层重新导出到一处,省得挨个加依赖。
//! 想精确控制依赖的话也可以直接用底下的 `dtmrs-core` / `dtmrs-server` /
//! `dtmrs-barrier` / `dtmrs-xa`。
//!
//! ## 两种用法,别装错
//!
//! dtmrs 的两端要的东西完全不同,feature 就是按这个切的:
//!
//! | 你的角色 | 装什么 | 拿到什么 |
//! |---|---|---|
//! | **跑协调器**(TC) | `dtmrs`(默认) | [`Embedded`]、`dtmrs` 二进制 |
//! | **业务服务**(RM) | `dtmrs = { features = ["barrier"], default-features = false }` | [`barrier`] —— 幂等所必需 |
//! | 业务服务 + XA | 再加 `"xa"` | [`xa`] |
//!
//! 业务侧**只需要屏障**,不该把整个协调器和 axum/tonic 拖进去 ——
//! 所以 `barrier` 不在默认 feature 里,`default-features = false` 之后它非常轻。
//!
//! ## 嵌入式:不用单独部署服务
//!
//! 这是相对 DTM 的结构性差异 —— 协调器当库链进你自己的进程,
//! 分支可以直接是进程内的函数:
//!
//! ```no_run
//! use dtmrs::{BranchResult, Embedded};
//!
//! # async fn demo() -> anyhow::Result<()> {
//! let tc = Embedded::builder("sqlite:app.db")
//! .handler("扣款", |_ctx| async { BranchResult::Success })
//! .handler("扣款撤销", |_ctx| async { BranchResult::Success })
//! .start()
//! .await?;
//!
//! tc.saga("order-1001")
//! .step("local://扣款", "local://扣款撤销")
//! .step("http://shipment/create", "http://shipment/cancel") // 可跟远端混用
//! .submit()
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! 也可以当独立服务跑:`cargo install dtmrs` 之后
//! `DTMRS_DB=sqlite:dtmrs.db dtmrs`。
//!
//! ## 业务侧接入:屏障不是可选项
//!
//! 分支接口**一定会**被重复调用(TC 重试 + 崩溃恢复),所以必须幂等。
//! 把屏障记录和业务 SQL 放进**同一个本地事务**:
//!
//! ```ignore
//! use dtmrs::barrier::{BranchBarrier, Decision};
//! use dtmrs::Backend;
//!
//! let mut bb = BranchBarrier::new(Backend::from_url(&url), tt, gid, branch_id, op)?;
//! let mut tx = pool.begin().await?;
//! if bb.decide(&mut tx).await? == Decision::Execute {
//! // 业务 SQL —— 必须在这个 tx 里
//! }
//! tx.commit().await?; // 原子性的来源
//! ```
//!
//! ## 一条写错就会数据不一致的规矩
//!
//! **超时 ≠ 失败。** 5xx、连接超时、gRPC 的 `UNAVAILABLE` / `DEADLINE_EXCEEDED`
//! 都表示**结果未知** —— 对方可能已经成功了,这时候回滚就是不一致。
//! 只有 HTTP 409、gRPC `ABORTED`、或响应体里的 `FAILURE` 才触发补偿。
//! 见 [`BranchResult`]。
// ---- 状态机与类型:任何 feature 组合下都有 ----
pub use *;
/// 存储层:一套 SQL 跑 sqlite / postgres / mysql
/// 协调器(TC)本体:推进器、嵌入式门面、HTTP/gRPC 接口
/// 子事务屏障 —— 业务侧(RM)做幂等用,**接入 dtmrs 的必需品**
/// XA 两阶段提交的业务侧(RM)助手
// ---- 最常用的东西提到顶层,省得记路径 ----
pub use ;
pub use BranchCtx;
pub use ;
pub use Store;
pub use ;