Предыстория проста. В нашем банке существует множество продуктов, контент которых нуждается в едином способе управления. Одни продукты построены на классических CMS, другие вовсе требуют усилий разработчиков для обновления информации на портале. Ситуация усугубляется требованием миграции всех продуктов в банковскую облачную платформу, построенную по принципу PaaS. Это означает, что поддержка какого то определённого сервиса (Kafka, Elasticsearch, PorstreSQL, и т. д.) или, тем более, системы (WordPress, Magento, Drupal, и т. д.) требует согласования и разработки в рамках платформы. И если абсолютно всех и в долгосрочной перспективе устраивает пресловутый WordPress, то с учетом прочих приоритетов его поддержка будет реализована со временем.

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

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

  • Что такое контент?

  • Как готовить?

  • Где хранить?

  • Как управлять?

  • Как поставлять?

Если вы хорошо знакомы с терминами Headless CMS, Content as Code, SSG, SSR, CDN и прочими трёхбуквенные аббревиатуры, то листайте сразу в комментарии, чтобы поделиться опытом.

Код

Один мой хороший товарищ в детстве мечтал стать программистом и представлял себе этот процесс как диалог с компьютером. Описываешь желаемое, отвечаешь на уточняющие вопросы и вуаля — приложение готово. Каково же было его «удивление», когда ему, в результате, пришлось программировать микроконтроллеры семейства 8051 на языке C51 и ассемблере.

С тех пор многое изменилось. Нейросети нашли широкое применение после стольких лет «забвения», стат. анализ претерпел ребрендинг и обрёл популярность в виде data science, визуальное программирование постепенно возрождается в новом качестве... Диалог с PC (и не только), хоть и не в контексте программирования, стал привычным/массовым явлением. Параллельно с этими процессами эволюционировало и понимание того, что именно можно называть «кодом», и это название теперь применимо к очень разным вещам.

Так каким может быть «код» сегодня?

Гипотетический сайт (React/JSX):

export const MainPage = () => (
  <TilesList>
	  <Tile link="/products" back={productsImage}>Цифровые <em>продукты</em></Tile>
	  <Tile link="/startups" secondary back={startupImage}>Стартапы</Tile>
	  <Tile link="/come-with-us" secondary>Стать частью <em>команды</em></Tile>
  </TilesList>
)

AWS CDK декларация SQS очереди, лямбда функции для отправки сообщений в очередь и REST API «вокруг» этой функции. По сути это конфигурация, которая может быть преобразована в YAML формат:

const myVpc = new ec2.Vpc(this, "MyVPC");
const myCluster = new ecs.Cluster(this, "MyCluster", { vpc: myVpc });

const myQueueProcessingService = new ecs_patterns.QueueProcessingFargateService(this, "MyQueueProcessingService", {
  cluster: myCluster,
  image: ecs.ContainerImage.fromAsset("my-queue-consumer")
});

const myFunction = new lambda.Function(this, "MyFrontendFunction", {
  runtime: lambda.Runtime.NODEJS_14_X,
  handler: "index.handler",
  code: lambda.Code.asset("my-front-end"),
  environment: {
    QUEUE_NAME: myQueueProcessingService.sqsQueue.queueName
  }
});
myQueueProcessingService.sqsQueue.grantSendMessages(myFunction);

const myApi = new apigateway.LambdaRestApi(this, "MyFrontendApi", {
  handler: myFunction
});

Термостат (нечеткая логика):

ЕСЛИ Температура ЕСТЬ Очень_Холодно ТО Мощность ЕСТЬ Максимум
ЕСЛИ Температура ЕСТЬ Холодно ТО Мощность ЕСТЬ Норма
ЕСЛИ Температура ЕСТЬ Норма ТО Мощность ЕСТЬ Минимум
ЕСЛИ Температура ЕСТЬ Тепло ТО Мощность ЕСТЬ Выключено

Всё это примеры кода, призванные показать многогранность этого понятия. Многое можно обозначить ёмким термином код, в самых разных областях.

Контент

???? Что такое контент? Как готовить?

Уверен, что ценность подобных определений не высока, но если коротко, то контент — это информационное наполнение системы, доступное пользователям, то ради чего информационная система (далее ИС) и создаётся.

Разработчики ИС зачастую рассматривают контент, который система должна предоставлять конечному пользователю, как нечто второстепенное по отношению к коду. Простейший пример подобного пренебрежения — тестирование карточки пользователя на именах вроде Иван Иванов и адресах электронной почты вроде test@test.com. Результат появления в системе пользователя по имени Венедикт Вознесенский с e-mail’ом venediktvoznesenskiy@ipvoznesenskiy.ru каждый может представить самостоятельно.

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

Давайте посмотрим, каким образом можно минимизировать проблемы, связанные с контентом, при проектировании гипотетической ИС.

