![cover](https://habrastorage.org/webt/lv/gg/cy/lvggcy92o5p1ybmzxmasrsilgmq.png)
Привет!
Тинькофф-журнал выпускает статьи уже 5 лет. За это время из блога на Вордпрессе он вырос в большую самодельную систему из десятков проектов и инструментов. Я в Т—Ж управляю разработкой и сегодня в общих чертах расскажу об архитектуре журнала и сервисах, на которых тот работает.
Сайт Т—Ж работает благодаря разным сервисам:
- Движок сайта — Меркурий.
- Фронтовый монорепозиторий, или просто монофронт.
- Соцплатформа.
- Урания — образовательная платформа.
- Фичеры — калькуляторы, тесты и прочие интерактивы.
- Мобильные приложения.
- Медиасервис.
![scheme](https://habrastorage.org/webt/th/dg/mc/thdgmcjmmjk5qwq6czihg_ffnmu.png)
Как устроен Т—Ж изнутри
Дальше — о том, что делает каждый из этих сервисов и как они взаимодействуют друг с другом.
Меркурий
Главное бэкэнд-приложение Т—Ж — его движок. Собственный движок нам понадобился, когда журнал в 2017 году переезжал с Вордпресса. Движок назвали Меркурием в честь древнеримского бога богатства и торговли, потому что мы же журнал про деньги. Написан на Python + Django.
Меркурий собирает страницы, которые видит читатель журнала. Часть компонентов страницы он переводит в разметку и стили из собственных джанго-шаблонов: контент статьи, страницу о журнале и поисковую выдачу. За компонентами поновее — шапкой, подвалом, профилем и прочим — он ходит в сервис-прослойку, который уже отдает разметку и стили с нашего фронта.
Меркурий сам хранит и обрабатывает авторский контент — изображения, статьи, редакционную структуру материалов и статистику опубликованного контента. Редакторам и верстальщикам он предоставляет интерфейс для управления всем этим добром — админку.
Верстальщики набирают статьи журнала в синтаксисе html. Для удобства админка еще поддерживает шорткоды — это как теги, только они разворачиваются в более сложные синтаксические конструкции с особыми стилями. Cписок сервисов из начала статьи в админке записан шорткодом [ol type=milchin][/ol].
![interface](https://habrastorage.org/webt/dd/iu/b4/ddiub4fljatvz7texuszb9rbvma.png)
Интерфейс, с которым работает верстальщик и иногда — редактор.
![dashboard](https://habrastorage.org/webt/cu/dn/ht/cudnhtwqkpru1bmeyr2s6q90gfi.png)
Главный экран админки. Та самая кнопка раньше отрубала поиск и рекомендации. Потом мы их оптимизировали, а кнопку оставили из ностальгических соображений.
![guide](https://habrastorage.org/webt/e-/_e/et/e-_eetjpy5vj0-8rdndbmcti_hw.png)
Руководство по верстке материалов из шорткодов и прочего. У верстальщиков есть еще своя методичка в гугл-документах.
Когда в админке сохраняют статью, по ней проходит типограф. Это скрипт, который меняет часть конструкций в тексте так, чтобы они соответствовали правилам типографики и редполитики журнала. Например, проставляет знак рубля и неразрывные пробелы. Мы воспользовались готовым решением, только переписали пару правил, чтобы результат нам подходил.
Меркурий кэширует большинство данных: контент, главную, статьи и другие редко меняющиеся страницы. Так мы экономим на дорогих расчетах и отрисовке, чтобы держать больше пользователей онлайн одновременно.
В журнале встречаются страницы, которые объединяют другие статьи, например:
- разводящие по тематикам — путешествия, бизнес, интерактивы;
- подборки — дневники трат, выплаты в пандемию;
- выдачи по тегу, автору или формату;
- результаты поиска.
Правила, по которым собираются эти страницы, также обрабатывает Меркурий: это могут быть формулы, выставленные вручную редакторские настройки или их комбинации. Для таких настроек у нас тоже предусмотрены отдельные конфиги.
![travel](https://habrastorage.org/webt/qs/ht/b5/qshtb50zndodpmwh083dnhrdyp8.png)
Разводящая раздела про путешествия. Сюда автоматически тянутся свежие статьи с этой темой, а блок подборок настраивает редактор.
![custom article kit](https://habrastorage.org/webt/zd/yl/jb/zdyljb3udts26i7gy6ss5wrldkg.png)
Витрина подборки. Статьи для нее собрал редактор.
![search result](https://habrastorage.org/webt/0d/lt/ze/0dltze6-a9ilv8geg35p0zxbcu8.png)
Результаты поиска, которые собираются автоматически. Сам поиск тоже реализован в Меркурии.
В апреле 2020 года мы начали тестировать новую версию нашего движка, который мы назвали Ретроградным Меркурием. Мы полностью перебрали код, разбили его на несколько сервисов и затащили новые фичи: веб-сокеты в админке, улучшенный поиск и другие. Пишем тоже на Python, но уже с FastAPI — как и другие крупные бэкенды журнала.
Монофронт
Когда мы окончательно переехали с Вордпресса на Меркурий, нам захотелось добавить интерактивности на страницы журнала. Возможности джанго-шаблонов тут ограничены, ведь только на них мы не смогли бы менять контент страниц в рантайме.
Чтобы не разрабатывать кучу кастомных страниц, решили вложиться в полноценный фронт — Монофронт.
Моно — потому что в один проект мы собрали:
- Интерфейс регистрации и авторизации.
- Комментарии.
- Футер.
- Хэдер.
- Отображение метаинформации материалов.
- Профиль пользователя.
- Промобаннер.
- Формы подписки.
- Страницу управления подписками.
- Образовательные курсы.
А еще в нем обрабатывается логика веб-трекинга пользовательских событий: откуда пользователь пришел на страницу журнала, что именно он на ней делает.
![frontend mono repository](https://habrastorage.org/webt/of/mi/ya/ofmiyahmhu_gprtauoltdqislpa.png)
Как это выглядит в репозитории.
В 2020 году хотим сделать из монофронта несколько независимых приложений, вытащить простые виджеты в npm-пакеты, а сложные — в рантайм и поднять дизайн-систему. Пока сделали и развернули шаблонное фронт-приложение, готовимся переносить туда рендеринг материалов. Пишем на React, Redux и TypeScript.
Соцплатформа
В Т—Ж есть не только авторский, но и пользовательский контент. Пользователи регистрируются, комментируют статьи, подписываются, связываются с редакцией через специальные анкеты. Все это происходит благодаря соцплатформе.
Чтобы зарегистрировать или авторизовать пользователя, соцплатформа взаимодействует с АПИ соцсетей. У себя она сохраняет ценные пользовательские данные, в том числе персональные: имя, фамилию, почту.
Мы разрабатываем интерфейс редактирования данных пользователя в его профиле. Пока он не готов, мы переложили ответственность за модерацию имени и фотографии на соцсети. Отчасти этим же мы решили проблему ботов, потому что соцсети стали просить привязку к номеру телефона. Но остается проблема с тем, что система считает одного человека из разных соцсетей разными пользователями.
У авторов журнала обычно два аккаунта. Один — в Меркурии, он определяет авторство статей. Второй — в соцплатформе, чтобы отвечать на комментарии. Думаю, в будущем перейдем на один общий аккаунт, а пока только немного облегчили жизнь редакторам: когда к аккаунту автора в соцплатформе привязывают аккаунт Меркурия, оттуда автоматически подтягиваются все его статьи.
Сообщество не получается развивать без правил. На каждое правило находятся нарушители. Поэтому соцплатформа предоставляет редакторам и модераторам интерфейс для проверки комментариев и бана нарушителей.
![moderation interface](https://habrastorage.org/webt/em/lv/0a/emlv0ag_pwpst62qwm-qmzifze8.png)
Модератор может удалить комментарий или замаскировать его часть, если она противоречит законам РФ или правилам пользования сайтом.
Соцплатформа хранит все комментарии пользователя и сопутствующую информацию: лайки и дизлайки, был ли это комментарий к статье или ответ другому пользователю в треде. Сами комментарии транслируются в Телеграм через специального бота, чтобы модераторы могли быстро среагировать, а редакторы получали обратную связь.
Часто мы предлагаем читателям поделиться чем-то с редакцией: рассказать о своей профессии, способе вести бюджет или о том, что люто бесит. Тут приходится рулить логикой сохранения ответов на вопросы форм и следить, чтобы они доходили до редакторов. Раньше гуглоформы мы заводили вручную и прикручивали проброс данных в них на фронте, а теперь автоматизировали это в соцплатформе.
![poll editor](https://habrastorage.org/webt/j_/wz/v-/j_wzv-wdpk1h8pq6wpkiscowpym.png)
Редактор сам может завести новый опрос, редактировать вопросы, из которых он состоит, и собрать опрос из шорткодов.
Урания
В 2019 году мы решили запустить собственную образовательную платформу. Сразу появились новые сущности: студент, курс, урок, экзамен. Чтобы их создавать и редактировать, разработали отдельный бэкэнд и админку — Уранию. Просто захотели продолжить тему античной мифологии. Но вообще чаще мы называем Уранию просто учебником.
Когда студент регистрируется, мы используем в качестве его идентификатора тот же аккаунт, что уже есть в соцплатформе. Сверху дописываем актуальную почту, курс, который студент проходит, и прогресс обучения. Так мы не смешиваем данные, но при этом можем просто синхронизироваться с соцплатформой. В тех же комментариях рядом используются данные из соцплатформы и учебника — например, чтобы нарисовать пользователю медальку курса, который он прошел. Эти данные удобно забирать по одному айди.
Контент самих курсов Урания пока берет из Меркурия. Курс состоит из уроков, а урок на самом деле — хитро стилизованная статья. С главными страницами курсов и со страницей всех курсов, которая скоро появится, Урания работает сама.
![invest course main page](https://habrastorage.org/webt/gl/be/f6/glbef69tgmqxmgkdo8fwwwovpji.png)
Главная страница курса «А как инвестировать».
![invest course lesson](https://habrastorage.org/webt/go/ud/sc/goudsczlphmsqzsze3oxkcysyh8.png)
Фрагмент урока о том, как выбирать брокера.
![invest course exam](https://habrastorage.org/webt/nj/n5/7v/njn57vekeaxr5snk2t3seahbvc4.png)
В конце каждого урока — тесты для закрепления материала. В конце курса — экзамен, тоже в формате теста.
Обычные тесты в журнале работают только на фронте, но в курсах мы проверяем ответы еще и на бэке. Учебник не только хранит вопросы, варианты ответов и правильные ответы, но и взаимодействует с тестами, потому что это отдельные приложения. Расскажу о них немного подробнее.
Фичеры
В Т—Ж выходят калькуляторы, карты, тесты, опросы и другие интерактивные материалы. Это такие отдельные фронтовые приложения. Мы разрабатываем их отдельной командой и не затаскиваем в Монофронт. Фичеры собираются в JavaScript-бандлы, а Меркурий подставляет их на страницы вместе с информацией о конфигах.
![feature loading](https://habrastorage.org/webt/9h/0c/bb/9h0cbbtbyqmrwkdxj3gbcds28rq.png)
ip-calendar.js содержит всю логику интерактива, который помогает ИП вовремя сдавать отчетность. В полном адресе бандла на CDN содержится хэш. Так мы поддерживаем версионирование интерактивов.
Большинство интерактивов используются максимум в двух местах: на отдельной странице и внутри статьи на ту же тематику. Но некоторые из них настолько прижились, что мы используем их десятки раз, например:
- Тесты, которые помогают читателям узнать, какого богатства они достойны, или закрепить знания в образовательных курсах.
- Опросы, которые разжигают дискуссии в комментариях.
- Обменник валюты, который пересчитывает цену из местной валюты в рубли по актуальному курсу и подставляет значение в статью.
- Эмодзи-сервис, который подставляет во все браузеры и на все устройства эмодзи из одного набора.
![currency exchanger](https://habrastorage.org/webt/d8/kb/6a/d8kb6a54qafhnuvkma_oykylg8e.png)
Обменник валюты переводит все цены в рубли по актуальному курсу. В редакции его называют валютным фичером.
![poll example](https://habrastorage.org/webt/k-/kh/bo/k-khbobw9m_gqwxrmphpphtducy.png)
Пример опроса в конце статьи.
Мобильные приложения
Пока приложения, которые мы делаем, находятся на стадии разработки или закрытых бета-тестирований. Но кое-что рассказать уже можно.
Во-первых, клиентские части активно используют все бэкэнды, которые уже есть в журнале: забирают контент из Меркурия, пользователей — из соцплатформы, прогресс студентов — из Урании. Во-вторых, у них появляются свои бэкэнды, которые обрабатывают особый контент приложений и разруливают пуш-уведомления.
Сами приложения пишем на React Native и TypeScript. Бэкенды — на Node.js и TypeScript.
![course app](https://habrastorage.org/webt/de/kc/ue/dekcueft_1zd93jkwbxbxtkzkxi.png)
Так выглядит урок курса об инвестициях в мобильном приложении. У приложения отдельный бэкэнд. Там мы обрабатываем контент, который есть только в приложении: аудио уроков и статьи для дополнительного чтения после курса. Этот же бэкэнд используем для пуш-уведомлений.
![journal app feed](https://habrastorage.org/webt/tn/ik/qd/tnikqdua3wyhxrsmqhnkt9sq3fm.png)
Это первая версия мобильного приложения журнала. Пока главная фича — подписываться на подборки. Бэкэнд этого приложения хранит и обрабатывает пользовательские настройки и тоже отвечает за пуш-уведомления.
Медиасервис
Основные медиафайлы в журнале — изображения. Медиасервис проверяет каждое изображение, перегоняет в webp и png, оптимизирует размер, обрезает под шаблоны из конфига и заливает на CDN. В ответ он получает ссылки на результат обработки — изображения разных форматов и размеров, которые получились из исходного. Остальные медиафайлы сервис просто бережно кладет на CDN.
Медиасервис особенно примечателен тем, что он использует модель serverless. Это не полноценный бэкэнд, развернутый на сервере или в облаке. Он лишь использует облачные функции Гугла и паттерн pub/sub.
Медиасервис использует соцплатформа для пользовательских картинок в комментариях и профиле. Этой функциональности пока нет на продакшене, но мы работаем над этим.
Что дальше
В следующих статьях расскажем подробнее о сервисах, инфраструктуре и командных процессах.
Вакансии Т—Ж.
Гитхаб Т—Ж.