Django
Вопрос о том, держать ли логику работы с данными в базе или в приложении, делит мир на два лагеря.
В первом смотрят на систему и видят в ней данные. Не важно, какие приложения запускают в них свои грязные руки сегодня, ведь данные всех переживут. Если, конечно, как следует позаботиться об их целостности и сохранности.
Во втором — превыше всего приложение, чудесное, высокотехнологичное, в которое пользователи влюбляются с первого клика. Конечно, оно использует данные, так что следует вооружиться ORM-ом и пусть какая-нибудь СУБД обеспечивает персистентность.
Сам я всегда принадлежал (и принадлежу) первому лагерю, но интересно же посмотреть, как другие живут. Поэтому из любопытства поковырял немного Django — на StackOverflow периодически попадаются вопросы типа Как бы мне сделать то-то и то-то, имеющие понятный ответ на SQL.
Структура данных в Django описывается в виде питоновских классов (моделей): класс — таблица, поле — столбец. Там же определяются ограничения целостности, в том числе внешние ключи.
Возьмем простой пример с поставками деталей:
Модель выглядит довольно изящно:
class Part(models.Model): pass class Vendor(models.Model): name = models.CharField(max_length=100) parts = models.ManyToManyField(Part)
И превращается (в случае PostgreSQL), заменяя отношение многие-ко-многим на промежуточную таблицу, в такой примерно код:
BEGIN; CREATE TABLE "db_part" ("id" serial NOT NULL PRIMARY KEY); CREATE TABLE "db_vendor" ("id" serial NOT NULL PRIMARY KEY, "name" varchar(100) NOT NULL); CREATE TABLE "db_vendor_parts" ("id" serial NOT NULL PRIMARY KEY, "vendor_id" integer NOT NULL, "part_id" integer NOT NULL); ALTER TABLE "db_vendor_parts" ADD CONSTRAINT "db_vendor_parts_vendor_id_5de36e57_fk_db_vendor_id" FOREIGN KEY ("vendor_id") REFERENCES "db_vendor" ("id") DEFERRABLE INITIALLY DEFERRED; ALTER TABLE "db_vendor_parts" ADD CONSTRAINT "db_vendor_parts_part_id_b4687c2a_fk_db_part_id" FOREIGN KEY ("part_id") REFERENCES "db_part" ("id") DEFERRABLE INITIALLY DEFERRED; ALTER TABLE "db_vendor_parts" ADD CONSTRAINT "db_vendor_parts_vendor_id_d443052f_uniq" UNIQUE ("vendor_id", "part_id"); CREATE INDEX "db_vendor_parts_96b1f972" ON "db_vendor_parts" ("vendor_id"); CREATE INDEX "db_vendor_parts_b4e61b8d" ON "db_vendor_parts" ("part_id"); COMMIT;
Префикс db — это просто имя приложения (логичнее было бы использовать схемы, но ладно).
Здесь здорово то, что дальнейшие изменения модели можно автоматически превратить в скрипты (так называемые миграции) для изменения уже существующей структуры таблиц. А это значит, что модель можно осмысленно держать под версионным контролем.
Например, добавление имени в модель Part
приводит к генерации таких команд:
BEGIN; ALTER TABLE "db_part" ADD COLUMN "name" varchar(100) DEFAULT '' NOT NULL; ALTER TABLE "db_part" ALTER COLUMN "name" DROP DEFAULT; COMMIT;
Дальше хуже. Из модели автоматически получается могучий API для работы с данными. Получая на вход некую конструкцию, API генерирует и выполняет соответствующие команды SQL. Продолжая взятый пример, попробуем написать несколько запросов. Их аналоги на SQL, а заодно и в терминах реляционной теории, можно посмотреть тут.
-
Получить поставщиков, поставляющих хоть что-нибудь:
Vendor.objects.filter(parts__isnull=False).distinct()
API устроен так, что позволяет «гулять» по ссылкам. Встретив упоминание
parts
в контексте классаVendor
, механизм понимает, чтоdb_vendor
надо соединять сdb_part
. Таким образом, генерируется следующий SQL-код:SELECT DISTINCT "db_vendor"."id", "db_vendor"."name" FROM "db_vendor" INNER JOIN "db_vendor_parts" ON ("db_vendor"."id" = "db_vendor_parts"."vendor_id") WHERE "db_vendor_parts"."part_id" IS NOT NULL
-
Поставщики, не поставляющие ни одной детали:
Vendor.objects.filter(parts__isnull=True)
Здесь генератор догадывается, что для
isnull=True
нужно левое соединение:SELECT "db_vendor"."id", "db_vendor"."name" FROM "db_vendor" LEFT OUTER JOIN "db_vendor_parts" ON ("db_vendor"."id" = "db_vendor_parts"."vendor_id") WHERE "db_vendor_parts"."part_id" IS NULL
-
Поставщики, поставляющие деталь 15:
Vendor.objects.filter(parts__id=15)
Тут все очевидно:
SELECT "db2_vendor"."id", "db2_vendor"."name" FROM "db2_vendor" INNER JOIN "db2_vendor_parts" ON ("db2_vendor"."id" = "db2_vendor_parts"."vendor_id") WHERE "db2_vendor_parts"."part_id" = 15
-
Поставщики, поставляющие все детали.
А вот здесь все плохо. Простые запросы на Django писать легко (несмотря на диковатый синтаксис), но нетривиальное оказывается попросту невозможным. Не исключено, что я просто не сумел придумать запрос, но коллективный разум пока тоже не предложил ответа.
Так или иначе, неполноценность языка запросов Django, кажется, ни у кого не вызывает сомнений.
Зачем же нужен такой язык, на котором можно сформулировать далеко не любой запрос? Ведь сколько сил было потрачено на реализацию, страшно подумать. Не просто же так? Вроде бы есть два аргумента, но оба не выдерживают критики с моей точки зрения:
-
Независимость от СУБД.
Но если диалекты SQL хоть как-то, да совместимы друг с другом, то уж язык запросов Django точно не совместим ни с чем. И вместо зависимости от СУБД разработчики получают зависимость от фреймворка. К чему это приведет? К тому, что пытаясь всеми силами избежать SQL, ограничения Django будут обходиться с помощью Питона. Избыточные запросы к базе, соединение таблиц в коде приложения, вот это вот все.
Особенно забавно в этом контексте выглядит модуль django.contrib.postgresql, который таки привязывает проект к PostgreSQL.
-
Абстрагирование от СУБД.
В том смысле, что незачем разработчику на Питоне знать про базу данных и учить SQL. Но ведь все равно придется учить какой-то язык запросов. SQL не самый сложный язык на свете, знание его может пригодиться и для других задач. А знание языка запросов Django ни для чего, кроме собственно Django, не нужно.
Можно посмотреть и с другой стороны. Как известно, все нетривиальные абстракции дырявы. Однажды к разработчикам придет недовольный администратор базы данных и спросит: А отчего вот такой-то запрос SQL выполняется десять тысяч раз подряд? А разработчики его даже не поймут, они же этот запрос в глаза не видали.
Спрашивается, не проще ли научиться писать запросы SQL, и — если уж необходимо — делать это так, чтобы можно было перейти на другую СУБД без титанических усилий? К слову, Django позволяет вполне удобно работать с SQL-запросами.
Еще кажется, что могут помочь представления, за которыми легко скрыть сложную логику независимым от СУБД образом. Но ведь это уже прямая дорога из второго лагеря в первый?
no subject
К чему это я: вечными является не БД, а весь ансамбль БД+СУБД+приложения. И если мы считаем СУБД первичной, то БД+приложения логично держать поблизости, разве что у нас есть специальные причины так не делать.
Мне тут разонравился классичесий подход к реализации ИИ посредством базы данных, и я решил совершить переход к "программам" для повышения гибкости в организме, но пока не вполне понимаю как.
no subject
Мне нравится, когда интерпретация задана на уровне БД. Но в реальном мире она, увы, не ограничивается ни заголовком таблицы, ни ограничениями целостности. Тогда возникают где-то как-то описанные договоренности, что приложения должны интерпретировать данные так-то и так-то, иначе другие приложения поломаются (ну или начинаются пляски с уровнями абстракции_от). И в этом смысле получается, что приложения действительно примазываются к ансамблю вечности.
А насчет гибкости в организме... Данные легко менять; легче, чем программы. Неужели ты смотришь в сторону самомодифицирующегося кода?
no subject
no subject
no subject
no subject
Что мне тут непонятно — это как ограничить пространство перебора, чтобы внесение модификаций было с достаточной вероятностью «совместимо с жизнью». Природа этому научилась, но там все ох как непросто.
no subject
no subject
no subject
Вот, например, есть у Оракла возможность легким движением руки поднять веб-сервисы прямо внутри СУБД. Но коллеги из Яндекса, знающие толк_в, вовремя дали нам по рукам, и правильно. Слишком много архитектурных вопросов возникает к такой схеме (не говоря уже о хреновой оракловой реализации).
Или взять OEBS. Так всегда почти весь код был внутри базы. А в новой инкарнации ораклоиды решили унести код на уровень приложения, хотя казалось бы. (Правда, ни хрена эта конструкция не взлетела.)
А в идеологии Постгреса так и вообще управлением транзакциями должно заниматься приложение, и только оно. А хранимые процедуры фиксировать и откатывать изменения не могут.
А ты видишь какие-то предпосылки_к?
no subject
Единственная причина, почему до сих пор существует деление на приложение и СУБД, - недостаточная масштабируемость СУБД плюс монолитная архитектура приложений. Когда народ в полной мере освоит микросервисную архитектуру - тут-то коммунизм и наступит. А горизонтально масштабируемые СУБД уже есть, это такой сросшийся класс продуктов IMDG/IMDB. Посмотри, например, Tarantool - офигительная же Весчъ™
no subject
no subject
no subject
no subject
no subject