explicit-python


Nameexplicit-python JSON
Version 2.2.1 PyPI version JSON
download
home_pageNone
SummaryНабор компонентов для построения многослойной архитектуры
upload_time2024-10-02 09:37:58
maintainerNone
docs_urlNone
authorBARS Group
requires_pythonNone
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Explicit
## Набор компонентов для построения явной (Explicit) многослойной архитектуры

[<img alt="explicit architecture" src="doc/100-explicit-architecture-svg.png" width=700>][explicit architecture]

## Решаемые проблемы
- Образование [BBoM] в приложении
- Смешивание [бизнес-логики][domain-model], [логики выборки данных][service-layer], запросов UI
- [Зацепление][coupling] модулей приложения
- [Дублирование логики][dry]

## Основные принципы построения
- [CQRS]:
  - Поток команд (Command), модифицирующий состояние приложения (БД) идёт через [слой предметной области][domain-model]
  - Поток запросов (Query) не меняет состояние и не проходит через предметную область
- В основе лежит [Явная архитектура][explicit architecture]: слой предметной области не зависит от сервисного слоя или конкретных реализаций ORM, СУБД, API сторонних служб и т.д.; используются порты и адаптеры абстрагируют вызовы к API; ядро приложения отделено от взаимодействующих с ним API, GUI, webservices.

## Реализуемые компоненты
### Команда (command)
Инкапсулирует передачу параметров запроса обработчику команды, стандартизирует передачу параметров:

```python
  # Application core
  class RegisterStudent(Command):
    last_name: str
    first_name: str
```

### Обработчик команды (command handler)
Принимает команду и выполняет действия над переданными в команде данными.
```python
  # Application core
  def register_student(command: RegisterStudent):  # handler
      student = Student(last_name=command.last_name, first_name=command.first_name)
      repository.add(student)
```

### Шина (bus, messagebus)
Обеспечивает доставку команды соответствующему обработчику, уменьшает зацепление между модулями и слоями приложения.
```python
  from core import bus

  # Django Rest Framework
  class StudentViewSet(ModelViewSet):
  
    def perform_create(self, serializer):
        command = RegisterStudent(**serializer.validated_data)
        bus.handle(command)
    
  # Spyne webservices
  @rpc(
    StudentData,
  )
  def RegisterStudent(ctx, data: 'StudentData'):
    command = RegisterStudent(last_name=data.last_name, first_name=data.first_name)
    bus.handle(command)
  
```

### Unit of Work

[Unit of Work]:
- Единица работы, логическая бизнес-транзация
- Обеспечивает атомарность выполняемых операций
- Предоставляет доступ к репозиториям приложения
- Устраняет зависимость логики от конкретного фреймворка или технологии БД

```python
def register_student(command: RegisterStudent, uow: 'UnitOfWork'):
    with uow.wrap():
        uow.users.add(user)
        uow.persons.add(person)
        uow.students.add(student)
```

### Репозиторий (Repository)
[Repository]:
- Является адаптером к СУБД
- Выполняет роль хранилища объектов предметной области в соответствующем слое
- Инкапсулирует логику выборки данных
- Устраняет зависимость логики от конкретного фреймворка или технологии БД

```python
class Repository:
    def get_object_by_id(self, identifier: int) -> Student:
        try:
            dbinstance = DBStudent.objects.get(pk=identifier)
            return self._to_domain(dbinstance)
        except ObjectDoesNotExist as e:
            raise StudentNotFound() from e

    def get_by_persons(self, *persons: 'Person') -> Generator[Student, None, None]:
        query = DBStudent.objects.filter(person_id__in=(person.id for person in persons))
        for dbinstance in query.iterator():
            yield self._to_domain(dbinstance)
```

### Фабрика (Factory)
Инкапсулирует логику создания нового объекта предметной области по известным параметрам.
```python
class Factory(AbstractDomainFactory):
    def create(self, data: StudentDTO) -> Student:
        return Student(
            person_id=data.person.id,
            unit_id=data.unit.id,
        )
```

### Объект передачи данных (DTO, Data Transfer Object)
[DTO] используется при передаче большого количества параметров между объектами приложения и для стандартизации передачи данных и контрактов.

