pyddd


Namepyddd JSON
Version 0.20.0 PyPI version JSON
download
home_pageNone
SummaryDomain-Driven Design for Python
upload_time2025-07-18 19:06:46
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseNone
keywords cqrs cqs ddd ddd python domain driven design domain-driven design event driven
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            [![Coverage Status](https://coveralls.io/repos/github/vmorugin/pyddd/badge.svg?branch=master)](https://coveralls.io/github/vmorugin/pyddd?branch=master) [![PyPI - License](https://img.shields.io/pypi/l/pyddd)](https://pypi.org/project/pyddd) [![PyPI](https://img.shields.io/pypi/v/pyddd)](https://pypi.org/project/pyddd) [![PyPI](https://img.shields.io/pypi/pyversions/pyddd)](https://pypi.org/project/pyddd) [![Mypy](http://www.mypy-lang.org/static/mypy_badge.svg)]()

# 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": "[![Coverage Status](https://coveralls.io/repos/github/vmorugin/pyddd/badge.svg?branch=master)](https://coveralls.io/github/vmorugin/pyddd?branch=master) [![PyPI - License](https://img.shields.io/pypi/l/pyddd)](https://pypi.org/project/pyddd) [![PyPI](https://img.shields.io/pypi/v/pyddd)](https://pypi.org/project/pyddd) [![PyPI](https://img.shields.io/pypi/pyversions/pyddd)](https://pypi.org/project/pyddd) [![Mypy](http://www.mypy-lang.org/static/mypy_badge.svg)]()\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"
}
        
Elapsed time: 1.00746s