refactor: make subscription lifecycle events durable

This commit is contained in:
jxxghp
2026-08-22 07:43:41 +08:00
parent 8f94fd620d
commit c5de1c7b1b
27 changed files with 1106 additions and 230 deletions
+32 -8
View File
@@ -1,12 +1,15 @@
"""按媒体身份批量删除订阅的应用用例。"""
from typing import Callable, Protocol
from datetime import datetime, timezone
from typing import Any, Callable, Protocol
from app.application.outbox import AsyncOutboxTransaction, OutboxIntent
from app.application.subscription.delete import (
AsyncUnitOfWork,
SubscribeDeletedPublisher,
SubscribeDeletionActor,
SubscribeDeletionCandidate,
build_subscribe_deleted_payload,
)
from app.schemas.types import MediaSource
@@ -24,8 +27,8 @@ class SubscribeIdentityDeletionRepository(Protocol):
"""读取匹配媒体身份的去重订阅快照。"""
...
async def delete(self, subscribe_id: int) -> None:
"""把指定订阅登记为待删除。"""
async def stage_delete(self, subscribe_id: int) -> None:
"""把指定订阅登记为待删除,但不自行提交事务"""
...
@@ -41,12 +44,14 @@ class DeleteSubscriptionsByIdentityCommand:
unit_of_work: AsyncUnitOfWork,
publish_deleted: SubscribeDeletedPublisher,
handle_event_error: SubscribeDeletionEventErrorHandler,
outbox: AsyncOutboxTransaction | None = None,
) -> None:
"""注入数据访问、事务、事件和事件错误处理端口。"""
self._repository = repository
self._unit_of_work = unit_of_work
self._publish_deleted = publish_deleted
self._handle_event_error = handle_event_error
self._outbox = outbox
async def execute(
self,
@@ -68,21 +73,40 @@ class DeleteSubscriptionsByIdentityCommand:
for candidate in candidates
if self._can_delete(candidate, actor)
]
events: list[tuple[SubscribeDeletionCandidate, dict[str, Any]]] = []
for candidate in deletions:
await self._repository.stage_delete(candidate.subscribe_id)
event_payload = build_subscribe_deleted_payload(
candidate.subscribe_id,
candidate.event_payload,
)
events.append((candidate, event_payload))
try:
if self._outbox:
now = datetime.now(timezone.utc)
for _, event_payload in events:
await self._outbox.stage(
OutboxIntent(
event_key=event_payload["idempotency_key"],
topic="subscribe.deleted",
payload=event_payload,
),
now,
)
await self._unit_of_work.commit()
except Exception:
await self._unit_of_work.rollback()
raise
for candidate in deletions:
for candidate, event_payload in events:
try:
await self._publish_deleted(
candidate.subscribe_id,
dict(candidate.event_payload),
)
await self._publish_deleted(event_payload)
if self._outbox:
await self._outbox.complete_by_event_key(
event_payload["idempotency_key"],
datetime.now(timezone.utc),
)
except Exception as error:
self._handle_event_error(candidate.subscribe_id, error)
return len(deletions)