```python

def create_person(data: 'PersonDTO'):
    person = factory.create(data)

def update_person(person, data: 'PersonDTO'):
    domain.update_person(data)

```

## Обработка входящего запроса и события

### 1. Обработка запроса

   1.1 Запрос приходит в контроллер (View, Spyne @rpc и т.д.)

   1.2 Контроллер извлекает параметры запроса, валидирует их, формирует команду с параметрами запроса

   1.3 Сформированная команда направляется в шину ядра приложения

   ```python
   def post(self, request, *args, **kwargs):
     serializer = self.get_serializer(data=request.data)
     serializer.is_valid(raise_exception=True)
     last_name = serializer.validated_data['last_name']
     first_name = serializer.validated_data['first_name']
   
     command = RegisterStudent(
         last_name=last_name,
         first_name=first_name,
     )
     bus.handle(command)
     return JsonResponse(data={'registered': True})
   ```

   1.4 Шина по типу команды определяет соответствующий обработчик и передает ему команду

   ```python
   class CommandBusMixin(ABC):
   
      _command_handlers: Dict[Type[Command], Callable] = {}
      
      def handle_command(self, command: Command):
         """Передача запроса обработчику типа данного запроса."""
         return self._command_handlers[type(command)](command)
   ```

   1.5 Обработчик выполняет требуемые действия, инициирует событие, соответствующее результату обработки, возвращает результат
 
   ```python
   def register_student(
      command: RegisterStudent, uow: 'UnitOfWork'
   ) -> 'Student':
      with uow.wrap():
          student: Student = domain_register_student(
              StudentDTO(**command.dict())
          )
      uow.add_event(events.StudentCreated(
          **command.dict(), **asdict(student)
      ))
      return student
   ```

   1.6 Событие попадает в шину ядра

   1.7 Ядро определяет список обработчиков, соответствующих типу события, передает им инстанс события

   ```python
   class EventBusMixin(ABC):
   
      _event_handlers: Dict[Type[Event], List[EventHandler]] = {}
   
      def handle_event(self, event: Event):
          """Передача события списку обработчиков типа данного события."""
          consume(
              handler(event)
              for handler in self._event_handlers[type(event)]
          )
   ```

   1.8 Обработчик события выполняет требуемые действия

   1.9 Обработчик события может передать событие на внешнюю шину, воспользовавшись соответствующим адаптером шины

   ```python
   def on_student_created(
      event: events.StudentCreated, adapter: 'AbstractAdapter'
   ):
      adapter.publish(
          'edu.students.created',
          json.dumps(asdict(event), default=encoder)
      )
   ```

### 2. Обработка внешнего события

   2.1 Подписчик получает событие из внешней шины

   2.2 Подписчик десериализует внешнее событие и инстанцирует внутреннее

   ```python
   def bootstrap():
      from students.core.adapters.messaging import adapter
      from students.core.domain import events
      
      TOPIC_EVENTS = {
         'edu.persons.created': events.PersonCreated,
      }
      
      for message in adapter.subscribe(*TOPIC_EVENTS):
         event = TOPIC_EVENTS[message.topic()](
             **json.loads(message.value())
         )
         bus.handle(event)

   ```

   2.3 Внутренее событие попадает в шину ядра

   2.4 Ядро определяет список обработчиков, соответствующих типу события, передает им инстанс события

   2.5 Обработчик события выполняет требуемые действия

   2.6 Обработчик события может инстанцировать новое событие

   ```python
   def on_person_created(
      event: events.PersonCreated, uow: 'UnitOfWork'
   ) -> None:
      with uow.wrap():
          transaction_id = event.meta.transaction_id
   
          saga = uow.sagas.get_object_by_uuid(transaction_id)
          student = uow.students.get_object_by_id(saga.student_id)
   
          student.person_id = event.id
          uow.students.update(student)
   
          uow.add_event(events.StudentCreated(
              **command.dict(), **asdict(student)
          ))
   ```


## Минимальный пример реализации
Можно увидеть в тестовом приложении src/testapp.

## Запуск тестов
```sh
$ tox
```


