| name | ddd-usage |
| description | Guide for using genies_ddd Domain-Driven Design primitives. Use when defining aggregates, domain events, implementing event sourcing patterns, or building DDD-based microservices with the Genies framework. |
DDD Module (genies_ddd)
Overview
genies_ddd 是 Genies 框架的领域驱动设计基础库,提供聚合根模式、领域事件和事件发布能力。采用 Outbox 模式将领域事件持久化到数据库,由 Dapr CDC 异步投递。
核心特性:
- 聚合根标识(AggregateType + WithAggregateId)
- 领域事件接口(DomainEvent trait)
- 事件发布器(publish / publishGenericDomainEvent)
- CloudEvent 兼容消息格式
- 与 genies_derive 宏无缝配合
Architecture
聚合根 → 领域事件 → publish() → Message 表 → CDC/Outbox → Dapr PubSub → 订阅者
核心组件:
AggregateType - 聚合类型标识 trait
WithAggregateId - 聚合 ID 访问 trait
AggregateIdOf<A> - 聚合 ID 类型别名
DomainEvent - 领域事件接口(event_type, event_type_version, event_source, json)
Message - 数据库持久化消息
Headers - CloudEvent 兼容消息头
publish / publishGenericDomainEvent - 事件发布函数
Quick Start
1. Dependencies
[dependencies]
genies_ddd = { workspace = true }
genies_derive = { workspace = true }
serde = { version = "1.0", features = ["derive"] }
2. Define Aggregate Root
use genies_derive::Aggregate;
use serde::{Deserialize, Serialize};
#[derive(Aggregate, Debug, Clone, Serialize, Deserialize)]
#[aggregate_type("Device")]
pub struct Device {
pub id: String,
pub name: String,
pub status: String,
}
#[derive(Aggregate)] 宏自动生成:
AggregateType trait:aggregate_type() 返回 "Device"
WithAggregateId trait:aggregate_id() 返回 &self.id
3. Define Domain Event
use genies_derive::DomainEvent;
use serde::{Deserialize, Serialize};
#[derive(DomainEvent, Debug, Serialize, Deserialize, Default, Clone)]
#[event_type_version("V1")]
#[event_source("com.example.device.domain.Device")]
#[event_type("com.example.device.event.DeviceCreated")]
pub struct DeviceCreatedEvent {
pub id: String,
pub name: String,
pub created_at: i64,
}
#[derive(DomainEvent)] 宏自动生成:
event_type() → "com.example.device.event.DeviceCreated"
event_type_version() → "V1"
event_source() → "com.example.device.domain.Device"
json() → serde_json::to_string(self)
4. Publish Domain Events
use genies_ddd::DomainEventPublisher::{publish, publishGenericDomainEvent};
use rbatis::executor::Executor;
pub async fn create_device(tx: &mut dyn Executor, device: &Device) {
let event = DeviceCreatedEvent {
id: device.id.clone(),
name: device.name.clone(),
created_at: chrono::Utc::now().timestamp_millis(),
};
publish(tx, device, Box::new(event)).await;
}
pub async fn send_notification(tx: &mut dyn Executor) {
let event = NotificationEvent { };
publishGenericDomainEvent(tx, Box::new(event)).await;
}
API Reference
AggregateType Trait
pub trait AggregateType {
fn aggregate_type(&self) -> String;
fn atype() -> String;
}
WithAggregateId Trait
pub trait WithAggregateId {
type Id: Debug + Clone + PartialEq + Serialize + DeserializeOwned;
fn aggregate_id(&self) -> &Self::Id;
}
pub type AggregateIdOf<A> = <A as WithAggregateId>::Id;
DomainEvent Trait
pub trait DomainEvent: Send {
fn event_type_version(&self) -> String;
fn event_type(&self) -> String;
fn event_source(&self) -> String;
fn json(&self) -> String;
}
Message Structure
pub struct Message {
pub id: Option<String>,
pub destination: Option<String>,
pub headers: Option<String>,
pub payload: String,
pub published: Option<u32>,
pub creation_time: Option<i64>,
}
Headers Structure
pub struct Headers {
pub ID: Option<String>,
pub PARTITION_ID: Option<String>,
pub DESTINATION: Option<String>,
pub DATE: Option<String>,
#[serde(rename = "event-aggregate-type")]
pub event_aggregate_type: Option<String>,
#[serde(rename = "event-aggregate-id")]
pub event_aggregate_id: Option<String>,
#[serde(rename = "event-type")]
pub event_type: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
Database Table
CREATE TABLE message (
id VARCHAR(36) PRIMARY KEY,
destination VARCHAR(255),
headers TEXT,
payload TEXT NOT NULL,
published INT DEFAULT 0,
creation_time BIGINT
);
Derive Macro Attributes
#[derive(Aggregate)]
#[aggregate_type("TypeName")] — 指定聚合类型名称(必选)
#[derive(DomainEvent)]
#[event_type("fully.qualified.EventType")] — 事件类型(必选)
#[event_type_version("V1")] — 事件版本(必选)
#[event_source("fully.qualified.AggregateType")] — 事件来源(必选)
Integration
- genies_dapr: Message 表由 Dapr CDC/Outbox 消费,通过 PubSub 投递给
#[topic] 标记的订阅者
- genies_derive: 提供
#[derive(Aggregate)] 和 #[derive(DomainEvent)] 宏
- genies_context: 提供
CONTEXT.rbatis 数据库连接
ID 生成规则
新项目统一使用雪花 ID 作为聚合根/实体 ID。 雪花 ID 是 Java UUID.randomUUID() 的功能平替,用于生成分布式唯一 ID。从 Java 迁移时,若已有数据使用 UUID 且雪花 ID 无法兼容,可继续使用 UUID 保持数据兼容性。
在聚合根工厂方法中生成 ID:
impl Device {
pub fn create(name: String) -> (Self, DeviceCreatedEvent) {
let id = genies::next_id();
let device = Self { id: id.clone(), name: name.clone(), status: "new".into() };
let event = DeviceCreatedEvent { id, name, created_at: chrono::Utc::now().timestamp_millis() };
(device, event)
}
}
在核心库中(无法使用 genies crate 时):
let id = genies_core::id_gen::next_id();
Key Files