Сучасна диджитал-освіта для дітей — безоплатне заняття в GoITeens ×
Mazda CX 30
×

Как мы оптимизировали деплой и льем код в любое время суток

Привет! Я работаю CTO в компании Poster, облачной системе автоматизации для кафе и ресторанов. За шесть лет мы из днепровского стартапа выросли до второй по величине планшетной POS-системы в Европе.

В статье я поделюсь нашим опытом и расскажу о том, как мы перестраивали процессы, стремясь к удобному и качественному деплою. Когда-то давно СТО (раньше эту должность занимал нынешний CEO Родион Ерошек) заходил на единственный сервер и вручную делал git pull origin master. Сейчас же наш серверный парк перевалил за 100, причем любой программист, прошедший испытательный срок, может задеплоить свой код на продакшен в любое время дня и ночи, и даже CTO обязан прислать соответствующей гильдии код на ревью перед деплоем на первых клиентов.

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

Немного контекста

Poster — это высоконагруженный продукт: с помощью системы ежемесячно пробивается 17 млн чеков в 11 тыс. ресторанах в 90 странах мира. Система состоит из POS-терминала для официантов и админпанели для владельцев. Программа работает на iPad- и Android-планшетах, desktop-версии запускаются под Windows и macOS, есть и веб-версия.

Ядро системы работает на JS + PHP. Нативные приложения используют Objective-C и Swift, Java и Kotlin, Electron и React Native. Часть микросервисов написаны на Python и Node.js. Вся инфраструктура размещается примерно на 75 dedicated и 25 виртуальных и cloud-серверах. В Dev-команде 40 человек.

Система позволяет сторонним разработчикам интегрироваться с ней на четырех уровнях:

  • API с веб-хуками;
  • кастомные страницы в админпанели;
  • отдельные JS-приложения, внедренные в POS-терминал и перехватывающие различные события;
  • другие iOS- и Android-приложения на иных планшетах, взаимодействующие c POS-терминалом внутри локальной сети по отдельному протоколу.

Сегодня в маркетплейсе находится порядка 40 сторонних приложений, плюс еще примерно 400 закрытых личных интеграций.

Техническая автоматизация деплоя

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

Мой совет: нужно постоянно следить за оверинжинирингом и точечно «накрывать огнем очаги сопротивления». Именно стремление к простым, удобным решениям и минимальному оверинжинирингу позволяет компаниям быстро расти. Программисты и другие сотрудники должны так расходовать свое рабочее время, чтобы компания получала от этого максимальную пользу.

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

Сейчас же тесты стали стандартом и входят в Definition of Done практически во всех командах. Хотя мы все еще против бездумного покрытия тестами ради самих тестов.

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

Ручной деплой

В первый год жизни компании, когда в ней работало лишь несколько программистов, CTO выливал код банальным git pull origin master. Этого было достаточно, ведь деплой чего-то нового происходил не каждый день.

Первой ласточкой стала автоматизация миграций. Данные каждого клиента Poster хранятся в отдельной базе. В первый год работы компании, чтобы провести миграцию баз, программисты писали новый метод в контроллере :) Затем происходило переподключение по всем базам и выполнение какого-то SQL-ника или куска PHP-кода. А передача изменений между разработчиками, которым также надо было локально обновить у себя базу, осуществлялась через Skype.

Эта схема накрылась примерно тогда, когда я пришел в компанию. Кто-то забыл скинуть в Skype один из SQL-запросов, и после деплоя система начала работать совсем не так, как надо. Я выбил неделю для устранения этой проблемы и написал библиотеку, которая могла бы гибко мигрировать все базы клиентов или какую-то конкретную.

В то время миграция была одной из нескольких задач, которые я каждый раз выполнял при деплое. Со временем туда добавились задачи по сборке и подготовке CSS- и JS-файлов, и время деплоя существенно увеличилось. При этом надо было постоянно следить за процессом и выполнять следующие команды лишь после успешного завершения предыдущих. Это было долго и неэффективно. Наконец мы решили полноценно автоматизировать сборку. После недолгого поиска среди простых решений выбор пал на Capistrano.

Capistrano

С Capistrano мы прожили около года. Постепенно добавляли туда все больше команд, и время деплоя снова увеличилось. Архитектура Capistrano не позволяла нам гибко ускорять процесс. К тому же, чтобы повысить отказоустойчивость, мы созрели к канареечному деплою. В итоге начали искать альтернативу и перешли на Deployer.

Deployer

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

Со временем мы начали использовать параллельный деплой на все app-серверы одновременно. Это позволяет выливать новый код на десятки серверов в среднем за 5-7 минут. Конечно, бывает и дольше, особенно если есть миграции или же в коде было очень много изменений (но теперь это мало кого беспокоит, так как все выполняется уже без участия разработчиков).

/*
 * Deploy task
 */
task('deploy', [
    'deploy:info',
    'deploy:lock',
    'deploy:prepare',
    'deploy:release',
    'deploy:update_code',
    'deploy:last_commit',
    'deploy:copy_dirs',
    'deploy:copy_files',
    'deploy:npm_install',
    'deploy:build',
    'deploy:shared',
    'deploy:shared_local',
    'deploy:check_migrate_pos',
    'deploy:check_migrate',
    'deploy:symlink_previous',
    'deploy:appcache_generate_data',
    'deploy:prepare_migrate',
    'deploy:symlink',
    'deploy:generate_webp',
    'deploy:reset_opcache',
    'deploy:migrate_pos',
    'deploy:migrate',
    'deploy:update_appcache',
    'cleanup',
    'deploy:unlock',
]);

После перехода на Deployer мы деплоили примерно два года с локальных машин. На тот момент у нас толком не было тестов, поэтому такой процесс нас хоть и с натяжкой, но устраивал. Когда же мы наняли первого Automation QA, то вариантов не создавать полноценный CI-/CD-процесс уже не было. У нас появился выделенный DevOps-инженер, который этим и занялся.

GitLab

CI-/CD-процесс мы организовали в рамках своего GitLab-сервера. Сначала создали pipeline для прохождения тестов, а после этого добавили отдельный pipeline непосредственно для деплоя после принятия MR.

В итоге сейчас все, что нам надо сделать, чтобы вылить какую-то ветку, это поставить MR в GitLab, а затем принять его после прохождения тестов. Либо же сразу выбрать автоматический деплой после прохождения тестов. И все.

4 часа, die(); и 11 514 чеков

Однажды года четыре назад я деплоил ночью большое изменение, которое нельзя было выливать днем: задача требовала вынужденной блокировки аккаунтов для проведения сложных миграций в базах. Хоть я и проверил, чтобы POS-терминалы клиентов запускались, но в сонном состоянии не учел, что это был кеш, который позволяет нашим клиентам работать offline. Довольный результатом работы я пошел спать. А в это время система лежала четыре часа. Тогда у нас еще не было круглосуточной технической поддержки, и до утра меня никто не разбудил... До сих пор с ужасом вспоминаю то утро.

Три года назад после окончания отлова одной из ошибок разработчик забыл <? die();?> в библиотеке, отвечающей за синхронизацию POS-терминалов с бэкендом. Благодаря дикой случайности этот код не ушел на серверы и не поломал нашим клиентам синхронизацию.

В тот же год наш будущий DevOps-инженер вылил код, который за 20 минут поломал закрытие 11 514 чеков. Мы потратили 12 часов, чтобы достать детали клиентских данных из логов и починить их.

К счастью, такие случаи были единичными, но именно благодаря им у нас появились E2E-тесты, а затем API- и unit-тесты. Только пройдя через боль и страдания, бизнес принимает необходимость автоматического тестирования.

Но если быть честным, хотя pipeline c автоматическим прохождением тестов появился у нас полтора года назад, к тому времени мы уже лили код по пятницам и выходным. Причиной этого была культура тестирования, которой мы придерживались давно. Задолго до этого нам пришлось также внедрить ряд инструментов, чтобы автотестирование стало реальностью.

Клонирование баз

Первое, что мы сделали, это автоматизировали клонирование баз с боевых серверов на локальные машины разработчиков. Сначала это был один маленький bash-скрипт, затем второй, третий и так далее, пока все скрипты не превратились в библиотеку poster-core-tools. Сейчас любой разработчик может законтрибутить туда изменение, которое автоматизирует его работу или работу всего отдела. Клонирование баз привело к тому, что мы смогли локально тестировать написанный нами код на данных лояльных клиентов.

QA-инженер

Мы очень долго сопротивлялись появлению у нас QA-инженеров. Как и у многих продуктовых компаний, у нас был стереотип, что разработчики должны писать код, не требующий дополнительного тестирования. И долгое время мы работали именно так: все разработчики тестировали свой код самостоятельно, либо это делали техлиды во время ревью.

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

Stage-сервер

Мы создали свой первый stage-сервер, куда научились выливать любую ветку с любой базой для проверки нового кода. Stage-сервер стал настолько популярным, что нам практически сразу пришлось добавить еще несколько: команда технической поддержки захотела делать то же самое во время обработки клиентских тикетов, чтобы удостовериться в наличии бага.

Когда запросы на stage-серверы превысили десять штук, в poster-core-tools была добавлена возможность разворачивать их автоматически. Сейчас у нас 40 серверов для ручного тестирования и еще 10 для прогона автоматических тестов. При необходимости можем добавить еще пару десятков запуском одной утилиты.

AQA-инженер

Два года назад мы наняли первого AQA-инженера. Нам понадобилось примерно 8 месяцев, чтобы покрыть все основные функции E2E-тестами и создать свой первый pipeline для проверки работоспособности системы.

Параллельно с E2E-тестами мы начали писать API-тесты и развивать культуру написания unit-тестов. Нельзя сказать, что все идет гладко, но практически ежеквартально у нас происходят какие-то изменения в процессе тестирования.

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

Канареечный деплой

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

Сначала такой деплой состоял из beta- и production-веток. Со временем к ним добавились testing, а примерно год назад stable-ветки. В итоге сейчас у нас следующая схема разлива кода:

Beta 5% → Testing 10% → Production 15% → Stable 70%

Все разработчики ежедневно льют свой код в beta, на следующий день он переливается из beta в testing, а еще через день — из testing в production. Каждый понедельник код меняет распложение — из production в stable.

Клиенты находятся в постоянной ротации между ветками согласно простому алгоритму: остатку от деления их ID на 5. При этом на ветки ниже stable стараемся переносить клиентов с наибольшим ID, чтобы «старики» оставались на stable. На beta находится примерно 5% клиентов с остатком, равным 1; на testing — 10% с остатком, равным 2; на production — 15% с остатком, равным 3; а все остальные — на stable.

У нас есть еще три ветки — beta 2, beta 3 и beta 4. На них выливаются большие или сложные изменения, которые требуют неопределенного времени тестирования в боевых условиях на работающих лояльных клиентах. После этого код переносится в стандартную beta-ветку.

Доверяй, но проверяй

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

В самом начале код на боевые серверы выливали основатели компании. Ревью проходило по-разному: где-то код проверялся внимательно, а где-то — по диагонали.

Когда тогдашнему CTO надоело смотреть мой код и мы научились деплоить с помощью Capistrano, все ревью кода и право деплоя негласно перешли ко мне. Так продолжалось примерно полтора года, пока я не стал самым узким местом в ядре и из-за меня не начала падать скорость доставки фич клиентам.

Это был первый урок: мы поняли, что техлид не должен тормозить и надо делегировать право деплоя и ревью другим ребятам. В тот момент я уже занимал позицию CTO, и надо было как-то ускорять процесс. Таким ускорением и стало появление позиций техлидов среди бэкенд- и фронтенд-разработчиков. Эти ребята взяли на себя обязанности ревью и деплоя, что существенно продвинуло нас вперед.

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

Такая система просуществовала примерно полтора года, пока в одной команде по ряду причин не остался один бэкенд-техлид на трех бэкенд-разработчиков. Проводя все время за ревью и деплоем, он начал слегка зашиваться.

Весной прошлого года мы приняли сложное, но революционное на тот момент решение: упразднили позиции техлидов и дали право на деплой всем, кто прошел трехмесячный испытательный срок. Благо тогда в командах уже сформировалась культура кросс-ревью, и такая реорганизация прошла безболезненно. Плюсом стало еще и то, что ответственность за вылитый код переместилась с техлида на автора кода.

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

Примерно три месяца назад на собрании Dev-отдела, я, CEO и Head of Engineering получили запрет на деплой кода без ревью соответствующей гильдии... Так и живем, создавая отдел по бирюзовой методологии.

Подведем итог

Сейчас из инструментов мы используем GitLab (для хранения репозитория) и Continuous Integration. Для Continuous Deployment применяем Deployer, а стартует все также из GitLab.

Каждый раз, когда разработчик пушит свой код в репозиторий, мы прогоняем unit-тесты и проверяем code style для JS и PHP.

После того как ветка готова, разработчик ставит MR. В этот момент запускается расширенный pipeline вместе с деплоем ветки на stage-серверы, зарезервированные под автотесты. В качестве тестов сейчас гоняем API-тесты, приемочные на Codeception и E2E-тесты на Selenium.

Далее порядок работы уже по желанию разработчика: ждет он прохождения автотестов или нет, но должен отдать ветку на ревью и, возможно, на мануальное тестирование. Для проведения мануального тестирования QA-инженеры выливают код на stage-серверы и прогоняют там свои тест-кейсы.

Как только все виды тестов и ревью пройдены, в GitLab принимается MR и код автоматически выливается на клиентов, находящихся на beta-ветке. На следующий день код будет перемещен на testing, а затем на production. С наступлением ближайшего понедельника код из production попадет на stable.

При необходимости разработчик может вылить код не на beta, а на beta 2 / beta 3 / beta 4 и перевести туда нескольких заинтересованных клиентов, чтобы они какое-то время потестили фичу. Но потом она все равно будет влита в beta.

Что дальше

Несмотря на все, мы видим в нашей системе деплоя еще уйму возможностей для улучшения и в ближайшем будущем планируем ее модифицировать. Вот только часть плана:

  1. Добавить в pipeline SonarQube, чтобы ускорить и упростить дальнейшее ревью кода.
  2. Сделать автоматический, а не ручной ежедневный перелив кода между ветками beta ⟶ testing ⟶ production ⟶ stable в рамках канареечного деплоя. Сейчас MR создаются автоматически, но после прохождения всех тестов подтверждаются вручную.
  3. Улучшить алгоритм и автоматизировать выбор клиентов для канареечного деплоя. Уйти от «глупого» остатка от деления и начать использовать более качественное распределение. Например, чтобы на beta, testing и production были клиенты как с большим, так и маленьким количеством чеков, с большим и маленьким меню, клиенты, работающие со всеми типами оборудования, и так далее. Это позволит находить неточности в новом коде раньше, не дожидаясь их попадания на всех клиентов.
  4. Деплоить только после получения определенного количества лайков со стороны гильдии.
  5. Автоматизировать выбор тех, кто будет ревьюить код.

И наконец, мы созрели для полного переосмысления своего текущего подхода к CI/CD, чтобы его можно было масштабировать с основного проекта на сателлитные подпроекты и микросервисы. Пока большая часть из них по-прежнему деплоится вручную без своих pipeline. Этим мы и займемся в скором времени.


Main image by Jonathan Segal


Читайте также: Щоденні релізи: досвід продуктових ІТ-компаній

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось2
До обраногоВ обраному12
LinkedIn

Схожі статті




62 коментарі

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Я тоже использую deployer
Сколько времени у вас занимает сборка JS?
У меня Angular 2 собирает 1 копию проекта до 30 минут (локально на мощном компе до 3 минут) на среднем VPS 2xCPU/4GBRAM+swap/SSD
На одной таком сервере могу разместить до 5 копий проектов (5 клиентов), но билдить их одновременно нереально, потомучто RAM заканчивается и получаем ошибку Java heap memory (node js).

У меня вопрос: как билдить проекты, если их 200+? Выделенный супермощный билд сервер?

Що таке «міграція баз» і чому окрема база для кожного клієнта? У вас одна предметна область, чому не єдина структура бази і таблиці з налаштуваннями клієнтів?

Например, потому что это SaaS сервис.

Разбиение баз помогает с масштабированием — ATБ (образно) на одном сервере, ещё 100 мелких кафешек на другом, несколько средних клиентов на третьем. При расширении бизнеса клиента — база переезжает на отдельный сервер.

Безопасность — везде человеческий фактор, чтоб из-за какой-то проблемы в коде, не получили доступ к информации другого клиента или не удалили.

Maintenance — проще бекапить, восстанавливать мелкую базу, чем одну огромную.

p.s. не являюсь сотрудником данной компании.

Щодо масштабування — є AWS і т.п. В цьому плані тримати десятки інстансів, в т.ч. окремі для великих клієнтів цілком розумно, але тримати кілька сотень інстансів MySQL і всім цим хазяйством рулити — ідея так собі.
Безпека мала б вирішуватись на рівні авторизації, далі юзер отримує персональний Id, плюс Id компанії. Далі запити до бази йдуть з цими Id. Запити з пустим Id компанїї просто не мають шансів пройти DAO layer. В базі всі таблиці мають колонки з Id компанії.
Є проблема хіба що з third-party apps якщо такі існують і мають доступ до бази з якимись особливими правами. Ну і DBA (против лома нет приема).
Щодо maintenance — все вірно... доки кількість таких маленьких баз не перевалить за кілька десятків.
P.S.
Менше з тим, це не зовсім те, що я мав на увазі — чому структура бази різна для різних користувачів, що, схоже, і викликає проблеми при міграції (можливо, я не так зрозумів).

але тримати кілька сотень інстансів MySQL

навіщо?
1 інстанс MySQL кілька сотень баз потягне. на шаред хостах тягне і більше

Безпека мала б вирішуватись на рівні авторизації, ... Далі запити до бази йдуть з цими Id.

Кращий код — ненаписаний код.

просто не мають шансів пройти DAO layer.

з пустим не мають.
з чужим — мають. DAO layer не зможе перевірити бізнес корректність двох послідовних запитів з різних сервісів.

Тому якщо треба, то авторізацію переносять на сервер БД. як і права. От він точно не пропустить.
Так роблять у банківському ПЗ. Бо не довіряють DAO layer
Але у такому підході є свої незручності.

В базі всі таблиці мають колонки з Id компанії.

1. Навряд чи таблиці для різних клієнтів повністю однакові
2. Буде важко вносити зміни у схему тільки для груп клієнтів
3. Ризики при технологічних операціях для одних клієнтів запороти данні інших. Як не тестуй, раз на рік — буде така біда.

Ідеальний спосіб захистити приміщення — не кодовий замок, а відсутність дверей :)

чому структура бази різна для різних користувачів

а так завжди. життя такє — різноманітне.
впевен що мінумум — у різних країнах різні правила, закони, і т.і.

а у різніх видів торгівлі — ще якась різниця.

і думаю це було головною причиною — неможливість обійтись загальною схемою для всіх.

Ну це не справа DAO перевіряти секюріті, як і бінес-коректність. Має бути окремий сервіс авторизації т.п. В даному випадку це скоріше як синтаксичний фільтр для при підготовці SQL, а-ля перевірки на null.
Щодо бізнес правил — в принципі, якісь елементарні речі і бажано в базі робити (а-ля NOT NULL, FK, унікальні ключі і констрейни). Але щодо секюріті на рівні БД — авторизація на рівні ролі/групи працює, на рівні юзера — ніхто його окремо заводити не буде. Маємо якусь роль «клієнта» з певними правами. І ми приходимо до того, про що йшла мова, тільки в менших масштабах (на рівні одієї компанії) — юзер може отримати дані іншого юзера в результаті «програмної помилки».
Але я чому за загальну структуру — у нас код на рівні data-layer один і той же. А структура бази в чомусь надлишкова для кожного окремого клієнта (навіть варіанти «цю таблицю юзає тільки один великий клієнт»). І з часом буде мінятися рідко та для всіх одразу (якщо ми не стартап, звичайно). В той час як різноманітність структури бази для різних клієнтів веде до того, що десь її треба враховувати. Виходить, у нас вже різний код для різних клієнтів.
А так ми виносимо бізнес-логіку в оремий сервіс і маємо окремі бізнес-правила для окремих клієнтів/груп клієнтів, ми зберігаємо ці правила в базі і опрацьовуємо дані згідно них. Більше того, у клієнтів є юзери, які ці бізнес-правила можусть самостійно міняти/додавати.
Тобто у нас є якесь ядро коду, яке юзає загальні або персоналізовані бізнес-правила, і яке залишається спільним для всіх.

а-ля NOT NULL, FK, унікальні ключі і констрейни

це не бізнес правила

це захист цілісності данних, від помилок

2 + «кг»

бізнес правила це коли

на 02.02.2020
2к огірків з Іспаніі додати 5 кг огірків з Єгипту дорівнює 10 для клієнтів які купили помідорів у січні більше 100 кг
а для інших ...
а з 15.02.2020
дорівнює 8 для клієнтів які купили помідорів у січні більше 200 кг
а для інших ...

І коли є:
Отримати загальну продаж огрікив за лютий, з потижневим угупуванням по продажу помідорів за січень

А тепер подумайте про «У вас одна предметна область»
коли у кожного клієнта є отакє одненьке, але унікальне правило.

В теорії це одна предметна область
На практиці — ви не зможете її змоделювати повністю, бо ще не знаєте, і ніхто не знає які будуть правила у березні.

у нас код на рівні data-layer один і той же

це інфраструктурний рівень кода.

І з часом буде мінятися рідко

рідко змінюється ПЗ для космічних супутників.
а у бізнесі зміни — це норма.

В той час як різноманітність структури бази для різних клієнтів веде до того, що десь її треба враховувати.

так треба враховувати — дійсність.

Неможливо зробити програмне рішення простіше за домен.

Виходить, у нас вже різний код для різних клієнтів.

так, бо у різних клієнтів — різні потреби.

А так ми виносимо бізнес-логіку в оремий сервіс і маємо окремі бізнес-правила

це в ідеальному світі.

у реальному світі відокремлення бізнес логіки від персистентних даних призводить до помітного зниження швидкодії.

зустрічав навіть такє:
Ви майже точно робите щось не так коли починаєте думати про написання bussines rules engine, DSL для нього, окремі сервіси для rules і т.п.
Виключення звісно є, і навіть є готові рішення.

Але зазвичай академічні рішення і будуть виглядатаи як архітектурна астронавтика.
Яка ще й ламається дійсністью, коли з`являється:
А огірки з Польщі заборонено додавати до інших огірків.

Тобто у нас є якесь ядро коду, яке юзає загальні або персоналізовані бізнес-правила, і яке залишається спільним для всіх.

А так і вийде, у якомусь вигляді.
Бо робити кожному ще й окреме ПЗ — то занадто дорого.

Ми ж почали з схем БД.

Для мене було б дивно що вона одна для такого різноманіття клієнтів.
Для вас дивно що вона різна для такого різноманіття клієнтів.

У кожного свій досвід, а програмування — не математика.

Ок, бізнес правила різні, база даних різна, сервісу для бізнес-правил нема, але

робити кожному ще й окреме ПЗ — то занадто дорого

?
То де і в якому вигляді ви зберігаєте бізнес-логіку ? І як в неї вносяться зміни ?

То де і в якому вигляді ви зберігаєте бізнес-логіку ?

у вигляді программного коду та параметрів у БД
можливо є і макроси, і розвиток у бік DSL

І як в неї вносяться зміни ?

не бачив ще конспекта усіх тих книжок на сотні сторінок про:
Программування = аналіз, проектування, реалізація у коді, «devops»

не відчуваю в собі таланту написати швиденько такий :)

а немає срібної кулі.

Доброе утро, Виталий.

Valeriy Shvets хорошо расписал три основных преимущества: масштабируемость, безопасность и maintenance — при работе с отдельными базами под каждого клиента. Это создает конечно немного геморроя в виде адаптации разных штук, которые с одной базой работают на ура, а с множеством — нет. Но в дальнейшем это дает отличный результат.

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

Нам не надо со своей стороны беспокоиться про создание id сущностей под каждого клиента, стандартный автоинкремент создает идентификаторы и в итоге у клиентов красивые неразрывные Id. Это очень им и нам помогает в работе.

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

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

А как откатываете фичи которые требуют изменения структуры бд с этапа production? Например 5% с беты уже 3 дня заполняют бд в новом виде, и вдруг факап на этапе прода и нужно откатить — а бд уже обратно не совместима.

Тоже довольно страндарная практика. БД должна быть обратно-совместима несколько следующих релизов, потом можно удалить то, что не нужно. И писать данные в старом и новом формате некторое время. Тут можно подробнее почитать nickcraver.com/...​-deployment-2016-edition.

Мы не откатываем.

90-95% миграций зачастую либо добавляют новые данные и поля, либо же их изменения настолько просты, что можно быстро потом что-то исправить в случае необходимости. Оставшиеся же кейсы наоборот, настолько могут быть сложны, что создание методов для отката данных приведет к тому, что надо будет написать столько же или в два раз больше кода, чем основной.

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

Для перестраховки в сложных случаях мы используем такой флоу:
1) Во-первых, все основные функции покрыты тестами. Соответственно мы знаем, что все, что работало ранее вот так, будет и дальше работать вот так.
2) Дальше мы валиваем такой код на beta2, beta3 или beta4 и переводим туда тестовые аккаунты наших сотрудников. А это может быть порядка 50-70 аккаунтов, с которыми каждый день работает техническая поддержка, менеджеры отдела продаж, QA-инженеры и сами программисты. Держим на этих аккаунтах код порядка недели. Обычно все мажорные баги, если они вдруг прошли тесты, отлавливаются на этом этапе.
3) После этого начинаем на эту ветку переводить клиентов по чуть-чуть раз в два дня: 10, 20, 50, 100 аккаунтов. Так код тестируется еще порядка одной иди двух недель. Параллельно устраняются минорные проблемы.
4) Дальше все по стандартному флоу: выливаем в beta, постепенно переливаем на testing, production и stable.

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

Сейчас у нас нет проблем деплоить в любое время: пятница вечер или понедельник утро.

Можете LaunchDarkly глянути, як варіант реалізації. Насправді, це з одного боку спрощує процес і в разі чого можна просто флаг на проді виключити, з іншого — це створює свого роду паралельний флоу по створенню/включенню/включенню флагів і додаткових джир по їх видаленню коли фіча stable. Плюс коли в тебе на якийсь функціонал впливає, скажімо, три флага, перевірка комбінацій теж займає час.
Ну і в коді це не завжди просто зреалізувати, особливо коли в тебе структура класів/DTO міняється.

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

Это, честно говоря, жесть

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

Все верно. У нас и сейчас разработчики не могут отдать QA на проверку неработающую или работающую не так как надо фичу.

Отдельно хочу заметить, что на ранних стадиях создания продуктовой компании, есть большие ограничения в средствах: время, деньги, люди. Поэтому надо стараться все делать максимально быстро и с минимальными временными затратами на лишние процессы. Иначе можно не дойти до точки самоокупаемости.

у нас появились E2E-тесты, а затем API- и unit-тесты

все ведь должно было быть наоборот. но конечно лучше поздно чем никогда.
В целом интересная статья.

Как раз от нескольких e2e/api тестов пользы может быть в разы больше, чем от армии юнит тестов. Особенно учитаывая затраченные усилия. Особенно когда в системе до этого тестов вообще не было. Принцип Парето 🙂.

тут класическая истории компании которая пришла к тестированию Е2Е после личного опыта что все таки оно надо. А потом от Е2Е к класической автоматизиованой пирамиде тестирования где все начинаеться с юнит тестов.

Большинство компаний где разработчики работают без тестировщиков они наооборот покрывают юнитами все что могут. Конечно это не словит всех багов в конце концов. А принцип Парето тут применим ко всем ступенькам пирамиды одинаково а не к тому что Е2Е Автотесты словят 80% багов а апи и юниты 20% тут все прямо пропорционально к покрытию, которое намного легче сделать и быстрее прогонять и поддерживать в средне-долгосрочной перспективе.

Так классическая пирамида становится все менее и менее актуальной с тем подходом, что unit тесты работают только с объектами в памяти. С разными docker/k8s довольно легко развернуть инфраструктуру и писать тесты уровна API/Message хендлеров с полноценным доступом к данным и всем остальным. Мокая только внешние сервисы.
Я про принцип Парето как раз в проекте, где тестов было мало или не было совсем. Только unit тесты кардинально картину не поменяют, а усилия для их написания буду довольно большими. А вот покрыть критичные кейсы e2e или api-тестами — сразу повысит уверенность команды значительно. При чем, опять же, по скорости они могут быть очень приемлимыми. Когда речи идет о миллисекундах даже увеличение времени теста на порядок особо не влияет. 50-500ms — это все равно довольно быстро.

Поддерживаю. Сейчас многие гонятся за большИм покрытие кода тестами, хотя если есть e2e, то они будут покрывать большинство unit-тестов и они фактически не нужны.

Ну как бы по канонам считается, что e2e тесты дороги в обслуживании и должны покрывать только самый критический функционал, а как раз за счёт интеграционных тестов и юнит тестов можно обеспечить стабильный продукт

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

Что вы понимаете под пересечением unit тестов с другими тестами? Я могу с уверенностью сказать Вам что качественного покрытия от e2e Вы не добьётесь, так как написание е2е процесс трудозатратный,а также эти тесты очень тяжело сапортить. E2E хорошо применять только для отдельных участков продукта

E2E хорошо применять только для отдельных участков продукта

Я с этим не спорю, а наоборот поддерживаю. Все зависит от продукта: для некоторых случаев e2e достаточно постые, где-то наоборот очень сложные.
Если вы например покрыли unit-тестами какой-то сервис и у вас есть e2e, которые в полной мере используют функционал этого сервиса, то это и есть пересечение.
У меня был проект, где одним из важных компонентов был сервис по просчету Excel-документов с кастомными функциями. По-хорошему, при добавлении новой функции, нужно писать соответствующий unit-тест. Но QA всегда такие кейсы добавляли в e2e, потому как это основа функционала. В этом случае unit-тесты были просто излишними.

Если с головой — можно и большой проект покрыть качественно за человекогод

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

Насколько я понимаю, то весь подход TDD родился именно из этой проблемы — потому что код, который не написан изначально для юнит тестирования зачастую сначала нужно перерефакторить основательно, чтобы можно было тесты вообще подкрутить. И если кода много, то сколько еще времени уйдет после этого на написание какого-то достаточного числа тестов, чтобы все это можно было бы безболезненно продолжать выкатывать в лайв?

Многие все еще вкладывают в юнит тесты не тот смысл, который в них вкладывался изначально. Юнит — это не метод класса. Методы класса внутри сервиса (как и сами классы) — это детали реализации. Юнит — это публичный контракт. Изоляция — не от других классов, а от других тестов. 🚀 DevTernity 2017: Ian Cooper — TDD, Where Did It All Go Wrong.

А тут уже от разработчика зависит, что он хочет протестировать. Если детали реализации какого-то публичного интерфейса несут в себе достаточно своей логики, которую хотелось бы проверять — то вполне можно и публичный интерфейс протестить и частный случай имплементации этого интерфейса.
Юнит тесты это тесты для _разработчиков_ чтобы увидеть _где_ что-то сломалось при появлении регрессии а не тратить пол дня расставляя здесь и там println, когда упадет e2e тест, который покажет, _что_ сломалось (с точки зрения функциональности), но не _где_.

Каждый грызет кактус, который ему нравится. Если тестить методы/классы, то такие тесты становятся очень хрупкими. Малейшее изменение реализации, которое не меняет поведения, ведет к сломанным тестам.

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

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

...что его нужно переписывать

Проблема в тому, що рефакторити код, у якого немає тестів, трохи стрьомно :) Особливо коли він настільки заплутаний, що незрозуміло що робить.
Тому приходиться спочатку писати на нього тести — що він мав би робити.

Первое, что мы сделали, это автоматизировали клонирование баз с боевых серверов на локальные машины разработчиков.

- как на это смотрит бизнес?

Обфускаторы разные для приватных данных, например. Не обязательно копировать все данные клиента, можно только какой-то хеш, который с реальным пользователем/данными связать нельза. Собственно так и работают там, где нужно GDPR.

Если у меня есть доступ к данным, пусть даже вместо названий ресторанов — хеш, но очень хочется найти конкретный ресторан то это не так сложно. Пойти в нужный ресторан, сделать нетипичный заказ вида «кофе, кефир, соленые огурцы», потом искать его в базе в чеках за день. Даже если вместо названий блюд тоже хеши, есть сумма такого заказа и цена каждой позиции (кофе, кефира, соленых огурцов). В конце концов можно сопоставить цены в интересующем ресторане (из его меню) с ценами в чеках из БД.

И чо? Клиенты сами соглашаются чтобы данные ресторана (меню/заказы/цены) можно было просмотреть. Суть в том, чтобы нельзя было сопоставить инфу по заказам с конкретным человеком, что нарушило бы GDPR.

Пойти в нужный ресторан, сделать нетипичный заказ вида «кофе, кефир, соленые огурцы», потом искать его в базе в чеках за день.

Ресторан — это не физлицо, поэтому приватность таких данных не гарантирована законами. Регулируется только EULA, если там указано «мы можем использовать эти данные для тестирования», то все ок.

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

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

Похоже бизнес не понимает что такое GDPR. Такие действия нарушают права пользователей.

Как только бизнес оштрафуют на 20M евро, сразу все поймут. Кстати, оштрафовать могут и вас, так как фактически вы производите несанкционирование использование персональных данных.

для таких вещей придумали предпродакшн версию прода — stage. Это версия платформы, которая максимально приближена к продакшну.

Не вижу никаких проблем (у самого так в компании): при снятии дампа базы личные данные анонимизируются. После этого это уже не личные данные. Связать что-то с клиентом нельзя.

Полезная штука, а если добавить слово «анонимизированные», то и с GDPR проблем не будет.

Денис, хорошая статья, спасибо что поделился! 👍

Сделать автоматический, а не ручной ежедневный перелив кода между ветками beta ⟶ testing ⟶ production ⟶ stable в рамках канареечного деплоя.

Кажется в этом случае вы всё ближе к trunkbaseddevelopment.com и martinfowler.com/...​cles/feature-toggles.html

Привет, Макс. Спасибо :)

Если честно, то мы никогда не пытались работать с ветками в рамках какого-то общепринятого подхода.

Наши разделения и правила вырабатывались эмпирически. Когда произошел какой-то факап года четыре назад, вылили код и попортили работу терминалов, поняли, что мы не можем выливать новый код сразу на всех клиентов. Тогда и ввели beta, потом через время testing так далее.

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

Оч круто! Спасибо за статью под кофе ))) и удачи Вам, одни из немногих в Днепре достойных ))

Рік тому у вас 80% клієнтів були Москва і Пітєр, на ринок яких, фактично, команда і працювала.
Ну, принаймі так мені пояснила ситуацію ваш рекретер під час знайомства по skype.

У вас щось змінилось? Просто фраза

второй по величине планшетной POS-системы в Европе

звучить трохи дивно. Ви там одні на ринку чи що?

Недавно просматривал этот рынок, походу одни. Альтернатив гугл не видел

Ну, во-первых, Україна — це Європа! А во-второых, какое это отношение имеет к теме?

До того, що це стало для мене достатньою причиною для відмови. І судячи з голосу рекретуреа — таких досить багато, щоб намагатися не акцентувати на цьому увагу.

Ви можете не вірити, але я не засуджую подібні речі. Мені насправді цікаво було зрозуміти масштаби ринку. І якщо «найбільший в Європі» — це Москва + Пітєр, то це хороше уявлення про стан ринку.

І до чого тут відношення України до Європи, якщо «замовник» фіч — Москва та Пітєр?

Та я помню, ты уже об этом писал. Норм повод еще одит политотосрачик развязать.

Евгений, среди планшетных POS-систем мы сейчас действительно делим первое и второе место с немецкой orderbird. У нас темп подключения новых клиентов лучше, поэтому в 2020 году выйдем уже на четкое первое место.

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

нєєє, рано!
Буду читать завтра =)

Спасибо за историю. Но если честно, несколько раз холодным потом облился, пока читал. Особенно про чеки. Сам работал с pos-терминалами, много геммора из-за разнообразия форматов и прочего

Підписатись на коментарі