software-architecture
June 8, 2023

Грязная тайна "No-code" сервисов

Не то чтобы совсем никто не был в курсе, просто одни не акцентируют ненужное внимание, чтобы «не загружать техническими деталями» дорогих клиентов, другие верят на слово и про эти детали слушать не хотят. Ну, а я снова пишу статью по мотивам многократных попыток объяснить — что не все так просто и любви за 50 баксов не бывает.

Красиво, правда. Кубики связываются и таскаются туда-сюда и программисты не нужны. И дешево.

О чем это все

Позволю себе процитировать:

No-code-инструменты позволяют обычным пользователям ПК создавать веб-сайты и приложения без необходимости написания программного кода

C пандемийного 2020го как грибы после дождя полезли «непонятные люди желающие странного»:

выкинуть на мороз дорогого и капризного Васю-программиста, заменив его «no-code» сервисом за 50 баксов в месяц.

Типа такого.

Поветрие было массовым и вообщем-то до сих пор не прошло окончательно.

Хотя опытные пользователи, пережившие 90е и видевшие предыдущие модные поветрия могут заподозрить, что идея 21 века «разработки без программистов», скажем прямо — не нова.

Всю мать ее историю ИТ люди пытались избавиться от дорогих программистов и дать возможность делать кастомизацию софта кривыми руками пользователей.

Знаете как появилось разделение на системных и прикладных программистов? Это была самая первая попытка такого избавления — один из тысячи программистов может написать ОС и компилятор, остальные 999 с этой ОС и компилятором работают.

Но конечно очень быстро этого оказалось мало, поэтому следующим уровнем деградации упрощения стал DSL:

Предметно-ориентированный язык (англ. domain-specific language, DSL — «язык, специфический для предметной области») — компьютерный язык, специализированный для конкретной области применения (в противоположность языку общего назначения, применимому к широкому спектру областей и не учитывающему особенности конкретных сфер знаний). Построение такого языка и/или его структура данных отражают специфику решаемых с его помощью задач[1]. Является ключевым понятием языково-ориентированного программирования.

Хороший пример, с которым большинство читателей точно встречалось — макросы в Microsoft Excel:

Sub Macro1() 
' 
' Macro1 Macro 
' 
' 
       Range("B1").Select 
       ActiveCell.FormulaR1C1 = "Hello World" 
       Range("B2").Select 
End Sub

Примерно такие DSL и используются в Low-Code платформах, чтобы уж совсем одним тасканием кубиков все задачи не решать.

Хотя выглядит все конечно очень красиво и дюже наглядно:

Даже эти адские блески-звездочки в тему - как первый эффект от использования No-code/Low-code платформы.

Грязная тайна

Ну да-да — не то чтобы грязная и не совсем тайна, скорее особенность работы этого цирка с конями.

Постараюсь максимально лаконично и доходчиво объяснить в чем тут прикол, но често не обещаю что поймете. Конкретно про no-code/low-code мне почему-то доходчиво не получается доносить мысли.

Для начала давайте посмотрим вот на эту замечательную картинку:

Типичное демо какой-то no-code платформы. Ничего "нипонятна" но красиво.

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

Все эти квадратики тут — отдельные функции, с параметрами, которые вызываются друг за другом по цепочкам, которые рисуются линиями.

Видите «When»? Это ни что иное как условие вызова — если проверка на условие проходит — выполнение цепочки идет дальше по ветви логики.

Такой себе упрощенный язык программирования, где вместо написания команд вы как дебил таскаете и связываете кубики.

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

Пять минут на размышление, а я выдержу МХАТовскую паузу и схожу за кофе.

Ну что есть идеи?

Конечно оно все хранится в базе, на чужом сервере, к которому вы не имеете прямого доступа. Скорее всего хранится в виде какого-то сложного XML или JSON-документа.

Все это достаточно очевидно, настолько что любой разработчик уже начнет зевать на этом месте.

Но сейчас я напишу лишь одну фразу и у всех причастных немедленно подгорит снизу и начнет дергаться глаз:

миграция данных

Да да мои хорошие, самое время выпить валерианки и пожаловаться на меня своему психологу — что де ваши «личные границы» опять грубо нарушили и расшатали.

Для тех кто не знает и не представляет что это такое, ниже более развернутый рассказ с примерами.

Я использовал для наглядности BPMN-нотацию, которая максимально близка или даже аналогична тому что используется внутри закрытых No-code/low-code платформ.

Больше и детальнее про технологию BPM и BPMN-нотацию в частности я уже рассказал тут, поэтому не буду повторяться.

Вот такая простейшая графическая схема:

в виде XML-документа выглядит как-то так:

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" 
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" 
xmlns:di="http://www.omg.org/spec/DD/20100524/DI" 
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" 
id="Definitions_1" 
targetNamespace="http://bpmn.io/schema/bpmn" 
exporter="Camunda Modeler" exporterVersion="0.1.0">
 <bpmn:process id="Process_1" isExecutable="true">
 <bpmn:startEvent id="StartEvent_1" name="Order Placed">
 <bpmn:outgoing>SequenceFlow_1bq1azi</bpmn:outgoing>
 </bpmn:startEvent>
 <bpmn:sequenceFlow id="SequenceFlow_1bq1azi" sourceRef="StartEvent_1" targetRef="Task_1f47b9v" />
 <bpmn:sequenceFlow id="SequenceFlow_09hqjpg" sourceRef="Task_1f47b9v" targetRef="Task_1109y9g" />
 <bpmn:sequenceFlow id="SequenceFlow_1ea1mpb" sourceRef="Task_1109y9g" targetRef="Task_00moy91" />
 <bpmn:endEvent id="EndEvent_0a27csw" name="Order Delivered">
 <bpmn:incoming>SequenceFlow_0ojoaqz</bpmn:incoming>
 </bpmn:endEvent>
 <bpmn:sequenceFlow id="SequenceFlow_0ojoaqz" sourceRef="Task_00moy91" targetRef="EndEvent_0a27csw" />
 <bpmn:serviceTask id="Task_1f47b9v" name="Collect Money">
 <bpmn:extensionElements>
 <zeebe:taskDefinition type="collect-money" retries="3" />
 </bpmn:extensionElements>
 <bpmn:incoming>SequenceFlow_1bq1azi</bpmn:incoming>
 <bpmn:outgoing>SequenceFlow_09hqjpg</bpmn:outgoing>
 </bpmn:serviceTask>
 <bpmn:serviceTask id="Task_1109y9g" name="Fetch Items">
 <bpmn:extensionElements>
 <zeebe:taskDefinition type="fetch-items" retries="3" />
 </bpmn:extensionElements>
 <bpmn:incoming>SequenceFlow_09hqjpg</bpmn:incoming>
 <bpmn:outgoing>SequenceFlow_1ea1mpb</bpmn:outgoing>
 </bpmn:serviceTask>
 <bpmn:serviceTask id="Task_00moy91" name="Ship Parcel">
 <bpmn:extensionElements>
 <zeebe:taskDefinition type="ship-parcel" retries="3" />
 </bpmn:extensionElements>
 <bpmn:incoming>SequenceFlow_1ea1mpb</bpmn:incoming>
 <bpmn:outgoing>SequenceFlow_0ojoaqz</bpmn:outgoing>
 </bpmn:serviceTask>
 </bpmn:process>
 <bpmndi:BPMNDiagram id="BPMNDiagram_1">
 <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
 <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
 <dc:Bounds x="191" y="102" width="36" height="36" />
 <bpmndi:BPMNLabel>
 <dc:Bounds x="175" y="138" width="68" height="12" />
 </bpmndi:BPMNLabel>
 </bpmndi:BPMNShape>
 <bpmndi:BPMNEdge id="SequenceFlow_1bq1azi_di" bpmnElement="SequenceFlow_1bq1azi">
 <di:waypoint xsi:type="dc:Point" x="227" y="120" />
 <di:waypoint xsi:type="dc:Point" x="280" y="120" />
 <bpmndi:BPMNLabel>
 <dc:Bounds x="253.5" y="99" width="0" height="12" />
 </bpmndi:BPMNLabel>
 </bpmndi:BPMNEdge>
 <bpmndi:BPMNEdge id="SequenceFlow_09hqjpg_di" bpmnElement="SequenceFlow_09hqjpg">
 <di:waypoint xsi:type="dc:Point" x="380" y="120" />
 <di:waypoint xsi:type="dc:Point" x="440" y="120" />
 <bpmndi:BPMNLabel>
 <dc:Bounds x="410" y="99" width="0" height="12" />
 </bpmndi:BPMNLabel>
 </bpmndi:BPMNEdge>
 <bpmndi:BPMNEdge id="SequenceFlow_1ea1mpb_di" bpmnElement="SequenceFlow_1ea1mpb">
 <di:waypoint xsi:type="dc:Point" x="540" y="120" />
 <di:waypoint xsi:type="dc:Point" x="596" y="120" />
 <bpmndi:BPMNLabel>
 <dc:Bounds x="568" y="99" width="0" height="12" />
 </bpmndi:BPMNLabel>
 </bpmndi:BPMNEdge>
 <bpmndi:BPMNShape id="EndEvent_0a27csw_di" bpmnElement="EndEvent_0a27csw">
 <dc:Bounds x="756" y="102" width="36" height="36" />
 <bpmndi:BPMNLabel>
 <dc:Bounds x="734" y="142" width="81" height="12" />
 </bpmndi:BPMNLabel>
 </bpmndi:BPMNShape>
 <bpmndi:BPMNEdge id="SequenceFlow_0ojoaqz_di" bpmnElement="SequenceFlow_0ojoaqz">
 <di:waypoint xsi:type="dc:Point" x="696" y="120" />
 <di:waypoint xsi:type="dc:Point" x="756" y="120" />
 <bpmndi:BPMNLabel>
 <dc:Bounds x="726" y="99" width="0" height="12" />
 </bpmndi:BPMNLabel>
 </bpmndi:BPMNEdge>
 <bpmndi:BPMNShape id="ServiceTask_0lao700_di" bpmnElement="Task_1f47b9v">
 <dc:Bounds x="280" y="80" width="100" height="80" />
 </bpmndi:BPMNShape>
 <bpmndi:BPMNShape id="ServiceTask_0eetpqx_di" bpmnElement="Task_1109y9g">
 <dc:Bounds x="440" y="80" width="100" height="80" />
 </bpmndi:BPMNShape>
 <bpmndi:BPMNShape id="ServiceTask_09won99_di" bpmnElement="Task_00moy91">
 <dc:Bounds x="596" y="80" width="100" height="80" />
 </bpmndi:BPMNShape>
 </bpmndi:BPMNPlane>
 </bpmndi:BPMNDiagram>
