bpm-engine 0.2.0

Lightweight embeddable BPM runtime for long-running, stateful workflows with tokens, timers, Saga compensation, and crash recovery
Documentation
好,这一步 **C** 非常“架构师级别” 🧠
我们来把你的 BPM Engine **模块 & trait 依赖关系**一次性讲清楚,并且给你一套 **“用 Rust 强制约束架构”的方法**。

> 目标一句话:
> **让错误的依赖在 `cargo build` 阶段就死掉**

---

# 🧩 一、总体模块依赖图(文字版)

先给你**全局视图**,这是你整个项目**唯一允许的依赖方向**:

```
          ┌────────────────────┐
          │     server/*       │  (REST / gRPC / UI)
          └─────────▲──────────┘
              ┌─────┴─────┐
              │  runtime  │  (Engine / Scheduler / Executor)
              └─────▲─────┘
        ┌───────────┴───────────┐
        │                       │
┌───────┴────────┐      ┌───────┴────────┐
│    storage     │      │      core      │
│ (traits only)  │      │ (pure domain)  │
└───────▲────────┘      └────────────────┘
┌───────┴────────┐
│   adapters/*   │  (memory / postgres / sqlite)
└────────────────┘
```

### 🔴 任何反向箭头 = 架构破坏

---

# 🧠 二、每一层“**允许知道什么 / 不允许知道什么**

这是最关键的部分。

---

## 1️⃣ `bpm-core` —— 领域宇宙的中心(Pure Domain)

### ✅ 它知道什么

- Process / Node / Token / Event
- Token State Machine
- Saga / Compensation
- Parallel / Join / Gateway 语义

### ❌ 它**永远不知道**

- 数据库
- async / tokio
- HTTP / gRPC
- Timer 的真实时间
- Scheduler / Engine

### 🔒 Rust 约束方式

```toml
# bpm-core/Cargo.toml
[dependencies]
# ❌ 不允许 async-trait
# ❌ 不允许 tokio
```

**你只要看到 core 里出现 `async fn`,就说明设计已经开始腐烂。**

---

## 2️⃣ `bpm-storage` —— 世界与引擎之间的“契约”

### 它的定位

> **不是实现存储,而是“声明引擎对世界的最低要求”**

### ✅ 它知道什么

- core 的类型
- async trait
- Result / Error

### ❌ 它不知道

- Engine
- Scheduler
- HTTP
- 具体 DB 实现

### 正确的 trait 形态

```rust
#[async_trait]
pub trait TokenStore {
    async fn claim_runnable(&self, limit: usize) -> Result<Vec<Token>>;
    async fn save(&self, token: &Token) -> Result<()>;
}
```

### 🚫 反模式(禁止)

```rust
async fn save_and_dispatch(...)
```

> storage **永远不该表达业务语义**

---

## 3️⃣ `bpm-runtime` —— 执行语义的唯一拥有者

### 这是你项目的“心脏”

### ✅ 它知道什么

- core 的所有模型
- storage 的 trait
- Scheduler / Dispatcher / Executor
- 并发 / 乐观锁
- Retry / Timeout / Saga 触发

### ❌ 它不知道

- HTTP 细节
- gRPC proto
- DB 类型(Postgres / SQLite)

### 关键抽象:**用 trait 把未来锁死**

```rust
pub struct Engine<S>
where
    S: TokenStore + ProcessStore + TimerStore,
{
    store: Arc<S>,
}
```

👉 这行代码的意义是:

- runtime **依赖能力,不依赖实现**
- adapter 可以随时换

---

## 4️⃣ `bpm-adapters/*` —— 世界的“肉身”

### 它的唯一职责

> **实现 storage trait**

### ✅ 它知道什么

- storage trait
- core 的模型
- DB / 内存 / MQ

### ❌ 它不能知道

- Engine
- Scheduler
- Token 执行逻辑
- API 层

### 正确示例

```rust
impl TokenStore for MemoryTokenStore {
    async fn claim_runnable(&self, limit: usize) -> Result<Vec<Token>> {
        // CAS / mutex / 乐观锁
    }
}
```

### 🚫 禁止

```rust
store.execute_token(...)
```

---

## 5️⃣ `server/*` —— 协议与世界的边界

### 它的角色

- REST / gRPC
- DTO / Auth / RBAC
- 调用 runtime API

### ✅ 它知道什么

- runtime 的 public API
- adapter 的构造方式

### ❌ 它不知道

- Token 内部状态机
- Scheduler 算法
- DB 表结构

### 正确模式

```rust
async fn start_process(
    State(engine): State<Arc<Engine<_>>>
) {
    engine.start_process(...).await;
}
```

---

# 🔐 三、如何用 Rust「强制」这个依赖关系

## ✅ 1. Cargo.toml 是你的第一道防线

**绝不在 core/storage 中加入:**

- tokio
- axum
- tonic
- sqlx

---

## ✅ 2. `pub` 的使用要极度克制

### core 中:

```rust
pub struct Token { ... }
```

### runtime 中:

```rust
pub(crate) fn execute_token(...)
```

👉 **Engine 对外 API ≠ 内部执行 API**

---

## ✅ 3. Trait 是“单向箭头”

- core **定义数据**
- storage **定义能力**
- runtime **实现流程**
- adapter **实现能力**

**永远不要反过来**

---

## ✅ 4. 一个简单但致命的自检问题

你每加一个 `use xxx`,问自己一句:

> “这一层**概念上**应该知道它吗?”

如果答案模糊 —— 说明模块边界已经开始滑坡。

---

# 🧪 四、模块依赖的“单元测试思路”

你可以写一个 **架构测试(compile-time)**:

```bash
# bpm-core
cargo build --no-default-features
```

如果 core 还能 build:

✅ 没偷带 runtime / async 进来

---

# 🧭 五、把这套依赖关系写进 README(非常重要)

建议加一节:

```md
## Architecture Invariants

- core never depends on runtime or storage
- storage defines traits only
- runtime owns execution semantics
- adapters implement storage
- server is a thin shell
```

这是**未来贡献者的“宪法”**。

---

# 🧠 总结一句话(给你吃个定心丸)

你现在这套:

- **不是 Java BPM 的“搬运”**
- 是非常 **Rust-native 的引擎分层**
- 已经具备「长期演进不崩」的结构条件

---

## 下一步你可以选:

**D** — 我直接帮你写一份「**memory adapter + runtime tick 的完整跑通实现**」
**E** — 我给你一份「**从现有仓库迁移到新结构的逐步重构 checklist**」
**F** — 我们一起写一份 **Architecture Decision Record(ADR)**

你选哪个,我们继续深挖 🚀