[](https://coveralls.io/github/vmorugin/pyddd?branch=master) [](https://pypi.org/project/pyddd) [](https://pypi.org/project/pyddd) [](https://pypi.org/project/pyddd) []()
# PyDDD - Domain-Driven Design для Python
PyDDD - это библиотека для реализации Domain-Driven Design (DDD) паттернов в Python с поддержкой как синхронного,
так и асинхронного выполнения.
## Основные возможности
- 🏗️ **Domain Entities & Aggregates** - Создание доменных сущностей и агрегатов
- 📨 **Commands & Events** - Система команд и событий
- 🔧 **Dependency Injection** - Встроенная система внедрения зависимостей
- 🔄 **Event Sourcing** - Поддержка событийного подхода
- ⚡ **Async Support** - Полная поддержка асинхронного выполнения
- 🎯 **Event Filtering** - Условная обработка событий
- 🗃️ **Unit of Work** - Паттерн Unit of Work для управления транзакциями
## Установка
```bash
pip install pyddd
```
## Быстрый старт
### Базовый пример
```python
import abc
from pyddd.application import Module,
Application
from pyddd.domain import DomainCommand,
DomainEvent
from pyddd.domain.entity import RootEntity
class CreatePet(DomainCommand, domain="pet"):
name: str
class PetCreated(DomainEvent, domain="pet"):
pet_id: str
name: str
class Pet(RootEntity):
name: str
@classmethod
def create(cls, name: str):
pet = cls(name=name)
pet.register_event(PetCreated(name=name, pet_id=str(pet.__reference__)))
return pet
pet_module = Module("pet")
class IPetRepository(abc.ABC):
@abc.abstractmethod
def save(self, pet: Pet):
...
@abc.abstractmethod
def get(self, pet_id: str) -> Pet:
...
@pet_module.register
def create_pet(cmd: CreatePet, repository: IPetRepository):
pet = Pet.create(cmd.name)
repository.save(pet)
return pet.__reference__
app = Application()
app.include(pet_module)
app.set_defaults("pet", repository=InMemoryPetRepository({}))
app.run()
# Выполняем команду
pet_id = app.handle(CreatePet(name="Fluffy"))
```
## Основные концепции
### Доменные команды
Команды представляют намерения изменить состояние системы:
```python
class CreateProduct(DomainCommand, domain="product"):
sku: str
price: int
class UpdatePrice(DomainCommand, domain="product"):
product_id: str
new_price: int
```
### Доменные события
События уведомляют о произошедших изменениях. Имя события состоит из действия в прошедшем времени.
```python
class ProductCreated(DomainEvent, domain="product"):
reference: str
price: int
class PriceUpdated(DomainEvent, domain="product"):
product_id: str
old_price: int
new_price: int
```
### Агрегаты
Агрегаты инкапсулируют бизнес-логику и генерируют события:
```python
class Product(RootEntity):
sku: str
price: int
@classmethod
def create(cls, sku: str, price: int):
product = cls(sku=sku, price=price)
product.register_event(ProductCreated(
reference=str(product.__reference__),
price=price
))
return product
def update_price(self, new_price: int):
old_price = self.price
self.price = new_price
self.register_event(PriceUpdated(
product_id=str(self.__reference__),
old_price=old_price,
new_price=new_price
))
```
### Подписка на события
Вы можете подписываться на события. Все события будут преобразованы в команду.
```python
greet_module = Module("greet")
@greet_module.subscribe("pet.PetCreated")
@greet_module.register
def register_pet(cmd: CreateGreetLogCommand, repository: IPetGreetRepo):
journal = PerGreetJournal.create(pet_id=cmd.pet_id, pet_name=cmd.name)
repository.save(journal)
return journal.__reference__
```
### Условная обработка событий
Можно добавлять условия для обработки событий:
```python
from pyddd.application import Equal, Not
from pyddd.domain.command import DomainCommand
class HandleFreeProductCommand(DomainCommand, domain='statistics'):
reference: str
class HandlePaidProductCommand(DomainCommand, domain='statistics'):
reference: str
amount: int
@module.subscribe('product.ProductCreated', condition=Equal(price=0))
@module.register
def handle_free_product(cmd: HandleFreeProductCommand):
print(f"Free product created: {cmd.reference} without price")
@module.subscribe('product.ProductCreated', condition=Not(Equal(price=0)))
@module.register
def handle_paid_product(cmd: HandlePaidProductCommand):
print(f"Paid product created: {cmd.reference} with price {cmd.price}")
```
## Асинхронная поддержка
PyDDD полностью поддерживает асинхронное выполнение:
```python
from pyddd.application import AsyncExecutor
# Асинхронные обработчики
@pet_module.register
async def create_pet_async(cmd: CreatePet, repository: IPetRepository):
pet = Pet.create(cmd.name)
await repository.save(pet)
return pet.__reference__
# Настройка асинхронного приложения
app = Application(executor=AsyncExecutor())
app.include(pet_module)
await app.run_async()
pet_id = await app.handle(CreatePet(name="Fluffy"))
```
### Конвертеры событий
Для преобразования данных событий в команды:
```python
@greet_module.subscribe(
"pet.PetCreated",
converter=lambda x: {"pet_id": x["reference"], "name": x["name"]}
)
@greet_module.register
async def record_log(cmd: CreateGreetLogCommand):
print("Created greet log for pet:", cmd.pet_id)
```
## Unit of Work
Для управления транзакциями используется паттерн Unit of Work:
```python
from pyddd.infrastructure.persistence.abstractions import IUnitOfWorkBuilder
@module.register
async def create_workspace(
cmd: CreateWorkspace,
uow_builder: IUnitOfWorkBuilder[IWorkspaceRepoFactory]
) -> WorkspaceId:
with uow_builder() as uow:
tenant_repo = uow.repository.tenant()
project_repo = uow.repository.project()
workspace_repo = uow.repository.workspace()
tenant = tenant_repo.create(name=cmd.tenant_name)
project = project_repo.create(name=cmd.project_name, tenant_id=tenant.__reference__)
workspace = workspace_repo.create(tenant=tenant, project=project)
uow.apply() # Применяем все изменения в рамках транзакции
return workspace.__reference__
```
## Внедрение зависимостей
PyDDD автоматически внедряет зависимости в обработчики команд:
```python
# Настройка зависимостей по умолчанию
app.set_defaults("product",
repository=ProductRepository(),
price_adapter=PriceAdapter(),
notification_service=EmailService()
)
# Автоматическое внедрение в обработчик
@module.register
async def update_product_price(
cmd: UpdateProductPrice,
repository: IProductRepository, # Автоматически внедряется
price_adapter: IPriceAdapter, # Автоматически внедряется
):
product = await repository.get(cmd.product_id)
new_price = await price_adapter.get_current_price(product.sku)
product.update_price(new_price)
await repository.save(product)
```
## Лучшие практики
### 1. Разделение доменов
Организуйте код по доменам и создавайте отдельные модули:
```python
# Домен продуктов
product_module = Module("product")
# Домен заказов
order_module = Module("order")
# Домен клиентов
customer_module = Module("customer")
```
### 2. Слабая связанность
Используйте события для связи между доменами вместо прямых вызовов:
```python
# Вместо прямого вызова
def create_order(cmd: CreateOrder):
order = Order.create(...)
# НЕ ДЕЛАЙТЕ ТАК: customer_service.notify_order_created(order)
# Используйте события
def create_order(cmd: CreateOrder):
order = Order.create(...)
order.register_event(OrderCreated(order_id=str(order.__reference__)))
```
### 3. Небольшие транзакции
Избегайте изменения нескольких агрегатов в одной транзакции:
```python
# Предпочтительно - одна команда, один агрегат
@module.register
async def create_product(cmd: CreateProduct, repository: IProductRepository):
product = Product.create(cmd.sku, cmd.price)
await repository.save(product)
return product.__reference__
```
## Примеры использования
### Система управления товарами
```python
# Команды
class CreateProduct(DomainCommand, domain="product"):
sku: str
price: int
class UpdateStock(DomainCommand, domain="product"):
product_id: str
quantity: int
# События
class ProductCreated(DomainEvent, domain="product"):
product_id: str
sku: str
class StockUpdated(DomainEvent, domain="product"):
product_id: str
new_quantity: int
# Агрегат
class Product(RootEntity):
sku: str
price: int
stock: int
@classmethod
def create(cls, sku: str, price: int):
product = cls(sku=sku, price=price, stock=0)
product.register_event(ProductCreated(
product_id=str(product.__reference__),
sku=sku
))
return product
def update_stock(self, quantity: int):
self.stock = quantity
self.register_event(StockUpdated(
product_id=str(self.__reference__),
new_quantity=quantity
))
```
### Система уведомлений
```python
notification_module = Module("notification")
@notification_module.subscribe("product.ProductCreated")
@notification_module.register
async def notify_product_created(cmd: NotifyProductCreatedCommand, email_service: IEmailService):
await email_service.send_notification(
subject="Новый товар создан",
message=f"Товар {cmd.sku} добавлен в каталог"
)
```
## Заключение
PyDDD предоставляет мощные инструменты для реализации чистой архитектуры и Domain-Driven Design паттернов в Python.
Библиотека поддерживает как синхронное, так и асинхронное выполнение, обеспечивая гибкость в различных сценариях
использования.
Для получения дополнительной информации и примеров обратитесь к тестам в директории `tests/unit/examples/`.
Raw data
{
"_id": null,
"home_page": null,
"name": "pyddd",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "cqrs, cqs, ddd, ddd python, domain driven design, domain-driven design, event driven",
"author": null,
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/32/e6/252ff4d410ef647a730825baff32e1cd9deaba69e92a98daa340b2f5cbcf/pyddd-0.20.0.tar.gz",
"platform": null,
"description": "[](https://coveralls.io/github/vmorugin/pyddd?branch=master) [](https://pypi.org/project/pyddd) [](https://pypi.org/project/pyddd) [](https://pypi.org/project/pyddd) []()\n\n# PyDDD - Domain-Driven Design \u0434\u043b\u044f Python\n\nPyDDD - \u044d\u0442\u043e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 Domain-Driven Design (DDD) \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432 \u0432 Python \u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u043a\u0430\u043a \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0433\u043e, \n\u0442\u0430\u043a \u0438 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f.\n\n## \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438\n\n- \ud83c\udfd7\ufe0f **Domain Entities & Aggregates** - \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u044b\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u0438 \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0432\n- \ud83d\udce8 **Commands & Events** - \u0421\u0438\u0441\u0442\u0435\u043c\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 \u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439\n- \ud83d\udd27 **Dependency Injection** - \u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439\n- \ud83d\udd04 **Event Sourcing** - \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430\n- \u26a1 **Async Support** - \u041f\u043e\u043b\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f\n- \ud83c\udfaf **Event Filtering** - \u0423\u0441\u043b\u043e\u0432\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439\n- \ud83d\uddc3\ufe0f **Unit of Work** - \u041f\u0430\u0442\u0442\u0435\u0440\u043d Unit of Work \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u043c\u0438\n\n## \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430\n\n```bash\npip install pyddd\n```\n\n## \u0411\u044b\u0441\u0442\u0440\u044b\u0439 \u0441\u0442\u0430\u0440\u0442\n\n### \u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440\n\n```python\nimport abc\nfrom pyddd.application import Module,\n Application\nfrom pyddd.domain import DomainCommand,\n DomainEvent\nfrom pyddd.domain.entity import RootEntity\n\n\nclass CreatePet(DomainCommand, domain=\"pet\"):\n name: str\n\n\nclass PetCreated(DomainEvent, domain=\"pet\"):\n pet_id: str\n name: str\n\n\nclass Pet(RootEntity):\n name: str\n\n @classmethod\n def create(cls, name: str):\n pet = cls(name=name)\n pet.register_event(PetCreated(name=name, pet_id=str(pet.__reference__)))\n return pet\n\n\npet_module = Module(\"pet\")\n\n\nclass IPetRepository(abc.ABC):\n @abc.abstractmethod\n def save(self, pet: Pet):\n ...\n \n @abc.abstractmethod\n def get(self, pet_id: str) -> Pet:\n ...\n\n\n@pet_module.register\ndef create_pet(cmd: CreatePet, repository: IPetRepository):\n pet = Pet.create(cmd.name)\n repository.save(pet)\n return pet.__reference__\n\n\napp = Application()\napp.include(pet_module)\napp.set_defaults(\"pet\", repository=InMemoryPetRepository({}))\napp.run()\n\n# \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443\npet_id = app.handle(CreatePet(name=\"Fluffy\"))\n```\n\n## \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u0438\n\n### \u0414\u043e\u043c\u0435\u043d\u043d\u044b\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b\n\n\u041a\u043e\u043c\u0430\u043d\u0434\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b:\n\n```python\nclass CreateProduct(DomainCommand, domain=\"product\"):\n sku: str\n price: int\n\nclass UpdatePrice(DomainCommand, domain=\"product\"):\n product_id: str\n new_price: int\n```\n\n### \u0414\u043e\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\n\n\u0421\u043e\u0431\u044b\u0442\u0438\u044f \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u044f\u044e\u0442 \u043e \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u0435\u0434\u0448\u0438\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0445. \u0418\u043c\u044f \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0432 \u043f\u0440\u043e\u0448\u0435\u0434\u0448\u0435\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438.\n\n```python\nclass ProductCreated(DomainEvent, domain=\"product\"):\n reference: str\n price: int\n\nclass PriceUpdated(DomainEvent, domain=\"product\"):\n product_id: str\n old_price: int\n new_price: int\n```\n\n### \u0410\u0433\u0440\u0435\u0433\u0430\u0442\u044b\n\n\u0410\u0433\u0440\u0435\u0433\u0430\u0442\u044b \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u0443\u044e\u0442 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0443 \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u044f:\n\n```python\nclass Product(RootEntity):\n sku: str\n price: int\n\n @classmethod\n def create(cls, sku: str, price: int):\n product = cls(sku=sku, price=price)\n product.register_event(ProductCreated(\n reference=str(product.__reference__), \n price=price\n ))\n return product\n\n def update_price(self, new_price: int):\n old_price = self.price\n self.price = new_price\n self.register_event(PriceUpdated(\n product_id=str(self.__reference__),\n old_price=old_price,\n new_price=new_price\n ))\n```\n\n### \u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\n\n\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f. \u0412\u0441\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u044b \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0443. \n\n```python\ngreet_module = Module(\"greet\")\n\n@greet_module.subscribe(\"pet.PetCreated\")\n@greet_module.register\ndef register_pet(cmd: CreateGreetLogCommand, repository: IPetGreetRepo):\n journal = PerGreetJournal.create(pet_id=cmd.pet_id, pet_name=cmd.name)\n repository.save(journal)\n return journal.__reference__\n```\n\n### \u0423\u0441\u043b\u043e\u0432\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439\n\n\u041c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439:\n\n```python\nfrom pyddd.application import Equal, Not\nfrom pyddd.domain.command import DomainCommand\n\n\nclass HandleFreeProductCommand(DomainCommand, domain='statistics'):\n reference: str\n\n \nclass HandlePaidProductCommand(DomainCommand, domain='statistics'):\n reference: str\n amount: int\n\n\n@module.subscribe('product.ProductCreated', condition=Equal(price=0))\n@module.register\ndef handle_free_product(cmd: HandleFreeProductCommand):\n print(f\"Free product created: {cmd.reference} without price\")\n\n\n@module.subscribe('product.ProductCreated', condition=Not(Equal(price=0)))\n@module.register\ndef handle_paid_product(cmd: HandlePaidProductCommand):\n print(f\"Paid product created: {cmd.reference} with price {cmd.price}\")\n```\n\n## \u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430\n\nPyDDD \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435:\n\n```python\nfrom pyddd.application import AsyncExecutor\n\n# \u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\n@pet_module.register\nasync def create_pet_async(cmd: CreatePet, repository: IPetRepository):\n pet = Pet.create(cmd.name)\n await repository.save(pet)\n return pet.__reference__\n\n# \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\napp = Application(executor=AsyncExecutor())\napp.include(pet_module)\n\nawait app.run_async()\npet_id = await app.handle(CreatePet(name=\"Fluffy\"))\n```\n\n### \u041a\u043e\u043d\u0432\u0435\u0440\u0442\u0435\u0440\u044b \u0441\u043e\u0431\u044b\u0442\u0438\u0439\n\n\u0414\u043b\u044f \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u044b:\n\n```python\n@greet_module.subscribe(\n \"pet.PetCreated\", \n converter=lambda x: {\"pet_id\": x[\"reference\"], \"name\": x[\"name\"]}\n)\n@greet_module.register\nasync def record_log(cmd: CreateGreetLogCommand):\n print(\"Created greet log for pet:\", cmd.pet_id)\n \n```\n\n## Unit of Work\n\n\u0414\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u043c\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0430\u0442\u0442\u0435\u0440\u043d Unit of Work:\n\n```python\nfrom pyddd.infrastructure.persistence.abstractions import IUnitOfWorkBuilder\n\n@module.register\nasync def create_workspace(\n cmd: CreateWorkspace, \n uow_builder: IUnitOfWorkBuilder[IWorkspaceRepoFactory]\n) -> WorkspaceId:\n with uow_builder() as uow:\n tenant_repo = uow.repository.tenant()\n project_repo = uow.repository.project()\n workspace_repo = uow.repository.workspace()\n \n tenant = tenant_repo.create(name=cmd.tenant_name)\n project = project_repo.create(name=cmd.project_name, tenant_id=tenant.__reference__)\n workspace = workspace_repo.create(tenant=tenant, project=project)\n \n uow.apply() # \u041f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u043c \u0432\u0441\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438\n \n return workspace.__reference__\n```\n\n## \u0412\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439\n\nPyDDD \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u043d\u0435\u0434\u0440\u044f\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u043a\u043e\u043c\u0430\u043d\u0434:\n\n```python\n# \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e\napp.set_defaults(\"product\", \n repository=ProductRepository(),\n price_adapter=PriceAdapter(),\n notification_service=EmailService()\n)\n\n# \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u0435 \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\n@module.register\nasync def update_product_price(\n cmd: UpdateProductPrice,\n repository: IProductRepository, # \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u043d\u0435\u0434\u0440\u044f\u0435\u0442\u0441\u044f\n price_adapter: IPriceAdapter, # \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u043d\u0435\u0434\u0440\u044f\u0435\u0442\u0441\u044f\n):\n product = await repository.get(cmd.product_id)\n new_price = await price_adapter.get_current_price(product.sku)\n product.update_price(new_price)\n await repository.save(product)\n```\n\n## \u041b\u0443\u0447\u0448\u0438\u0435 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0438\n\n### 1. \u0420\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u043e\u0432\n\n\u041e\u0440\u0433\u0430\u043d\u0438\u0437\u0443\u0439\u0442\u0435 \u043a\u043e\u0434 \u043f\u043e \u0434\u043e\u043c\u0435\u043d\u0430\u043c \u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u043e\u0434\u0443\u043b\u0438:\n\n```python\n# \u0414\u043e\u043c\u0435\u043d \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432\nproduct_module = Module(\"product\")\n\n# \u0414\u043e\u043c\u0435\u043d \u0437\u0430\u043a\u0430\u0437\u043e\u0432 \norder_module = Module(\"order\")\n\n# \u0414\u043e\u043c\u0435\u043d \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432\ncustomer_module = Module(\"customer\")\n```\n\n### 2. \u0421\u043b\u0430\u0431\u0430\u044f \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0441\u0442\u044c\n\n\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0434\u043b\u044f \u0441\u0432\u044f\u0437\u0438 \u043c\u0435\u0436\u0434\u0443 \u0434\u043e\u043c\u0435\u043d\u0430\u043c\u0438 \u0432\u043c\u0435\u0441\u0442\u043e \u043f\u0440\u044f\u043c\u044b\u0445 \u0432\u044b\u0437\u043e\u0432\u043e\u0432:\n\n```python\n# \u0412\u043c\u0435\u0441\u0442\u043e \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430\ndef create_order(cmd: CreateOrder):\n order = Order.create(...)\n # \u041d\u0415 \u0414\u0415\u041b\u0410\u0419\u0422\u0415 \u0422\u0410\u041a: customer_service.notify_order_created(order)\n\n\n# \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\ndef create_order(cmd: CreateOrder):\n order = Order.create(...)\n order.register_event(OrderCreated(order_id=str(order.__reference__)))\n```\n\n### 3. \u041d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438\n\n\u0418\u0437\u0431\u0435\u0433\u0430\u0439\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0432 \u0432 \u043e\u0434\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438:\n\n```python\n# \u041f\u0440\u0435\u0434\u043f\u043e\u0447\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u043e - \u043e\u0434\u043d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430, \u043e\u0434\u0438\u043d \u0430\u0433\u0440\u0435\u0433\u0430\u0442\n@module.register\nasync def create_product(cmd: CreateProduct, repository: IProductRepository):\n product = Product.create(cmd.sku, cmd.price)\n await repository.save(product)\n return product.__reference__\n```\n\n## \u041f\u0440\u0438\u043c\u0435\u0440\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\n\n### \u0421\u0438\u0441\u0442\u0435\u043c\u0430 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u043e\u0432\u0430\u0440\u0430\u043c\u0438\n\n```python\n# \u041a\u043e\u043c\u0430\u043d\u0434\u044b\nclass CreateProduct(DomainCommand, domain=\"product\"):\n sku: str\n price: int\n\nclass UpdateStock(DomainCommand, domain=\"product\"):\n product_id: str\n quantity: int\n\n# \u0421\u043e\u0431\u044b\u0442\u0438\u044f\nclass ProductCreated(DomainEvent, domain=\"product\"):\n product_id: str\n sku: str\n\nclass StockUpdated(DomainEvent, domain=\"product\"):\n product_id: str\n new_quantity: int\n\n# \u0410\u0433\u0440\u0435\u0433\u0430\u0442\nclass Product(RootEntity):\n sku: str\n price: int\n stock: int\n\n @classmethod\n def create(cls, sku: str, price: int):\n product = cls(sku=sku, price=price, stock=0)\n product.register_event(ProductCreated(\n product_id=str(product.__reference__), \n sku=sku\n ))\n return product\n\n def update_stock(self, quantity: int):\n self.stock = quantity\n self.register_event(StockUpdated(\n product_id=str(self.__reference__),\n new_quantity=quantity\n ))\n```\n\n### \u0421\u0438\u0441\u0442\u0435\u043c\u0430 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439\n\n```python\nnotification_module = Module(\"notification\")\n\n@notification_module.subscribe(\"product.ProductCreated\")\n@notification_module.register\nasync def notify_product_created(cmd: NotifyProductCreatedCommand, email_service: IEmailService):\n await email_service.send_notification(\n subject=\"\u041d\u043e\u0432\u044b\u0439 \u0442\u043e\u0432\u0430\u0440 \u0441\u043e\u0437\u0434\u0430\u043d\",\n message=f\"\u0422\u043e\u0432\u0430\u0440 {cmd.sku} \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\"\n )\n```\n\n## \u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\n\nPyDDD \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043c\u043e\u0449\u043d\u044b\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0447\u0438\u0441\u0442\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u0438 Domain-Driven Design \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432 \u0432 Python. \n\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043a\u0430\u043a \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0435, \u0442\u0430\u043a \u0438 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435, \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u044f \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c \u0432 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f\u0445 \n\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f.\n\n\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u043a \u0442\u0435\u0441\u0442\u0430\u043c \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 `tests/unit/examples/`.\n",
"bugtrack_url": null,
"license": null,
"summary": "Domain-Driven Design for Python",
"version": "0.20.0",
"project_urls": {
"Homepage": "https://github.com/vmorugin/pyddd",
"Issues": "https://github.com/vmorugin/pyddd/issues"
},
"split_keywords": [
"cqrs",
" cqs",
" ddd",
" ddd python",
" domain driven design",
" domain-driven design",
" event driven"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "2d765686daea6183e1e97e71277c484af9bf29904c73e928eff093a1ced41f1d",
"md5": "326ed64a0744de57a4b768347deac1c8",
"sha256": "864eb8354a7ef4992cf49852c96ff90341d324bb40c5ad40ebf7184bc93fe2ac"
},
"downloads": -1,
"filename": "pyddd-0.20.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "326ed64a0744de57a4b768347deac1c8",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 55382,
"upload_time": "2025-07-18T19:06:44",
"upload_time_iso_8601": "2025-07-18T19:06:44.617959Z",
"url": "https://files.pythonhosted.org/packages/2d/76/5686daea6183e1e97e71277c484af9bf29904c73e928eff093a1ced41f1d/pyddd-0.20.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "32e6252ff4d410ef647a730825baff32e1cd9deaba69e92a98daa340b2f5cbcf",
"md5": "e33ae581130f2702bd481afe7b7640b6",
"sha256": "163fc34d08227a9fe72442f6827f78a4560d42acde68968ad71cff3368f5aa6b"
},
"downloads": -1,
"filename": "pyddd-0.20.0.tar.gz",
"has_sig": false,
"md5_digest": "e33ae581130f2702bd481afe7b7640b6",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 30939,
"upload_time": "2025-07-18T19:06:46",
"upload_time_iso_8601": "2025-07-18T19:06:46.459402Z",
"url": "https://files.pythonhosted.org/packages/32/e6/252ff4d410ef647a730825baff32e1cd9deaba69e92a98daa340b2f5cbcf/pyddd-0.20.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-18 19:06:46",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "vmorugin",
"github_project": "pyddd",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pyddd"
}