Уже почти полгода я курирую разработку проекта и во многом диктую чистую архитектуру в разработке. К чему это привело:

  • Чистые сценарии предметки которые не зависят от Django от слова совсем
  • Гибкость в создании архитектурных элементов
  • Свой ORM над Django-ORM (Это как бы шутка, но на самом деле правда)

Сегодня пообщались с коллегами из другой команды: они тоже на Django но используют по-максимому Django-ORM и готовые библиотеки. Пока что размеры не соизмеримы, поэтому сложно сравнивать решения, однако у меня кое-что зацепило. На примере state-machine: была использована django-fsm которая диктует свои правила и ограничения в реализации состояний. Често говоря, по библиотеке сложно понять как с ней работать. Да и выглядит это…не гибко, не нативно к проекту. И так будет с каждым решением. И ведь написание своего велосипеда тоже будет выглядеть костылем, потому что это не Django.

В нашем решении, есть набор сущностей предметной области и вереница: UseCase, Presenter, Storage. UseCase (он же Interactor) представляет собой основные функции бизнес логики. Их удобно инициализировать и запускать и чувствуется необходимость в создании структуры UseCaseOutputPort - сейчас мы возращаем из сценариев словари, которые на самом деле возращает Presenter который вызывается в конце функции execute сценария. Это был сильный косяк. Во-первых это дополнительный Dependency Injection в UseCase, а во вторых он не нужен по простой причине: в рамках Django, роль Presenter может выполнять view. Это выглядит правильным, потому что зачем выносить спецефичный для текущей реализации (т.е. Django) в какие-то отдельные классы и оставлять view только логику формирования входных параметров для UseCase.

Конечно мы начали достаточно скоро реагировать на изменения бизнеса, что привело к созданию концепта Workflow - та же state-machine. Это получилось не в самом лучшем виде т.к. делалось на скорую руку, но в целом это получилось расширяемым решением: для разных команд, у которых слишком разные рабочие процессы, мы смогли писать отдельные классы с их кастомной логикой. Причем мы можем себе позволить отдать реализацию таких классов разрабочикам команд, которые пользуются нашим софтом, потому что API прост и использует лишь пару классов (структур пока-что) из предметной области. И все это API не зависит от Django или чего-то еще. Оно краткое.

Конечно лошка дегтя есть: сейчас не очень понятно когда и как вызываются функции Workflow. Более того, остается гибкость в выборе реализации state-machine: можно взять и стороннее решение и по своему написать. Причем есть Workflow и без набора состояний: это такие Workflow в которых из каждого состояния можно перейти в каждое, т.е. Workflow не управляет этим. Все что он делает это…ничего. NoopWorkflow - реализация по-умолчанию. Однако с точки зрения чистой архитектуры, в целом, все выглядит достаточно приятно.

Выработались такие патерны:

  • Создавайте связку Storage и Presenter (View) с привязкой к конкретному UseCase - это позволит больше сконцентрироваться на сценарии и проводить при необходимости жуткие оптимизации ценой небольшого дублирования.

  • Storage - это просто набор операций который необходим конкретному сценарию. Код Storage должен быть максимально простым. Особое внимание нужно обращать на вызовы других функций и условные операторы: чаще всего это признак того, что часть бизнес-логики лежит в Storage

  • Сразу же стоит озаботиться написанием сериализаторов из моделей Django в модели предметной области. Можно конечно считерить и обойтись без этого, отдавая сами модели, но стоит ограничить себя в обращениях к ORM-API который предоставляет Django-ORM вне Storage потому что тогда привязанность к Django-ORM останется и само приложение будет уже Django’вским.

У меня остался открытым самый жывотрепещущий вопрос: что делать с этими чертовыми ReactJS и Redux?! Пока что они живут своей жизнью, но ощущается что это не правильно. Им не хватает “чистоты”.