</bpmn:definitions>

Круто да?

Пример взят из документации Camunda, если вдруг кому интересно. Там еще много такого.

Теперь представьте как это мигрировать.

А пардон, я же тут пишу для обычных людей, не для айтишников. Так что давайте деградировать вместе опустимся до кубиков:

Вот этим жирным красным крестом я пометил блок, которого больше нет, по независящим от вас и «no-code» платформы причинам.

Не договорились, контракт кончился, санкции ввели — что угодно. Причины тут не так важны.

Важны последствия:

В вашей замечательной чистой, белой и пушистой «no-code» схеме появилась дыра. По независящим от вас причинам. Без вашей воли и без ваших действий.

Как минимум, ваша схема станет работать неправильно, как максимум — сломается совсем и вся автоматизация встанет.

Как супер-максимум — сломается на уровне данных, так что чинить будете с тех.поддержкой и подключением разрабочиков платформы.

Вот про последнее и поговорим.

Работает — не трож!

Как чаще всего делается внутренняя автоматизация бизнеса на «no-code» решениях?

Вы долго и упорно ваяете схему в онлайн-кострукторе, зарабатывая мозоль длинными темными ночами от таскания мышкой всех этих разноцветных блоков. Затем запускаете в работу и забываете.

Проходит полгода-год до момента следующей мелкой правки. И внезапно оказывается что половина созданного уже не работает.

Что же могло пойти не так?

Дело в том что:

сама «no-code» платформа это тоже софт, у которого есть релизы и обновления, который постоянно дорабатывают и исправляют

Естественно что за эти полгода, пока вы думали что все успешно сделали, запустили и забыли — платформа продолжала обновляться и перезапускаться.

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

Но есть один нюанс:

миграция пользовательских данных — самый страшный, кромешный п#здец из возможных в ИТ

Даже в крупных ИТ-компаниях этот процесс — русская рулетка, а утеря и порча пользовательских данных из-за кривого обновления были и у Google и у Facebook и у Яндекса с VK.

Причем понять что что-то пошло не так сразу нельзя, а откатывать апдейт по данным запредельно сложно и очень долго.

Вообщем если вы когда-либо видели упавший фейсбук, утерю почтовых сообщений или переписки ВК — знайте что это все из-за обновлений.

Упавший StackOverflow.

Теперь вернемся к нашим баранам — «no-code» решению.

Программа, которая считала себя блок-схемой

Напомню еще раз:

набор разноцветных связанных линиями кубиков на красивой блок-схеме — по факту является программой

Со всеми вытекающими последствиями, главные из которых — контроль выполнения и наличие состояния.

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

А все что между запуском и завершением — состояние.

Которое надо где-то сохранить и уметь продолжить выполнение вашей блок-схемы с этой конкретной точки.

Звучит сложно?

Именно так оно и есть для обычных программ, которые вы запускаете локально. А теперь представьте что состояние вашей «схемы-программы» хранится в базе данных в виде все той-же XML, с привязкой к структурам данных платформы.

И криворукие разработчики платформы уже выкатывают потенциально опасный апдейт, который все это сломает.

Еще не взялись за голову?

Крепкие же у вас нервы.

Подобьем «итого».

Итого

Идеи из серии «выкинь Васю-программиста на мороз, чтоб не платить столько денег» в ИТ были с самого его начала, где-то с 70х годов прошлого века, если не раньше.

Собственно абсолютно любой массовый бизнес про удешевление для конечного потребителя, кому это удается тот и в дамках.

Тем не менее, мой личный опыт показывает, что все попытки выкинуть именно разработчиков приводят к тому что их «электронная замена» со временем начинает стоить дороже чем содержание живого сотрудника, при этом без гибкости и универсальности homo sapiens.

С технической точки зрения, выбирая «no-code» решение, вы все равно будете писать программу, пусть и в виде блок-схемы.

Шутка юмора зашла уже настолько далеко, что появился термин no-code разработчик, так что это не мои выдумки или троллинг.

А отказ от традиционных средств разработки и своеобразная «культура отмены программистов» в среде модных стартаперов приводит ровно к тому же что и в жизни — вы однажды проснетесь обосранным, с пониманием что все про#бали из-за чужого навязанного мнения.