Асинхронный django: status update
- вторник, 30 августа 2022 г. в 00:38:59
Название поста может быть знакомо некоторым читателям. Действительно, я некоторое время назад задался вопросом, насколько будет сложно сделать django асинхронным. Так вот, процесс кодинга в этом направлении продолжился, и теперь есть целая альфа-версия.
Проект называется vinyl. У него есть репозиторий. Пользоваться им можно и нужно! Про то, как это делать, можно узнать из README - я думаю, всё вполне соответствует ожиданиям от асинхронной версии. Здесь я хочу рассказать про то, что, как говорится, удалось узнать в процессе.
Сейчас vinyl - только асинхронный фреймворк, но первая версия такой не была, она поддерживала как синхронный, так и асинхронный I/O. API асинхронной версии было зеркальным отражением синхронной. Мне всегда было интересно, почему нельзя так сделать - и вот попробовал сам. Как оказалось, это возможно, и относительно нетрудно, особенно, если все операции выполняются последовательно, как в django orm.
Однако вскоре меня начали мучить сомнения, что поддержка сразу обеих версий может быть медвежьей услугой, и долгосрочно не оправдано. И, конечно, этих сомнений было достаточно, чтобы я простился со своим "универсальным" кодом и сделал фреймворк async-only.
А причины вот какие: поддерживая обе версии, синхронную и асинхронную, я тем самым заставляю весь стек библиотек делать то же самое. Это касается как библиотек более высокого уровня (сериализация данных, валидация), так и более низкого - драйверы баз данных. Кроме того, универсальный код - это парочка лишних врапперов в стеке вызовов. В общем, спорные вопросы есть. А синхронной версии vinyl в итоге нет.
Интересная, конечно, ситуация: лично я, например, не знаю, что лучше: только синхронный или только асинхронный django. Потому что эндпоинты/урлы, для которых нужен асинхронный I/O, обычно в явном меньшинстве. Но, тем не менее, примем, что асинхронный orm всем нужен как статистический факт.
Идём дальше. Я обнаружил один интересный факт, который существенно облегчает портирование orm на асинхронный I/O. Он касается операций записи в базу данных - тех, которые обычно приводят к серии insert/update/delete запросов. Так вот, в отличие от select-запросов, нам не очень важен результат в таких запросах. То есть, важно, чтобы они успешно выполнилось, а, скажем, сколько строк обновил UPDATE - не очень важно. Для INSERT может понадобиться первичный ключ сохранённого объекта, но это отдельный случай.
Решение напрашивается, как говорится: логику синхронного кода мы не меняем, но вместо выполнения запросов мы только сохраняем их в списке, а в самом конце уже асинхронно выполняем. Таким образом, логику многих CRUD операций, а также всяких related менеджеров, почти не пришлось менять. Этот трюк позволил сохранить высокоуровневый API django для операций записи - в противном случае, я думаю, он стал бы более минималистичным.
В общем, всё это было для затравки, гляньте на репозиторий и скажите, насколько он хорош.