2026-07-02 03:22:12 +08:00
|
|
|
|
"""领域事件 DTO。
|
|
|
|
|
|
|
|
|
|
|
|
定义跨层传递的领域事件值对象,供 ``EventPublisherPort`` 发布与订阅者消费。
|
|
|
|
|
|
事件载荷为不可变值对象(``dataclass(frozen=True)``),不泄露领域实体引用。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
from yuxi.channels.contract.dtos.outbox import OutboxStatus
|
|
|
|
|
|
from yuxi.channels.contract.errors import ValidationError
|
|
|
|
|
|
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
|
class OutboxStateChangedEvent:
|
2026-07-03 19:18:13 +08:00
|
|
|
|
"""发件箱状态变更事件(OBX-004)。
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
描述一条发件箱条目(``OutboxEntry``)的状态迁移,由应用层在 outbox
|
|
|
|
|
|
状态机推进时(``markSent`` / ``markFailed`` / ``markSuppressed`` /
|
|
|
|
|
|
``markSentUnconfirmed`` 等)产出,经 ``EventPublisherPort`` 分发给
|
|
|
|
|
|
订阅者。状态值取自 ``OutboxStatus`` 枚举的字符串形式,便于跨层序列化
|
|
|
|
|
|
与审计。
|
|
|
|
|
|
|
|
|
|
|
|
字段:
|
|
|
|
|
|
entry_id: 发件箱条目 ID(``OutboxEntry.outbox_id``)。
|
|
|
|
|
|
old_state: 变更前状态(``OutboxStatus`` 字符串值)。
|
|
|
|
|
|
new_state: 变更后状态(``OutboxStatus`` 字符串值)。
|
|
|
|
|
|
reason: 变更原因(如投递成功、重试失败、栅栏抑制等)。
|
|
|
|
|
|
occurred_at: 事件发生时间。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
entry_id: str
|
2026-07-03 19:18:13 +08:00
|
|
|
|
old_state: OutboxStatus
|
|
|
|
|
|
new_state: OutboxStatus
|
2026-07-02 03:22:12 +08:00
|
|
|
|
reason: str
|
|
|
|
|
|
occurred_at: datetime
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
def __post_init__(self) -> None:
|
|
|
|
|
|
"""校验必填字段非空。
|
|
|
|
|
|
|
|
|
|
|
|
``entry_id`` / ``old_state`` / ``new_state`` / ``reason`` 必须非空,
|
|
|
|
|
|
在构造时即抛出 ``ValidationError``,避免空值事件传播到订阅者
|
|
|
|
|
|
(INV-8)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not self.entry_id:
|
|
|
|
|
|
raise ValidationError("entry_id", "must not be empty")
|
|
|
|
|
|
if not self.old_state:
|
|
|
|
|
|
raise ValidationError("old_state", "must not be empty")
|
|
|
|
|
|
if not self.new_state:
|
|
|
|
|
|
raise ValidationError("new_state", "must not be empty")
|
|
|
|
|
|
if not self.reason:
|
|
|
|
|
|
raise ValidationError("reason", "must not be empty")
|
|
|
|
|
|
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
|
class OutboxEntryPurgedEvent:
|
2026-07-03 19:18:13 +08:00
|
|
|
|
"""发件箱条目被物理清理事件(OBX-005)。
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
描述一批发件箱条目(``OutboxEntry``)被物理删除(purge)的事件,由
|
|
|
|
|
|
调度器定时清理 dead / sent 终态条目时产出,经 ``EventPublisherPort``
|
|
|
|
|
|
分发给订阅者。``entry_ids`` 为本次被清理条目的业务 ID 列表,供下游
|
|
|
|
|
|
做审计、统计与缓存失效等处理。
|
|
|
|
|
|
|
|
|
|
|
|
字段:
|
|
|
|
|
|
entry_ids: 被物理清理的发件箱条目业务 ID 列表(``OutboxEntry.outbox_id``)。
|
|
|
|
|
|
occurred_at: 事件发生时间。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
entry_ids: tuple[str, ...]
|
2026-07-02 03:22:12 +08:00
|
|
|
|
occurred_at: datetime
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
def __post_init__(self) -> None:
|
|
|
|
|
|
"""校验 entry_ids 非空。
|
|
|
|
|
|
|
|
|
|
|
|
``entry_ids`` 必须为非空元组,空列表的清理事件无意义,在构造时即
|
|
|
|
|
|
抛出 ``ValidationError``,避免空事件传播到订阅者(INV-8)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not self.entry_ids:
|
|
|
|
|
|
raise ValidationError("entry_ids", "must not be empty")
|
|
|
|
|
|
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
__all__ = ["OutboxStateChangedEvent", "OutboxEntryPurgedEvent"]
|