Перейти к содержимому

Инженерные истории · 05

Безопасный выпуск в действующий магазин Shopify, который правят совместно

  • GitHub
  • Delivery

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

Shopify OS 2.0 · GitHub integration · Production delivery · Shared-state management · Release safety

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

Конфигурация, которая делает обычный Git опасным

Магазин работает на GitHub-интеграции Shopify. Три темы привязаны к трём веткам: production-тема на live-ветке, интеграционная тема на ветке dev и резервная копия. Важна именно привязка. Когда что-то сливается в live-ветку, Shopify автоматически публикует это на витрину. Нет ни отдельного шага деплоя, ни диалога подтверждения. Слияние и есть деплой.

Так что первое правило написалось само: прямые пуши в live-ветку закрыты, и изменения попадают в неё только через pull request. Слитый PR это релиз в production, задумывал ты это так или нет.

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

Это решает вопрос с кодом. Второе поведение как раз кусается, и оно не имеет отношения к коду.

Команда контента правит магазин каждый день, и Shopify коммитит их правки обратно в Git. Мерчандайзеры меняют тексты, заменяют изображения и переупорядочивают sections через Shopify Customizer в рамках своей обычной работы. GitHub-интеграция Shopify записывает каждую из этих правок обратно в репозиторий как коммит, поэтому история ветки несёт постоянный поток коммитов «Update from Shopify». В этом репозитории 317 из 484 коммитов это те самые автоматические возвраты из Customizer. Две трети истории написаны людьми, которые ни разу не открывали редактор кода.

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

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

Всё остальное в теме, файлы .liquid для sections и snippets, а также assets, пишется в коде. Ими я владею и могу пушить свободно. JSON из опасной зоны я не владею единолично. Относиться к этим двум категориям одинаково и есть та самая ошибка.

Два инцидента, которые научили правилу

Я не вывел опасную зону из первых принципов. Границу мне очертили два инцидента в production.

Инцидент первый: устаревший шаблон перезаписал sections, которые существовали только в редакторе. Я запушил JSON шаблона product со своей локальной ветки. Моя копия была старее магазина, потому что за это время команда контента вставила в эту страницу новые sections через Customizer. Мой пуш заменил живой шаблон моей устаревшей версией, и эти только что вставленные sections исчезли. Их никогда не было в Git. Они существовали только в живом состоянии магазина, которое мой пуш только что перезаписал. Откатываться было не к чему, потому что Git никогда не хранил хорошую версию. Работа команды пропала, а не откатилась.

Инцидент второй: полный пуш на go-live откатил шапку. Во время запуска я выполнил полный theme push, чтобы разом поднять пачку работы наверх. Он откатил текст промо-бара, логотип в шапке и активный выбор меню, весь живой контент, который команда задала через Customizer. JSON section-group шапки был одним из файлов в пуше, а я не подтянул текущую версию заранее. Полный пуш не трогает один файл. Он затирает каждый совместно редактируемый JSON в теме одним движением, так что запуск, который должен был добавить фич, заодно тихо откатил шапку витрины.

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

Рабочий процесс, который я спроектировал в ответ

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

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

Делай бэкап настроек перед любым полным push и по возможности вообще не делай полный push. Инцидент на go-live был полным пушем. Безопасное умолчание это перемещать только те конкретные файлы, которые нужны изменению, и относиться к пушу всей темы как к редкой операции, которая начинается с подтягивания и бэкапа общих JSON.

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

Код пушь свободно, к контенту относись осторожно. Файлы .liquid для sections и snippets, а также assets, пишутся локально. Их я пушу напрямую, без церемоний. Вся дисциплина сосредоточена на четырёх категориях JSON, которые держат состояние, которым я не владею. Точное знание того, какие это файлы, и держит процесс быстрым, а не параноидальным.

Две детали, которые важны на этапе деплоя

Порядок важен, когда section и его шаблон меняются вместе. Если я меняю схему section и заодно меняю шаблон, который его использует, я сначала деплою section, потом шаблон. Shopify вырезает из шаблона любую настройку, которой нет в текущей схеме section. Запушь шаблон первым, и шаблон приедет со ссылками на настройки, которые живой section ещё не определяет, так что Shopify тихо их отбросит. Сначала section, потом шаблон, и настройки уцелеют.

Каждое изменение выходит как один изолированный, откатываемый PR. Это тезис, на который опирается каждая другая статья в этом проекте, и вот почему он верен. Каждая фича это один PR, который делает одну вещь. Если в production что-то пойдёт не так, всё откатывается одним действием: кнопкой Revert в GitHub или git revert -m 1 <merge-sha> на merge-коммите. Поскольку слияние в live-ветку публикуется автоматически, откат тоже публикуется автоматически. Путь отката это тот же одношаговый механизм, что и релиз. Маленькие, узконаправленные PR здесь не вопрос стиля. Именно они делают «откат в один клик» реальным на магазине, где слияние равно деплою.

Тот же урок в другой системе

JSON из опасной зоны это один случай более широкого правила: знай, каким состоянием ты не владеешь. Второй случай это склад. Уровни остатков ведёт Veeqo, внешняя система фулфилмента и учёта запасов, которая пушит количества в Shopify. Отредактируй остаток руками в Shopify Admin, и Veeqo перезапишет его на следующей синхронизации. Та же форма, что и проблема с Customizer, только другая система: источник истины живёт вне той поверхности, которую ты редактируешь, поэтому локальная запись временна и будет затёрта. По этой же причине строчки вроде «Осталось всего 8» на витрине это статический контент, который команда контента задаёт в Customizer, а не живое чтение остатков. Привязать их к реальному складу означало бы драться с Veeqo за власть над числом, которым владеет Veeqo.

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

Итог

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

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

Что я перенёс бы в следующий магазин

  • На совместно редактируемой теме Shopify безопасная доставка это задача проектирования, а не навык Git. Платформа даёт тебе автопубликацию при слиянии и команду контента, которая пишет в те же файлы. Рабочий процесс должен исходить из обоих фактов.
  • Называй своё общее состояние явно. Короткий, заученный список файлов из опасной зоны (templates/*.json, два файла section-group, settings_data.json) стоит больше, чем общее ощущение осторожности.
  • Подтягивай перед тем, как пушить файлы, которыми ты не владеешь, и никогда не делай полный push без бэкапа. Большая часть риска живёт в операциях над всей темой.
  • Сделай откат полноценной возможностью, удерживая каждое изменение в одном PR. Безопасность отката покупается на этапе написания, а не на этапе инцидента.
  • Для каждого значения на странице спрашивай, кто владеет его источником истины. Customizer владеет контентом, Veeqo владеет остатками. Перезапись любого из них не из того места это одна и та же ошибка дважды.

Технические детали

  • Платформа: Shopify Online Store 2.0, GitHub-интеграция (привязка ветки к теме, автопубликация при слиянии в live-ветку), 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 (одношаговый откат).
  • Порядок деплоя: когда схема section и его шаблон меняются вместе, деплой section перед шаблоном, чтобы Shopify не вырезал настройки шаблона, отсутствующие в схеме section.
  • Смежное общее состояние: склад ведётся внешне через Veeqo; ручные правки остатков в Shopify перезаписываются на следующей синхронизации.
Назад к кейсуLumway

Нужен инженер Shopify, который работает по всему коммерческому пути?

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

Начать разговор