На самом деле до сих пор идёт внутренняя борьба между несколькими развилками в реализации. Моей целью изначально было желание отбросить поверхностные мнения в стиле «MVC для ботов - оверхед», и посмотреть как это всё-таки адаптировать, хотя бы из интереса "а подойдет ли?".
В моем случае деление системы проходит так:
Есть отделения
Диспатч,
Хэндлеры,
Контроллеры,
Модели,
Отображения,
Сервис (а также иерархические внутренности).
Диспатч - слой роутинга, к нему относятся телодвижения апдейтов, фильтрация, проверки и т. п., результат уходит в Хэндлеры на обработку (в самих обработчиках никаких проверок и фильтров нет, вы же не пилите один толстый хэндлер на сообщения с полотном ифов, верно? Тут тот же принцип, дробление и выполнение готовых команд)
Хэндлеры - слой обработчиков, командует контроллерами, тела хэндлеров в курсе, какие контроллеры использовать для достижения целей. Внутри используется executor контроллеров из utils, которому нужно лишь передать апдейт и контроллеры. Сам executor дергает у контроллеров метод запуска. Был вариант сделать мидлварь, которая хватает апдейт и прокидывает его в executor, а сам executor вместо update уже в хэндлер (и в итоге в хэндлере требуется только он), но варик отпал за невозможностью выпилить обязательный апдейт-арг из сигнатуры. Поэтому ексекутор идет просто отдельным импортом.
Контроллеры - слой управления операциями. Каждый контроллер занимается каким-нибудь одним бизнес-процессом, например BalanceController занимается подготовкой модели юзера, загрузкой в неё баланса, и передачей модели отображению. Вся задача контроллера - подготовить нужные модели в инициализаторе, провести манипуляции и дернуть интерфейсы показа у preview и view объектов (preview полезен, например, для тайпинга от бота, или предупреждения о начале долгой операции). Контроллеры собраны по идеям паттерна Command, и имеют базового родителя, у которого просто перегружают метод манипуляций и конструктор. Таким образом мы разделяем TO_DO и то, что видит пользователь независимо друг от друга и не захламляем один большой хэндлер на 40-50 строк какой-то каши, где идет первичная фильтрация «а можно ли», потом запрос в бд, затем подготовка клавиатур, форматирование текста, подготовка списка выборки, ответ на колбэк, итоговая посылка сообщения и т. п. дичь в которую чтобы что-то добавить, нужно поебстись с расположением (дичь в плане совокупности кучи несвязных вещей, объединенных лишь смыслом «обработки»).
Контроллер по идее получается тонким, т. к. всего лишь берет модели, дергает у них методы load/save для загрузки данных в атрибуты модели-датакласс, и сохранения в БД, а также дергает методы показа. То есть по сути является связующим клеем, без собственной бизнес-логики. Ну это если занудствовать.
# псевдокод метода запуска
# __init__: user, preview, view
preview.show() # if is not None
manipulate() # по-умолчанию в теле pass
view.show() # if is not None
При всём при том, метод todo может вообще отсутствовать, также как и методы показа (мало ли извращенцев). Поэтому особо не опирался на абстракную родительскую базу.
Модели - самая толстая по части логики вещь. Тут описаны все манипуляции по получению/сохранению данных (с DBMS из сервисного слоя), а также методы бизнес-логики. Здесь расположены именно данные, в том виде, в котором они проколонованы в базе.