Как можно построить архитектуру действительно большого Flask приложения?

Разрабатываю большое Flask приложение, возникла проблема в его архитектуре. В целом то мне понравился подход в https://github.com/sean-/flask-skeleton, что можно использовать Blueprint (так то подобных примеров и гайдов полно).

Однако приложение большое тем, что оно не просто разбито на несколько файлов для удобства, а обслуживает несколько сайтов (хостов/поддоменов - будет задаваться в конфиге), которые имеют пересечение функционала (следовательно, есть общий код для этого) и между ними поддерживается общая сессия пользователя и сама база с ними (и некоторыми другими данными).

В целом то архитектура построена сейчас таким образом, что есть некоторое ядро, где созданы общие модели, классы для всех сайтов, а так же создается приложение Flask, подключено расширение Flask-SQLAlchemy. Так же было включена поддержка host matching и реализовано указание хоста в Blueprint. Сами же блоки функционала хранятся в индивидуальных пакетах, где создается Blueprint'ы под это дело, определены формы, модели (они импортируют из ядра созданный объект класса SQLAlchemy из расширения), вьюхи. В целом, задумка такая же, как и в указанном скелетоне.

Казалось бы все хорошо, регистрируй Блюпринты в приложении Flask и радуйся. Но проблема в том, что Blueprint по факту просто регистрирует в таблице роутинга одни и те же импортированными вьюхи под разными маршрутами (предполагается регистрация одного Блюпринта множество раз - Flask это позволяет). И даже не в этом корень проблемы: модель то в блоке функционала остается той же самой и работает с теми же самыми данными. По факту по разным адресам будет одно и то же, что сводит весь смысл в приложении на нет.

Сначала пытался решить проблему с моделями, но начал задумываться: а правильно ли была построена архитектура приложения? Не слишком ли требовать от Blueprint'а и пакетов питона полной изоляции? Возможно, надо двигаться в сторону создания конкретных экземпляров моделей из абстрактной и подсовывать это в вьюху через паттерн DI? Возможно, вовсе стоит уже сейчас прибегнуть к паттерну фабрики приложения и создавать Flask приложение для каждого сайта (но тогда надо прокидывать сессию и прочие общности между ними)?

Если кратко и на пальцах, то хотелось бы построить архитектуру, где пускай есть site1.ru, site2.ru, site3.ru. Базы пользователей и регистрация у них общая, сессия тоже. Имеется общий код в виде блоков, который пускай реализуют функционал тех же новостей, статей и форумов. И в конфиге бы указывалось, что на site1.ru нужны новости на главном (префикс /), на site2.ru нужно два каталога статей (префиксы /articles_1/ и /articles_2/), на site3.ru нужны новости (префикс /news/) и форум (префикс /forum/). И, понятное дело, что эти, так сказать, блоки кода должны оперировать с разным контентом.

Ну и отвечу на некоторые возможные вопросы:
  • Flask выбран потому, что он минималистичен и позволяет гибко строить приложения (да, расплата за это то, что необходимо местами делать связки и строить архитектуру самостоятельно. но гибкость, минималистичность, мощность я ценю
  • Django не выбрал потому, что она удобна скорее для типовых решений. мне не все нравится как в ней устроено, пришлось бы так же переписывать и реализовывать некоторые вещи
  • Да, мои знания в Python не идеальны, но мне нравится погружаться в нечто новое с помощью реальных проектов, а не вызубривать тонны книжек перед этим или строя очередной учебный мироклон твиттера, где описанные проблемы просто не поднимаются
  • Внутренности работы SQLAlchemy мне до конца не ясны пока, но мне начинает казаться, что надо воевать не с ней, а в архитектуре просто допущен косяк, почему все и выходит так...
P.S. Извиняюсь за столь длинную портянку, но хотелось бы решить проблему красиво, так как бьюсь над ней уже некоторое время. А решать колхозом вроде складывания всего контента всего в одну модель (точнее таблицу, так как на нее модель мапится) - не хотелось бы. Если бы не было ORM, то в чистом SQL решилось бы простыми префиксами у таблиц. Но ORM, Python - это для меня пока новый стек технологий, которые очень бы хотелось уже использовать, а не сидеть в php4-style.
  • Вопрос задан
  • 2201 просмотр
Пригласить эксперта
Ответы на вопрос 4
просто переходите на Django. меньше головной боли получите
Ответ написан
sim3x
@sim3x
Если ты хочешь построить большое приложение, то у тебя уже сразу есть большая проблема
Разделяй
Тебе не обязательно иметь, все в одной бд и на одном сайте
Сделай аккаунты на одном домене - одно приложение
А все остальное делай через развертывание на новом домене

А про фласк, я не видел саксесс сториес
Ответ написан
ri_gilfanov
@ri_gilfanov
Web- and desktop-developer
1. По привязке приложения Flask к разным доменам

Маршруты можно привязывать не только к урл-префиксам и субдоменам, но и к доменам.

Пример объявления маршрута через декоратор на конкретный домен:
@app.route("/", host="site2.ru")
def index():
    return 'pass'


В официальной документации Flask это не указано, так как этот аргумент прокидывается внутри словаря **options аж до модуля routing библиотеки Werkzeug.

В комментариях указано, что эта опция исключает аргумент subdomain (что в общем-то логично):
This also means that the subdomain feature is disabled.


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

Чтобы привязать Blueprint тут https://stackoverflow.com/questions/40730110/flask... предлагают наследоваться от стандартного Blueprint и переопределить два метода:
class MyBlueprint(Blueprint):
    def __init__(self, *args, **kwargs):
        self.default_host = kwargs.pop('default_host', None)
        Blueprint.__init__(self, *args, **kwargs)

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        options['host'] = options.pop('host', self.default_host)
        super().add_url_rule(self, rule, endpoint=None, view_func=None, **options)

...

shop = Blueprint(..., default_host='localhost:<port>')
info = Blueprint(..., default_host='info.localhost:<port>')



2. По SQLAlchemy

Не понимаю проблемы:
  • назвать модели можно как угодно;
  • названия таблиц можно задать через свойство __tablename__ модели;
  • в каких вьюхах к каким моделям обращаться -- дело разработчика;
  • при работе с одной моделью с разных сайтов, можно проверять значение host внутри request;
  • так же при работе с одной моделью с разных сайтов, ничто не мешает сохранять значение host в базе данных;
  • судя по документации Flask-SQLAlchemy ( flask-sqlalchemy.pocoo.org/2.3/binds ), одновременная работа с разными СУБД и базами данных предусмотрена.
Ответ написан
@asd111
Динамический выбор таблицы для одной и той же модели в django обычно решается примерно так:
def get_model(db_table):
  class MyClassMetaclass(models.base.ModelBase):
    def __new__(cls, name, bases, attrs):
      name += db_table
      return models.base.ModelBase.__new__(cls, name, bases, attrs)

  class MyClass(models.Model):
    __metaclass__ = MyClassMetaclass

    class Meta:
      db_table = db_table

  return MyClass

my_model = get_model('29345794_table')
my_model.objects.filter( ...


Суть в том чтобы при создании модели динамически указать название таблицы. А всё остальное будет работать как обычно. Думаю как это сделать под sqlAlchemy разберетесь самостоятельно.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы
Work Solutions Ростов-на-Дону
от 70 000 до 100 000 руб.
RYDLAB Екатеринбург
от 65 000 руб.
от 140 000 до 160 000 руб.
16 июн. 2019, в 15:01
1000 руб./за проект
16 июн. 2019, в 14:02
7000 руб./за проект