Хотя граница между кодом и контентом не всегда очевидна и не всегда стоит выделять контент как самостоятельную сущность. При этом надо понимать, что отделение сущностей упрощает управление ими и, вместе с тем, усложняет конечное решение.

Возьмём вот такой пример кода:

export const MainPage = () => (
  <TilesList>
	  <Tile link="/products" back={productsImage}>Цифровые <em>продукты</em></Tile>
	  <Tile link="/startups" secondary back={startupImage}>Стартапы</Tile>
	  <Tile link="/come-with-us" secondary>Стать частью <em>команды</em></Tile>
  </TilesList>
)

Этот листинг представляет главную страницу некоего сайта, которая состоит из плиток. Каждая плитка по факту является ссылкой на определённую страницу и имеет фон в виде некоего изображения.

Что в данном случае будет «контентом»? Имеет ли смысл отделять его от непосредственно «кода»?

Если задаться подобными вопросами, то можно перепроектировать наш компонент, вынеся заголовки, ссылки и адреса изображений в отдельный файл в формате JSON:

{
  "tilesData": [
    {
      "title": "Цифровые <em>продукты</em>",
      "link": "/products",
      "image": "products.png"
    },
    {
      "title": "Стартапы",
      "link": "/startups",
      "secondary": true,
      "image": "startups.png"
    },
    {
      "title": "Стать частью <em>команды</em>",
      "link": "/come-with-us",
      "secondary": true,
      "image": "come-with-us.png"
    }
  ]
}

Компонент видоизменяется сообразно:

export const MainPage = () => (
  <TilesList>
    {tilesData.map(({ title, link, secondary, image }) => (
      <Tile key={link} link={link} secondary={secondary} back={<BackImg src={image} />}>
        <span dangerouslySetInnerHTML={{ __html: title }} />
      </Tile>
    ))}
  </TilesList>
)

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

Никак нет.

Стало сложнее — да, остальное — под вопросом. Были вынесены данные из конкретного компонента. Данные остались на уровне представления. Терминология и структура не изменились. По сути, это всего лишь мудрёная вариация оригинального листинга. Особенно если вспомнить, что разница между треугольными скобками в JSX и фигурными в JSON — не велика.

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

Давайте попробуем спроектировать контент сверху вниз. Наш гипотетический сайт предоставляет N секций. Секция — это заголовок, иллюстрация и содержание (почти как новость в ленте). То есть ключевой момент в проектировании контента — это достичь чёткого понимания, какая именно информация должна быть предоставлена пользователю.

products.json

{
  "title": "Цифровые *продукты*",
  "preview": "products.png",
  "content": "Содержание первой секции в формате `Markdown`"
}

startups.json

{
  "title": "Стартапы",
  "preview": "startups.png",
  "content": "Содержание второй секции в формате `Markdown`"
}

come-with-us.json

{
  "title": "Стать частью *команды*",
  "preview": "come-with-us.png",
  "content": "Содержание третьей секции в формате `Markdown`"
}

Что изменилось в качественном смысле?

  • Используется более общая терминология

  • Контент секций теперь размещён в отдельных файлах, так как фильтрация, группировка и сортировка зависят от контекста

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

  • Применён Markdown как более безопасный и платформенно-независимый формат

  • Появилось поле content

Уже лучше, хотя проще не становится.

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

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

Список смежных вопросов при проектировании контента на этом не заканчивается:

  • Доставку какого контента необходимо оптимизировать через статическую генерацию?

  • Какой контент необходимо проиндексировать, чтобы пользователь мог «задавать вопросы» сложнее, чем просто переход по URL?

  • Какой CDN использовать для доставки изображений?

  • Где хранить и как управлять более тяжелым контентом (видео и аудио)?

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

Итак, контент представлен JSON документами и изображениями, расположенными прямо в кодовой базе. Выглядит по меньшей мере непривычно. Напрашивается перенос контента в некую БД.

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

Рассмотрим варианты архитектур в контексте вопросов «Где хранить?» и «Как управлять?».

Управление Контентом

???? Где хранить? Как управлять?

Вопросы, вынесенные в подзаголовок, породили обширный класс решений, именуемых Системы Управления Контентом (далее CMS).

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

Классическая CMS

Плюсы:

  • Вся функциональность предоставляется из коробки (от управления контентом до развертывания конечной ИС)

  • Множество готовых решений для типовых ИС (блог, интернет-магазин, и т.д.)

  • Конструирование типовой ИС практически не требует компетенций разработчика

  • Как правило, всё настраивается и многое можно расширить через готовые плагины

  • Готовое решение для управления изображениями, аудио- и видео- контентом

  • SEO-оптимизация тоже из коробки

Минусы:

  • Требует специфических компетенций от команд разработки и сопровождения

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

  • Готовые широко используемые решения, как правило, более уязвимы с точки зрения безопасности

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

  • Инертность сопровождения коробочных решений

  • Как правило, слабая оптимизация скорости загрузки ИС (SEO)

Слабые стороны классических CMS являются продолжением сильных сторон и вытекают именно из стремления автоматизировать все аспекты разработки ИС в виде коробочного решения.

Headless CMS

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

Суть Headless CMS в том, что «тело» (бэкенд) полностью отделено от «головы» (фронтенда). Изобретённые как противоположность классических CMS, «безголовые» CMS обращают плюсы и минусы классических CMS в их противоположности.

Главным преимуществом решений этого класса является полное отделение контента (и процесса управления им) от его представления и доставки конечным пользователям. Что означает полную свободу выбора стека для создаваемой/изменяемой ИС. Также немаловажным оказывается и то, что единый интерфейс редактирования для всех каналов доставки контента упрощает работу редакторов.

Тем не менее с большой силой приходит и большая ответственность, и при использовании «безголовой» системы управления контентом, многие вещи, которые тот же WordPress берёт на себя, нужно будет внедрять самостоятельно. При этом, очевидно, контроль за ними оказывается стопроцентным и вы вольны изменять эти части системы сообразуясь с потребностями бизнеса/своим чувством прекрасного.

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

Content as Code подход в рамках Headless CMS

В рамках Headless CMS существует подмножество решений эксплуатирующих подход Content as Code. Да, наконец-то это понятие упоминается прямым текстом...

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

Данная архитектура требует пояснений.

Пунктир на диаграмме означает условную принадлежность перечисленных компонент к CMS. По сути CMS сводится к административному интерфейсу, который взаимодействует с Системой Управления Версиями через (GitLab/GitHub/...) API.

Контент и код могут существовать бок о бок в следующих вариациях:

  • В одном репозитории (единый Git Flow)

  • В одном репозитории, но в разных ветках (параллельные Git Flow)

  • В разных репозиториях (этот вариант отлично подходит для "большого" контента)

На этапе сборки ИС происходит интеграция кода и контента. А страницы с наиболее востребованной информацией могут быть заранее сгенерированы. Такой подход принято называть Static-Site Generation (далее SSG).

База данных «Документы», изображенная на диаграмме, является опциональным компонентом. И пополняется контентом, который нуждается в индексации, через пайплайн в ответ на изменения в репозитории с контентом. Наличие этого компонента позволяет осуществлять сложные поисковые запросы по контенту в runtime. Разработчики ИС вольны выбирать любую документо-ориентированную БД.

CMS в таком варианте вынуждена эксплуатировать провайдеры аутентификации и ролевую модель соответствующей VCS. К примеру:

  • Под ролью «Редактора» может подразумеваться роль Developer

  • А под ролью «Главного Редактора» — роль Maintainer

Плюсы

  • Простота архитектуры и свобода выбора вспомогательных компонент

  • Свобода выбора стека ИС

  • Версионирование контента (можно откатить к старой версии)

  • Совместное редактирование контента через процесс ревью (MR, PR)

  • Возможность проверить интеграцию кода и контента до выпуска в прод

  • Миграция контента между окружениями сводится к созданию merge commit

Минусы:

  • Необходимость выбора сторонних компонент для построения полноценной ИС

  • Инертность CI/CD делает контент доступным на стенде через минуты (в отдельных случаях десятки минут)

  • Необходимость учёта лимитов VCS по рекомендованному количеству файлов и их общему объёму в расчёте на один репозиторий (для GitLab это ~50K файлов и ~1Gb соответственно)

  • SEO вопросы тоже ложатся на плечи разработчиков ИС

С одной стороны это может повергнуть в уныние, ведь мы, кажется, удвоили количество трудностей при реализации проекта. Но есть и хорошая новость — практики и инструменты, применяемые к коду, а именно версионирование, ревью, тестирование, CI/CD и многие другие, вполне применимы и контенту. Именно здесь и кроются основные преимущества подхода Content as Code, помимо бесспорной простоты архитектуры.

Расширенный Пример

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

Проектирование контента в данном случае — тривиальная задача. Новость — это заголовок, иллюстрация, дата публикации и содержание. Лента должна выдавать новости в порядке убывания даты (свежие новости в начале). Это означает, что самый востребованный («горячий») контент идёт в начале Ленты.

Пока не звучит как что-то специфичное. Любая архитектура из перечисленных подходит.

???? У нас есть команда React разработчиков (подставьте свой фреймворк), которые не имеют опыта работы с классическими CMS (распространённое явление)

Это обстоятельство приводит к мысли о Headless CMS.

Конечно, под рукой может оказаться архитектор, готовый провести анализ (или обладающий экспертизой) существующих классических CMS, и помочь с поиском соответствующих разработчиков. Что означает определённую потерю гибкости в принятии решений при проектировании других продуктов.

Всё еще спорно... Что может подтолкнуть к выбору упрощенной архитектуры?

???? В перспективе необходим полный контроль над SEO оптимизациями и минимизация расходов на облачную инфраструктуру

Ещё пара пунктов в пользу Headless CMS. Что ещё?

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

???? Проверка интеграции кода и контента должна происходить на тестовом стенде до итоговой публикации

Данное требование «дешевле» всего ложится на подход Content as Code в рамках Headless CMS, склоняя выбор в пользу соответствующей архитектуры.

Однако, необходимо учесть ограничения, накладываемые VCS. Для этого оценим общий объём контента в единицу времени. Допустим, что:

  • Каждый день команда редакторов публикует пару новостей

  • Новость — это в среднем 5 тысяч символов текста, и Full HD иллюстрация вдобавок

Итого за день:

2 * (5Kb + 500Kb) ≈ 1Mb

За год:

365 * 1Mb ≈ 400Mb

За 5 лет:

5 * 400Mb = 2Gb

И GitLab и GitHub легко справятся с такими объёмами.

Но не стоит воспринимать это рассуждение как полноценный анализ, это простая иллюстрация. Реальный продукт потребует учета всех нефункциональных требований. В частности, инертность CI/CD может перевесить прочие достоинства подхода через Content as Code. По меньшей мере, стоит учитывать все варианты при выборе архитектуры, и проще всего начинать анализ с наиболее простых, коим и является подход через Content as Code.

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

Для полноты картины остаётся затронуть ряд технических вопросов, возникающих при разработке ИС, контент который находится под управлением Headless CMS.

Фреймворки

???? Как поставлять?

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

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

  • Как унифицировать доступ к распределённому по разным источникам контенту (простые файлы в репозитории, внешний API, RSS ленты, классические CMS, и т. д.)?

  • Как строить Server-Side Rendering (SSR) для оптимизации скорости загрузки и SEO продвижения?

  • Как строить Static-Site Generation (SSG) для наиболее «горячего» контента?

  • Как строить Routing с опорой на контент (к примеру, с каждой новостью может быть ассоциирован слаг для построения красивых URL)?

  • Как подготавливать сырой контент для доставки пользователям ИС? К примеру:

    • Преобразование Markdown в HTML

    • Преобразование и оптимизация изображений

    • Оптимизация TTFB (Time to First Byte), FCP (First Contentful Paint), TTI (Time to Interactive)

  • Как строить offline режим и прочие PWA аспекты?

  • Если контент и код едины (Conten as Code), как обеспечить инкрементность сборок относительно контента, генерируя только изменившиеся страницы?

Справедливости ради, стоит отметить, что решение подобных вопросов можно отложить и начать разработку на чистом React/Vue/Angular. Другими словами, на том стеке, который привычен.

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

SSG

SSR

React

Gatsby

Next.js

Vue

Gridsome

NuxtJS

Деление на SSG и SSR означает степень проработки данных аспектов в соответствующих фреймворках. Иначе говоря, SSG в Gatsby проработан глубже чем SSR.

Gatsby

Gridsome

Next.js

NuxtJS

SPA

+

+

+

+

SSG

GraphQL

GraphQL

+

@nuxt/content

DSG

Cloud

-

-

-

SSR

+-

+-

+

+

TypeScript

+

плагин

+

+

Порог вхождения

Низкий

Низкий

Средний

Средний

Расширяемость

+

+

+-

+-

Динамический routing

+

+

+

+

Трансформация изображений

плагин

плагин

+

+

Markdown

плагин

плагин

remark

+

Offline mode

плагин

плагин

+

+

Инкрементный SSG (ISR)

+

-

+

+

License

MIT

MIT

MIT

MIT

Релиз

2017

2016

2021

Мои личные предпочтения на стороне NuxtJS (Vue) и Gatsby (React) как наиболее зрелых и проработанных. Gridsome — это довольно слабый и молодой аналог Gatsby на Vue (многой функциональности не хватает).

Более подробный разбор отдельных фреймворков, ровно как и историю про Headless CMS, к разработке которой мы “пришли” в итоге, оставлю для следующих публикаций. На момент написания данной статьи происходит внедрение разработанной CMS в банковские продукты. Поэтому рассказ о мотивации и результатах разработки собственного решения лучше отложить. Несмотря на то, что описанный в статье подход, который с достаточной степенью точности может быть назван Git-based Headless CMS, трудно охарактеризовать как новейший и революционный, кому-то он может показаться довольно необычным и потребовать преодоления привычек (возможно стереотипов) для полного понимания тех плюсов, которые он с собой приносит. А как кажется вам, заслуживают ли внедрения подобные headless решения?

Благодарности

В первую очередь, спасибо хорошему товарищу и соавтору статьи @ktzv

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

Термины

Комментарии (0)