[//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax)

   [bbom]: <https://ru.wikipedia.org/wiki/Большой_комок_грязи>
   [ddd]: <https://ru.wikipedia.org/wiki/Предметно-ориентированное_проектирование>
   [domain-model]: <http://design-pattern.ru/patterns/domain-model.html>
   [service-layer]: <http://design-pattern.ru/patterns/service-layer.html>
   [coupling]: <https://ru.wikipedia.org/wiki/Зацепление_(программирование)>
   [dry]: <https://ru.wikipedia.org/wiki/Don’t_repeat_yourself>
   [cqrs]: <https://martinfowler.com/bliki/CQRS.html>
   [explicit architecture]: <https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/>
   [unit of work]: <http://design-pattern.ru/patterns/unit-of-work.html>
   [repository]: <http://design-pattern.ru/patterns/repository.html>
   [dto]: <http://design-pattern.ru/patterns/data-transfer-object.html>

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "explicit-python",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": null,
    "author": "BARS Group",
    "author_email": "education_dev@bars-open.ru",
    "download_url": "https://files.pythonhosted.org/packages/74/4a/6b43ff6d9252f97fa7df22c848bee298ecb1049b381645f5b7a56de5a9a9/explicit_python-2.2.1.tar.gz",
    "platform": null,
    "description": "# Explicit\n## \u041d\u0430\u0431\u043e\u0440 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \u044f\u0432\u043d\u043e\u0439 (Explicit) \u043c\u043d\u043e\u0433\u043e\u0441\u043b\u043e\u0439\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b\n\n[<img alt=\"explicit architecture\" src=\"doc/100-explicit-architecture-svg.png\" width=700>][explicit architecture]\n\n## \u0420\u0435\u0448\u0430\u0435\u043c\u044b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b\n- \u041e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 [BBoM] \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438\n- \u0421\u043c\u0435\u0448\u0438\u0432\u0430\u043d\u0438\u0435 [\u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438][domain-model], [\u043b\u043e\u0433\u0438\u043a\u0438 \u0432\u044b\u0431\u043e\u0440\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445][service-layer], \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 UI\n- [\u0417\u0430\u0446\u0435\u043f\u043b\u0435\u043d\u0438\u0435][coupling] \u043c\u043e\u0434\u0443\u043b\u0435\u0439 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\n- [\u0414\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043b\u043e\u0433\u0438\u043a\u0438][dry]\n\n## \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f\n- [CQRS]:\n  - \u041f\u043e\u0442\u043e\u043a \u043a\u043e\u043c\u0430\u043d\u0434 (Command), \u043c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (\u0411\u0414) \u0438\u0434\u0451\u0442 \u0447\u0435\u0440\u0435\u0437 [\u0441\u043b\u043e\u0439 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u043d\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438][domain-model]\n  - \u041f\u043e\u0442\u043e\u043a \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 (Query) \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438 \u043d\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u043d\u0443\u044e \u043e\u0431\u043b\u0430\u0441\u0442\u044c\n- \u0412 \u043e\u0441\u043d\u043e\u0432\u0435 \u043b\u0435\u0436\u0438\u0442 [\u042f\u0432\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430][explicit architecture]: \u0441\u043b\u043e\u0439 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u043d\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f \u0438\u043b\u0438 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0445 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0439 ORM, \u0421\u0423\u0411\u0414, API \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0441\u043b\u0443\u0436\u0431 \u0438 \u0442.\u0434.; \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043f\u043e\u0440\u0442\u044b \u0438 \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u044b \u0430\u0431\u0441\u0442\u0440\u0430\u0433\u0438\u0440\u0443\u044e\u0442 \u0432\u044b\u0437\u043e\u0432\u044b \u043a API; \u044f\u0434\u0440\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043e\u0442\u0434\u0435\u043b\u0435\u043d\u043e \u043e\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0441 \u043d\u0438\u043c API, GUI, webservices.\n\n## \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b\n### \u041a\u043e\u043c\u0430\u043d\u0434\u0430 (command)\n\u0418\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0443 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u044b, \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0443 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:\n\n```python\n  # Application core\n  class RegisterStudent(Command):\n    last_name: str\n    first_name: str\n```\n\n### \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043a\u043e\u043c\u0430\u043d\u0434\u044b (command handler)\n\u041f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043d\u0430\u0434 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u0434\u0430\u043d\u043d\u044b\u043c\u0438.\n```python\n  # Application core\n  def register_student(command: RegisterStudent):  # handler\n      student = Student(last_name=command.last_name, first_name=command.first_name)\n      repository.add(student)\n```\n\n### \u0428\u0438\u043d\u0430 (bus, messagebus)\n\u041e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u043c\u0443 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0443, \u0443\u043c\u0435\u043d\u044c\u0448\u0430\u0435\u0442 \u0437\u0430\u0446\u0435\u043f\u043b\u0435\u043d\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438 \u0438 \u0441\u043b\u043e\u044f\u043c\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.\n```python\n  from core import bus\n\n  # Django Rest Framework\n  class StudentViewSet(ModelViewSet):\n  \n    def perform_create(self, serializer):\n        command = RegisterStudent(**serializer.validated_data)\n        bus.handle(command)\n    \n  # Spyne webservices\n  @rpc(\n    StudentData,\n  )\n  def RegisterStudent(ctx, data: 'StudentData'):\n    command = RegisterStudent(last_name=data.last_name, first_name=data.first_name)\n    bus.handle(command)\n  \n```\n\n### Unit of Work\n\n[Unit of Work]:\n- \u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0440\u0430\u0431\u043e\u0442\u044b, \u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0431\u0438\u0437\u043d\u0435\u0441-\u0442\u0440\u0430\u043d\u0437\u0430\u0446\u0438\u044f\n- \u041e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439\n- \u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\n- \u0423\u0441\u0442\u0440\u0430\u043d\u044f\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0438 \u043e\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u0438\u043b\u0438 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438 \u0411\u0414\n\n```python\ndef register_student(command: RegisterStudent, uow: 'UnitOfWork'):\n    with uow.wrap():\n        uow.users.add(user)\n        uow.persons.add(person)\n        uow.students.add(student)\n```\n\n### \u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 (Repository)\n[Repository]:\n- \u042f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u043e\u043c \u043a \u0421\u0423\u0411\u0414\n- \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0440\u043e\u043b\u044c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u043d\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u043c \u0441\u043b\u043e\u0435\n- \u0418\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u0443\u0435\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u0432\u044b\u0431\u043e\u0440\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445\n- \u0423\u0441\u0442\u0440\u0430\u043d\u044f\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0438 \u043e\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u0438\u043b\u0438 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438 \u0411\u0414\n\n```python\nclass Repository:\n    def get_object_by_id(self, identifier: int) -> Student:\n        try:\n            dbinstance = DBStudent.objects.get(pk=identifier)\n            return self._to_domain(dbinstance)\n        except ObjectDoesNotExist as e:\n            raise StudentNotFound() from e\n\n    def get_by_persons(self, *persons: 'Person') -> Generator[Student, None, None]:\n        query = DBStudent.objects.filter(person_id__in=(person.id for person in persons))\n        for dbinstance in query.iterator():\n            yield self._to_domain(dbinstance)\n```\n\n### \u0424\u0430\u0431\u0440\u0438\u043a\u0430 (Factory)\n\u0418\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u0443\u0435\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u043d\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u043f\u043e \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c.\n```python\nclass Factory(AbstractDomainFactory):\n    def create(self, data: StudentDTO) -> Student:\n        return Student(\n            person_id=data.person.id,\n            unit_id=data.unit.id,\n        )\n```\n\n### \u041e\u0431\u044a\u0435\u043a\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 (DTO, Data Transfer Object)\n[DTO] \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0435 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043c\u0435\u0436\u0434\u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0434\u043b\u044f \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u043e\u0432.\n\n```python\n\ndef create_person(data: 'PersonDTO'):\n    person = factory.create(data)\n\ndef update_person(person, data: 'PersonDTO'):\n    domain.update_person(data)\n\n```\n\n## \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\n\n### 1. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\n\n   1.1 \u0417\u0430\u043f\u0440\u043e\u0441 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 (View, Spyne @rpc \u0438 \u0442.\u0434.)\n\n   1.2 \u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u0430, \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u0442 \u0438\u0445, \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\n\n   1.3 \u0421\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u0448\u0438\u043d\u0443 \u044f\u0434\u0440\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\n\n   ```python\n   def post(self, request, *args, **kwargs):\n     serializer = self.get_serializer(data=request.data)\n     serializer.is_valid(raise_exception=True)\n     last_name = serializer.validated_data['last_name']\n     first_name = serializer.validated_data['first_name']\n   \n     command = RegisterStudent(\n         last_name=last_name,\n         first_name=first_name,\n     )\n     bus.handle(command)\n     return JsonResponse(data={'registered': True})\n   ```\n\n   1.4 \u0428\u0438\u043d\u0430 \u043f\u043e \u0442\u0438\u043f\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0435\u043c\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u0443\n\n   ```python\n   class CommandBusMixin(ABC):\n   \n      _command_handlers: Dict[Type[Command], Callable] = {}\n      \n      def handle_command(self, command: Command):\n         \"\"\"\u041f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0443 \u0442\u0438\u043f\u0430 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430.\"\"\"\n         return self._command_handlers[type(command)](command)\n   ```\n\n   1.5 \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f, \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0443 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\n \n   ```python\n   def register_student(\n      command: RegisterStudent, uow: 'UnitOfWork'\n   ) -> 'Student':\n      with uow.wrap():\n          student: Student = domain_register_student(\n              StudentDTO(**command.dict())\n          )\n      uow.add_event(events.StudentCreated(\n          **command.dict(), **asdict(student)\n      ))\n      return student\n   ```\n\n   1.6 \u0421\u043e\u0431\u044b\u0442\u0438\u0435 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u0432 \u0448\u0438\u043d\u0443 \u044f\u0434\u0440\u0430\n\n   1.7 \u042f\u0434\u0440\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0442\u0438\u043f\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0438\u043c \u0438\u043d\u0441\u0442\u0430\u043d\u0441 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\n\n   ```python\n   class EventBusMixin(ABC):\n   \n      _event_handlers: Dict[Type[Event], List[EventHandler]] = {}\n   \n      def handle_event(self, event: Event):\n          \"\"\"\u041f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0441\u043f\u0438\u0441\u043a\u0443 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u0442\u0438\u043f\u0430 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f.\"\"\"\n          consume(\n              handler(event)\n              for handler in self._event_handlers[type(event)]\n          )\n   ```\n\n   1.8 \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\n\n   1.9 \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043d\u0430 \u0432\u043d\u0435\u0448\u043d\u044e\u044e \u0448\u0438\u043d\u0443, \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0432\u0448\u0438\u0441\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u043c \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u043e\u043c \u0448\u0438\u043d\u044b\n\n   ```python\n   def on_student_created(\n      event: events.StudentCreated, adapter: 'AbstractAdapter'\n   ):\n      adapter.publish(\n          'edu.students.created',\n          json.dumps(asdict(event), default=encoder)\n      )\n   ```\n\n### 2. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f\n\n   2.1 \u041f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0438\u0437 \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0448\u0438\u043d\u044b\n\n   2.2 \u041f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u0432\u043d\u0435\u0448\u043d\u0435\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0438 \u0438\u043d\u0441\u0442\u0430\u043d\u0446\u0438\u0440\u0443\u0435\u0442 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0435\n\n   ```python\n   def bootstrap():\n      from students.core.adapters.messaging import adapter\n      from students.core.domain import events\n      \n      TOPIC_EVENTS = {\n         'edu.persons.created': events.PersonCreated,\n      }\n      \n      for message in adapter.subscribe(*TOPIC_EVENTS):\n         event = TOPIC_EVENTS[message.topic()](\n             **json.loads(message.value())\n         )\n         bus.handle(event)\n\n   ```\n\n   2.3 \u0412\u043d\u0443\u0442\u0440\u0435\u043d\u0435\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u0432 \u0448\u0438\u043d\u0443 \u044f\u0434\u0440\u0430\n\n   2.4 \u042f\u0434\u0440\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0442\u0438\u043f\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0438\u043c \u0438\u043d\u0441\u0442\u0430\u043d\u0441 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\n\n   2.5 \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\n\n   2.6 \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0438\u043d\u0441\u0442\u0430\u043d\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435\n\n   ```python\n   def on_person_created(\n      event: events.PersonCreated, uow: 'UnitOfWork'\n   ) -> None:\n      with uow.wrap():\n          transaction_id = event.meta.transaction_id\n   \n          saga = uow.sagas.get_object_by_uuid(transaction_id)\n          student = uow.students.get_object_by_id(saga.student_id)\n   \n          student.person_id = event.id\n          uow.students.update(student)\n   \n          uow.add_event(events.StudentCreated(\n              **command.dict(), **asdict(student)\n          ))\n   ```\n\n\n## \u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438\n\u041c\u043e\u0436\u043d\u043e \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 src/testapp.\n\n## \u0417\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432\n```sh\n$ tox\n```\n\n\n[//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax)\n\n   [bbom]: <https://ru.wikipedia.org/wiki/\u0411\u043e\u043b\u044c\u0448\u043e\u0439_\u043a\u043e\u043c\u043e\u043a_\u0433\u0440\u044f\u0437\u0438>\n   [ddd]: <https://ru.wikipedia.org/wiki/\u041f\u0440\u0435\u0434\u043c\u0435\u0442\u043d\u043e-\u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435_\u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435>\n   [domain-model]: <http://design-pattern.ru/patterns/domain-model.html>\n   [service-layer]: <http://design-pattern.ru/patterns/service-layer.html>\n   [coupling]: <https://ru.wikipedia.org/wiki/\u0417\u0430\u0446\u0435\u043f\u043b\u0435\u043d\u0438\u0435_(\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435)>\n   [dry]: <https://ru.wikipedia.org/wiki/Don\u2019t_repeat_yourself>\n   [cqrs]: <https://martinfowler.com/bliki/CQRS.html>\n   [explicit architecture]: <https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/>\n   [unit of work]: <http://design-pattern.ru/patterns/unit-of-work.html>\n   [repository]: <http://design-pattern.ru/patterns/repository.html>\n   [dto]: <http://design-pattern.ru/patterns/data-transfer-object.html>\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "\u041d\u0430\u0431\u043e\u0440 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \u043c\u043d\u043e\u0433\u043e\u0441\u043b\u043e\u0439\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b",
    "version": "2.2.1",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3d4bfe07b89f5f5bdf9927ba5e028b2b5767e680135e7f6013ddf9ec3a33b25d",
                "md5": "e89e35406e3e85b1dad862359fccd2c2",
                "sha256": "8d5563a7f6df5f4a148a7245192278aa1cfdda9aa1c930aa67a38acad60fb3d3"
            },
            "downloads": -1,
            "filename": "explicit_python-2.2.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e89e35406e3e85b1dad862359fccd2c2",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 23477,
            "upload_time": "2024-10-02T09:37:56",
            "upload_time_iso_8601": "2024-10-02T09:37:56.751888Z",
            "url": "https://files.pythonhosted.org/packages/3d/4b/fe07b89f5f5bdf9927ba5e028b2b5767e680135e7f6013ddf9ec3a33b25d/explicit_python-2.2.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "744a6b43ff6d9252f97fa7df22c848bee298ecb1049b381645f5b7a56de5a9a9",
                "md5": "078328eed04c41383ad36194b786046b",
                "sha256": "bf350308c98ca478aedbe2d28ae9a76cc21d2597d456aa6a40bb2fc18e6504c5"
            },
            "downloads": -1,
            "filename": "explicit_python-2.2.1.tar.gz",
            "has_sig": false,
            "md5_digest": "078328eed04c41383ad36194b786046b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 20313,
            "upload_time": "2024-10-02T09:37:58",
            "upload_time_iso_8601": "2024-10-02T09:37:58.335631Z",
            "url": "https://files.pythonhosted.org/packages/74/4a/6b43ff6d9252f97fa7df22c848bee298ecb1049b381645f5b7a56de5a9a9/explicit_python-2.2.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-10-02 09:37:58",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "explicit-python"
}
        
Elapsed time: 0.81127s