🧱 DDD for python
モジュラモノリス + ドメイン駆動設計のカーネル。業務語彙をひとつも持たないことを設計制約にしている。
アーキテクチャ契約の検査は clean-architecture が担う。 このリポジトリは「import できるカーネル」だけを配る。
入っているもの
| 層 | 提供するもの |
|---|---|
| 合成 | AppModule / CompositeModule — モジュールの宣言点と合成ルート |
| 集約 | DomainEvent / DomainEventPublisher / DomainEventSubscriber / DomainRegistry |
| ユースケース | UnitOfWork / ApplicationServiceLifeCycle / @transactional |
| outbox | StoredEvent / EventStore / EventContextProvider |
| inbox | ConsumedNotification / ConsumedNotificationStore / MessageSubscriber |
| 発行 | Notification / NotificationPublisher / PublishedNotificationTracker |
| アダプタ | InMem*(テスト用) / SQLAlchemyUnitOfWork(extras: sqlalchemy) |
| 適合テスト | ddd4py.common.testing.verify_* — 自分のアダプタ実装が契約を満たすか検証する |
導入
uv add ddd4py
uv add "ddd4py[sqlalchemy]" # SQLAlchemy アダプタも使う場合
配布名は ddd4py、import 名は ddd4py.<module> になる。今あるモジュールは ddd4py.common
(業務語彙を持たないカーネル本体)だけで、ddd4py 直下には __version__ しか置かない。
common 以外のモジュールを足しても import 名が衝突しないための空間の取り方。
from ddd4py.common import DomainEvent, UnitOfWork, transactional
from ddd4py.common.port.adapter.persistence.inmem import InMemEventStore
トランザクションと配送の保証
@transactional の内側で publish されたドメインイベントは、集約の更新と同一トランザクションで
outbox(StoredEvent)に追記される。ネストした境界は最外に join し、内側で失敗すれば最外まで巻き戻る
(部分 commit を許さない)。
受信側は MessageSubscriber._dispatch が listener ごとにトランザクションを開始し、
consumed marker の INSERT(claim-before-process)と listener の副作用を単一トランザクションで commit する
(Idempotent Consumer / transactional inbox)。at-least-once の重複は marker の unique 制約が先勝ち 1 つに絞る。
実行文脈というただ 1 つの拡張点
カーネルは「いま誰のリクエストを処理しているか」を知らない。知っているのは、outbox にそれを刻印する ことと、受信時にそれを確立することだけ。
from ddd4py.common import EventContext, EventContextProvider
class TenantContextProvider(EventContextProvider):
def current(self) -> EventContext:
tenant = CurrentTenant.get()
return EventContext(partition_key=tenant.id, payload=tenant.to_dict())
@contextmanager
def bind(self, context: EventContext) -> Iterator[None]:
with CurrentTenant.of(context.payload).bind():
yield
単一テナントなら NullEventContextProvider のままでよい。
案件ごとの差し替え
CompositeModule は並べた順に DI を登録し、後ろに置いたモジュールが前の束縛を差し替える。
案件固有の実装は継承や上書きではなく、後ろにモジュールを足すことで注入する。
CompositeModule([Core(), Authority(), Tenant(), AcmeOverrides()])
適合テスト
自分のアダプタ実装が契約を満たすかを、利用側の CI で検証する。
from ddd4py.common.testing import verify_consumed_notification_store
def test_postgresql_consumed_notification_store(store):
verify_consumed_notification_store(store)
開発
task init # 依存インストール
task test # テスト
task style:check # ruff / mypy
task style:check:arch # clean-architecture による自分自身への契約検査
リリース
PyPI への公開は GitHub Release をトリガーに、Trusted Publishing (OIDC) で自動実行される。 API トークンはリポジトリに置かない。
# 1. pyproject.toml の version を上げて main へマージする
# 2. その version と同じタグで Release を作る (v プレフィックス付き)
gh release create v0.1.0 --generate-notes
タグと pyproject.toml の version が食い違うとワークフローは公開前に落ちる。
__version__ は pyproject.toml から読むため、バージョンを書き換える箇所は pyproject.toml の 1 行だけ。
ライセンス
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ddd4py-0.2.0.tar.gz.
File metadata
- Download URL: ddd4py-0.2.0.tar.gz
- Upload date:
- Size: 60.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b6abd8ef4e9de80d432cb0f4c1512a81bb0125fc2664b2219414a2271d896cc4
|
|
| MD5 |
32d4721800a137210e65af929f47b9b9
|
|
| BLAKE2b-256 |
21225819abae4f0f7a9c0636f9ab25f6d777c81e6af6150fe2a90b596d2956f3
|
Provenance
The following attestation bundles were made for ddd4py-0.2.0.tar.gz:
Publisher:
publish.yml on theindiehacker/ddd4py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ddd4py-0.2.0.tar.gz -
Subject digest:
b6abd8ef4e9de80d432cb0f4c1512a81bb0125fc2664b2219414a2271d896cc4 - Sigstore transparency entry: 2693250739
- Sigstore integration time:
-
Permalink:
theindiehacker/ddd4py@d075ac6aa3fe732f7eb15e731c7667e3d92a9e1e -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/theindiehacker
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d075ac6aa3fe732f7eb15e731c7667e3d92a9e1e -
Trigger Event:
release
-
Statement type:
File details
Details for the file ddd4py-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ddd4py-0.2.0-py3-none-any.whl
- Upload date:
- Size: 43.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a7ad4ed1449786bdb48c3a6e8e058a03185c03e48e2832bfe8cc296e06ac2ee
|
|
| MD5 |
3eff99803483c4a5ac138911047c7cf3
|
|
| BLAKE2b-256 |
4ff92a7c43605a724c71176a38908e1e6a3c9b339fc34a04dac35942f4656073
|
Provenance
The following attestation bundles were made for ddd4py-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on theindiehacker/ddd4py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ddd4py-0.2.0-py3-none-any.whl -
Subject digest:
5a7ad4ed1449786bdb48c3a6e8e058a03185c03e48e2832bfe8cc296e06ac2ee - Sigstore transparency entry: 2693250791
- Sigstore integration time:
-
Permalink:
theindiehacker/ddd4py@d075ac6aa3fe732f7eb15e731c7667e3d92a9e1e -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/theindiehacker
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d075ac6aa3fe732f7eb15e731c7667e3d92a9e1e -
Trigger Event:
release
-
Statement type: