Перейти до вмісту

Інженерні історії · 05

Безпечні релізи в живий магазин Shopify зі спільним редагуванням

  • GitHub
  • Delivery

Проєктування робочого процесу доставки навколо двох особливостей Shopify: гілки прив'язані до тем, а злиття автоматично публікуються, і контент-команда цілий день спільно редагує ті самі файли через Customizer.

Shopify OS 2.0 · Інтеграція GitHub · Доставка в продакшн · Керування спільним станом · Безпека релізів

Це не історія про знання Git. Git тут як даність. Історія про те, що Git робить із робочим магазином Shopify, коли ти користуєшся ним звичним способом, бо дві особливості платформи перетворюють рутинний робочий процес на знищувач робочого контенту. Я засвоїв обидві дорогою ціною, а потім спроєктував процес доставки навколо них. Саме завдяки цьому процесу кожна інша частина цього проєкту могла випускати по одній зміні за раз і відкочуватися одним кліком.

Налаштування, через яке звичайний Git стає небезпечним

Магазин працює на інтеграції GitHub від Shopify. Три теми прив'язані до трьох гілок: продакшн-тема на робочій гілці, інтеграційна тема на гілці dev і резервна копія. Ключова частина саме прив'язка. Коли щось зливається в робочу гілку, Shopify автоматично публікує це на вітрину. Немає окремого кроку розгортання і немає діалогу підтвердження. Злиття це і є розгортання.

Тож перше правило написалося саме собою: прямі пуші в робочу гілку закриті, і зміни потрапляють туди лише через pull request. Злитий PR це продакшн-реліз, незалежно від того, чи ти цього хотів.

Кожен розробник працює на власній неопублікованій копії теми для попереднього перегляду. Ти вказуєш Shopify CLI на свою тему через .env, доданий у gitignore, запускаєш shopify theme dev і отримуєш синхронізацію локальних файлів із темою в реальному часі, яка ніколи не зачіпає ані чужий попередній перегляд, ані вітрину. Твої експерименти лишаються твоїми, доки не стануть PR.

Це вирішує питання коду. Друга поведінка це та, що завдає клопоту, і вона не має нічого спільного з кодом.

Контент-команда редагує магазин щодня, і Shopify комітить їхні правки назад у Git. Мерчандайзери змінюють тексти, замінюють зображення і переставляють секції через Shopify Customizer у межах своєї звичайної роботи. Інтеграція GitHub від Shopify записує кожну з цих правок назад у репозиторій як коміт, тож історія гілки несе постійний потік комітів «Update from Shopify». У цьому репозиторії 317 із 484 комітів це ті автоматичні цикли обміну з Customizer. Дві третини історії написали люди, які жодного разу не відкривали редактор коду.

Наслідок легко сформулювати і легко забути: жменька файлів JSON це спільний стан, а не локальні файли, якими я володію. Вони містять актуальний мерчандайзинг команди. Моя локальна копія цих файлів застаріває тієї миті, коли мерчандайзер торкається магазину. Файли, що несуть цей контент, ті, які я почав вважати небезпечною зоною, це:

  • templates/*.json (які секції показує сторінка, у якому порядку, з якими налаштуваннями)
  • sections/header-group.json і sections/footer-group.json (контент шапки і підвалу: логотип, промо-панель, вибір меню)
  • config/settings_data.json (глобальні налаштування теми)

Усе інше в темі, файли .liquid секцій і snippets та assets, створюється в коді. Ними я володію і можу пушити вільно. JSON з небезпечної зони я не володію одноосібно. Ставитися до цих двох категорій однаково це якраз і є помилка.

Два інциденти, що навчили правила

Я не вивів небезпечну зону з перших принципів. Межу мені накреслили два продакшн-інциденти.

Інцидент перший: застарілий шаблон перезаписав секції, які існували лише в редакторі. Я запушив JSON шаблону product зі своєї локальної гілки. Моя копія була старіша за магазин, бо за цей час контент-команда додала на ту сторінку нові секції через Customizer. Мій пуш замінив робочий шаблон моєю застарілою версією, і ті щойно додані секції зникли. Їх ніколи не було в Git. Вони існували лише в актуальному стані магазину, який мій пуш щойно перезаписав. Не було до чого відкочуватися, бо Git ніколи не тримав правильної версії. Робота команди зникла, а не відкотилася.

Інцидент другий: повний пуш під час запуску відкотив шапку. Під час запуску я виконав повний theme push, щоб підняти пакет роботи за один раз. Це відкотило текст промо-панелі, логотип шапки і активний вибір меню, увесь актуальний контент, який команда налаштувала через Customizer. JSON групи секцій шапки був одним із файлів у пуші, а я не підтягнув спершу поточну версію. Повний пуш не зачіпає один файл. Він затирає кожен спільно редагований JSON у темі за один рух, тож запуск, який мав додати функції, непомітно відкотив шапку вітрини водночас.

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

Робочий процес, який я спроєктував у відповідь

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

Pull перед push для будь-якого файлу з небезпечної зони. Перш ніж торкнутися одного з тих файлів JSON, я спершу підтягую поточну робочу версію: shopify theme pull --only <file>. Це фіксує все, що контент-команда зробила відтоді, як я востаннє синхронізувався. Потім я накладаю свою зміну поверх їхнього поточного стану і лише тоді пушу. Моя правка стає доповненням до їхнього актуального контенту, а не його заміною. У репозиторії це виглядає як невеликі коміти-фіксації, що записують стан Customizer безпосередньо перед тим, як зміна потрапляє всередину.

Резервуй налаштування перед будь-яким повним пушем і краще взагалі ніколи не роби повний пуш. Інцидент під час запуску був повним пушем. Безпечний варіант за замовчуванням це переносити лише конкретні файли, потрібні для зміни, і ставитися до пушу всієї теми як до рідкісної операції, що починається з підтягування і резервування спільного JSON.

Ніколи не пуш, поки запущено theme dev. Синхронізація в реальному часі і ручний пуш борються за ті самі файли, і програє зазвичай робочий контент.

Пуш коду вільно, з контентом обережно. Файли .liquid секцій і snippets та assets створюються локально. Їх я пушу прямо без церемоній. Уся дисципліна зосереджена на чотирьох категоріях JSON, що містять стан, яким я не володію. Точне знання того, які саме це файли, і є тим, що тримає процес швидким, а не параноїдальним.

Дві деталі, що мають значення під час розгортання

Порядок має значення, коли секція та її шаблон змінюються разом. Якщо я змінюю схему секції і водночас змінюю шаблон, який її використовує, я розгортаю спершу секцію, потім шаблон. Shopify прибирає будь-яке налаштування з шаблону, коли цього налаштування немає в поточній схемі секції. Запуш шаблон першим, і він прибуде з посиланнями на налаштування, яких робоча секція ще не визначає, тож Shopify непомітно їх відкидає. Спершу секція, потім шаблон, і налаштування виживають.

Кожна зміна виходить як один ізольований PR, який можна відкотити. Це теза, на яку спирається кожна інша стаття в цьому проєкті, і ось чому вона правдива. Кожна функція це один PR, що робить одну річ. Якщо в продакшні щось піде не так, це відкочується однією дією: кнопкою Revert у GitHub або git revert -m 1 <merge-sha> на коміті злиття. Оскільки злиття в робочу гілку автоматично публікується, відкат теж автоматично публікується. Шлях відкату це той самий однокроковий механізм, що й реліз. Невеликі PR з однією метою тут не питання стилю. Саме вони роблять «відкат одним кліком» реальним у магазині, де злиття дорівнює розгортанню.

Той самий урок в іншій системі

JSON з небезпечної зони це один випадок ширшого правила: знай, яким станом ти не володієш. Другий випадок це залишки товару. Рівнями запасів керує Veeqo, зовнішня система фулфілменту та обліку запасів, яка проштовхує кількості в Shopify. Відредагуй залишки вручну в Shopify Admin, і Veeqo перезапише їх під час наступної синхронізації. Та сама форма, що й проблема з Customizer, інша система: джерело істини живе поза поверхнею, яку ти редагуєш, тож локальний запис тимчасовий і буде затертий. Це також причина, чому рядки на вітрині у стилі «Залишилося лише 8» це статичний контент, який контент-команда задає в Customizer, а не читання актуальних залишків. Прив'язати їх до реальних запасів означало б боротися з Veeqo за владу над числом, яким володіє Veeqo.

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

Результат

Підтверджений результат це процес, а не метрика. Після впровадження робочого процесу контент із Customizer більше не втрачався. Файли з небезпечної зони підтягуються перед пушем, повні пуші спершу резервуються, а релізи виходять як окремі PR, які можна відкотити. Два інциденти, що сформували правила, більше не повторювалися.

Очікувана бізнес-цінність випливає з цього, сформульована як захист, а не як виміряний приріст. Щоденна робота контент-команди більше не є побічною втратою під час розгортання коду, що захищає актуальний мерчандайзинг і довіру команди до того, що інженерія не відкотить непомітно їхню роботу. Регресії, що впливають на дохід, отримують однокроковий відкат, бо кожна зміна ізольована. І запуски водночас стали швидшими: нова цільова сторінка це копія шаблону, у якій замінено лише прив'язки товарів, а решту контент-команда заповнює через Customizer, на шляху, який більше не сприймає їхні правки як конфлікт злиття, що чекає нагоди статися.

Що я візьму із собою в наступний магазин

  • У спільно редагованій темі Shopify безпечна доставка це проблема проєктування, а не навичка Git. Платформа дає тобі автопублікацію під час злиття і контент-команду, що пише в ті самі файли. Робочий процес має враховувати обидва.
  • Називай свій спільний стан явно. Короткий, вивчений напам'ять список файлів небезпечної зони (templates/*.json, два файли груп секцій, settings_data.json) вартий більше, ніж загальне відчуття обережності.
  • Роби pull перед push файлів, якими ти не володієш, і ніколи не роби повний пуш без резервної копії. Більшість ризику живе в операціях над усією темою.
  • Зроби відкат повноцінною функцією, тримаючи кожну зміну в одному PR. Безпека відкату купується під час написання, а не під час інциденту.
  • Питай для кожного значення на сторінці, хто володіє його джерелом істини. Customizer володіє контентом, Veeqo володіє залишками. Перезаписати будь-яке з них не з того місця це та сама помилка двічі.

Технічна довідка

  • Платформа: Shopify Online Store 2.0, інтеграція GitHub (прив'язка гілки до теми, автопублікація під час злиття в робочу гілку), Shopify CLI.
  • Попередній перегляд: особисті неопубліковані копії dev-тем; shopify theme dev для односторонньої синхронізації локальних файлів із темою; --theme-editor-sync, щоб підтягувати JSON з Customizer назад у локальні файли.
  • Файли спільного стану: templates/*.json, sections/header-group.json, sections/footer-group.json, config/settings_data.json.
  • Основні команди: shopify theme pull --only <file> (зафіксувати стан Customizer перед редагуванням); git revert -m 1 <merge-sha> або кнопка Revert у PR (однокроковий відкат).
  • Порядок розгортання: коли схема секції та її шаблон змінюються разом, розгортай секцію перед шаблоном, щоб Shopify не прибрав налаштування шаблону, яких немає в схемі секції.
  • Пов'язаний спільний стан: запасами зовні керує Veeqo; ручні правки залишків у Shopify перезаписуються під час наступної синхронізації.
Назад до кейсуLumway

Потрібен інженер Shopify, який працює на всьому комерційному шляху?

Я працюю там, де сходяться UX вітрини, логіка товару, обмеження платформи, вимірювання та релізи в продакшн.

Почати розмову