📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Every Frontend System Design Pattern w/ Senior Engineer (Microfrontend, BFF, CDN etc)

theSeniorDev38:02

Transcription

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

Теперь начнем с нашей первой концепции проектирования фронтенд-систем — микрофронтендов. Дополнительное фронтенд-приложение будет начинаться как фронтенд-монолит. Это означает, что практически все находится в одном месте. А когда дело доходит до микрофронтендов, это в основном означает, что мы разделим это приложение на независимые фронтенд-приложения, а затем соберем их все вместе. Но прежде чем мы даже поговорим о микрофронтендах, нам нужно понять микросервисы, и это время для бэкенд-архитектур. И, глядя на нашу модель клиент-сервер, и сервер, и клиент будут монолитами, то есть одиночными приложениями. И в этот момент мы продолжаем добавлять к нему функции, потому что вы находитесь на начальном этапе проекта. Но по мере масштабирования все больше и больше людей будут работать над одной и той же кодовой базой, и ваша команда разработчиков станет огромной. И я работал с разработчиками, которые говорили мне, что их команда разработчиков состояла из 40 человек, а их ежедневные стендапы занимали полтора часа только для того, чтобы каждый мог говорить по полторы минуты, и это просто неустойчиво. Вот где на сцену выходят микросервисы и микрофронтенды, потому что такая архитектура не масштабируется с точки зрения разработки.

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

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

Теперь, согласно закону Конвея, программная архитектура системы будет отражать организацию людей. Так что, если мы хотим иметь небольшие независимые команды, нам нужно каким-то образом разбить нашу систему на небольшие независимые вертикальные срезы, и это обычно начинается на бэкенде, где мы берем наш монолит и выделяем независимые модули для создания микросервисов. Это независимые небольшие бэкенды, микро-бэкенды, которые предоставляют свой собственный API и могут развертываться независимо. Кодовая база независима, и у вас могут быть независимые команды, которые затем должны общаться друг с другом, расширяя их, и они общаются только друг с другом через API. Таким образом, типичный шаблон микросервиса будет выглядеть примерно так: у вас есть API, есть слой бизнес-логики, затем есть слой персистентности и обычно база данных. В этом случае я добавил Progress DB, но это может быть база данных NoSQL. И это строительный блок вашего приложения, а большие приложения будут иметь тысячи таких в продакшене. Пару лет назад я работал в этой финансовой компании, и у нас было около тысячи микросервисов в продакшене, и у вас были разные команды, владеющие разными микросервисами. Моя команда владела 13 из них. И кстати, если вы хотите увидеть, где вы находитесь во всем стеке, есть бесплатная оценка, которую вы можете пройти по ссылке ниже, и вы, по сути, поймете, сколько знаний полного стека у вас на самом деле есть, где есть пробел, и мы добавили новый раздел ИИ, потому что многие компании начинают задавать вопросы об ИИ на собеседованиях по фронтенд-инженерии. Так что пройдите оценку, и вы сможете более или менее понять, где вы находитесь на рынке и какие пробелы вам нужно закрыть. Ссылка. Она в комментариях.

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

Теперь, как мы все это собираем? Ну, в основном у нас будет оболочка, и оболочка микрофронтенда отвечает за глобальные функции, такие как маршрутизация, язык, глобальное состояние, вошли ли пользователи в систему или нет — все это обрабатывается ею, а все остальные небольшие приложения живут внутри этого. Они, по сути, загружаются этой оболочкой микрофронтенда, и эта оболочка микрофронтенда также передает это глобальное состояние внутрь этих микрофронтендов, но помните, что эти приложения теперь могут развертываться независимо. Это означает, что у вас может быть домен, который является опасным. Где, например, мы бы разместили только наше приложение заголовка, и вы могли бы загрузить его независимо, а затем у вас есть страница продукта и страница корзины, но затем у вас есть оболочка, которая собирает их все вместе. Так что, если вы посмотрите на крупную софтверную компанию, такую как Amazon.com, вы можете, по сути, разделить заголовок, а затем, возможно, выделить страницу продукта и платеж в три разных микрофронтенда. Но, честно говоря, я чувствую, что у них там еще большая гранулярность, и они, вероятно, разделяют свои фронтенды на еще более мелкие и более сфокусированные микрофронтенды.

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

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

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

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

Другая классная функция при наличии API Gateway заключается в том, что вы можете сделать связь между клиентом и API Gateway HTTPS, но связь между микросервисами и API Gateway HTTP, потому что вам больше не нужна эта безопасность, поскольку вы находитесь в закрытой среде, и преимущество здесь — производительность, потому что HTTPS, вероятно, менее производителен, чем HTTP, потому что вам нужно больше раундов для рукопожатия HTTPS. Поэтому в целом, когда у вас есть пара микросервисов в продакшене, хорошей идеей является добавление API Gateway как для безопасности и производительности, так и для снижения сложности на фронтенде.

Теперь перейдем к нашему следующему паттерну, очень похожему на API Gateway, — "бэкенд для фронтенда". Давайте вспомним нашу предыдущую настройку, где у нас есть клиент, наш API Gateway и все наши микросервисы. Проблема здесь в том, что клиенту приходится обращаться ко всем этим различным микросервисам, которые могут иметь разный API, и существует так много вызовов fetch. И если вы находитесь во фронтенд-команде, когда вам нужна новая функция, которая требует даже малейшего изменения на бэкенде, вам нужно обратиться к этой бэкенд-команде, выяснить, будет ли это приоритетом для них, добавить это в их бэклог, и, возможно, что-то будет сделано. В итоге это происходит очень, очень медленно, и одна из самых больших проблем, с которой сталкиваются некоторые инженеры, с которыми мы работаем, — это старшие разработчики, работающие в больших компаниях, — им так трудно добиться результатов, потому что им приходится разговаривать с тремя разными бэкенд-командами, у которых есть свои бэклог и свои приоритеты, и они просто не хотят реализовывать этот маленький API-запрос, который им нужен.

Поэтому лучшее решение — каким-то образом владеть своим бэкендом в качестве фронтенд-команды, в качестве клиентской команды. Также существует большая сложность, когда приходится реализовывать все эти разные API, потому что все они выглядят по-разному. Вам нужен разный клиент и SDK для всех этих разных API, и это больше фронтенд-сложности. Поэтому решение для этого — добавить то, что мы называем "бэкенд для фронтенда". Это бэкенд, который будет принимать фронтенд-запрос, а затем просто перенаправлять его на микросервисы. Но преимущество здесь в том, что всякий раз, когда вам нужно реализовать функцию, вы можете действовать как полнофункциональный разработчик, когда вы находитесь во фронтенд-команде. Таким образом, по сути, у вас есть все эти микросервисы, вы интегрируетесь, чтобы изменить способ их запроса из бэкенда для фронтенда, затем вы реализуете свою функцию и, по сути, владеете клиентской частью функции от начала до конца, а бэкенд-команды могут просто работать изолированно над различными микросервисами. Таким образом, фронтенд-команда в конечном итоге владеет как клиентом, так и бэкендом для фронтенда, а бэкенд-команды создают чистые бэкенд-сервисы, и это очень важно для вас как для фронтенд-инженера, потому что это означает, что вам нужно знать, по крайней мере, на высоком уровне, как расширить микросервис, как создать бэкенд для фронтенда, вам нужно знать дизайн API, и вам нужно знать немного о GraphQL, например, который является отличной технологией для создания бэкенда для фронтендов. Это требование для всех фронтенд-инженеров, которых вы видите, работающих в больших компаниях, которые обычно являются теми, у кого также лучшие условия и самая захватывающая работа. Поэтому, даже если вы занимаетесь фронтендом, убедитесь, что вы также можете добиться результатов на бэкенде.

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

Перейдем к следующей концепции — балансировке нагрузки. Теперь, возвращаясь к нашей модели клиент-сервер, у нас был сервер, и у нас были клиенты, но проблема в том, что у нас может быть много пользователей, и поэтому у вас есть все эти клиенты, делающие запросы к одному серверу. Теперь типичный сервер NodeJS довольно мощный. Вы обычно можете удовлетворить до 2000-10000 одновременных запросов с хорошо оптимизированным сервером NodeJS. Но когда вы выходите за эти пределы, вам могут понадобиться разные способы масштабирования, потому что у одного сервера есть свои ограничения. И самый простой способ масштабировать его — это балансировка нагрузки. И это означает, что вы, по сути, создадите идентичные экземпляры ваших серверов, а затем у вас будет этот компонент — балансировщик нагрузки приложений, который распределяет трафик между ними.

Балансировка нагрузки — это отдельная тема. Существуют разные способы распределения трафика и разные критерии. И как фронтенд-инженеру, вам не нужно углубляться в это. Но убедитесь, что вы знаете об этом. В идеале вы даже сможете настроить небольшой балансировщик нагрузки, используя веб-серверы, такие как Nginx, и Docker Compose. Вы можете настроить это на своей локальной машине. В любом случае, важно, чтобы вы могли рассуждать об этом. Большинство облачных провайдеров, таких как AWS или Google Cloud, позволяют вам предоставить балансировщик за секунды. Так что не беспокойтесь об этом. Очень редко вам нужно настраивать его вручную, но важно, чтобы вы знали о нем.

Поздравляю с тем, что вы дошли так далеко. Обязательно подпишитесь, чтобы не пропустить никаких обновлений в будущем. И перейдем к нашей следующей концепции — системам контейнеризации. Таким образом, мы, по сути, распределили нашу архитектуру на микрофронтенды и различные микросервисы. Но проблема в том, что развертывание всего этого будет головной болью. Теперь нам нужно создавать и предоставлять инфраструктуру и конвейеры, и все они могут использовать разные технологии. У вас может быть микросервис на Python, а затем на NodeJS. У вас может быть приложение Vue.js или приложение Next.js, и все это очень сложно довести до продакшена. Поэтому, чтобы стандартизировать развертывание, мы можем использовать Docker, а Docker — это технология, которая позволяет упаковать ваше приложение в образ Docker, и делается это так: вы берете свой код, а затем у вас есть Dockerfile, где вы, по сути, объявляете рецепт того, как следует упаковать этот код, и, по сути, на основе этого вы создадите образ Docker. Образ Docker будет содержать весь код приложения, а затем среду выполнения. Представьте, что вы используете Next.js. Тогда он будет содержать Next.js. У нас будет среда выполнения, которая является Node, а затем операционная система, которая обычно является Linux, и это полный образ Docker. И преимущество в том, что всякий раз, когда вы находите хост, который запускает Docker, вы можете запустить этот образ. Вам не нужно беспокоиться о версии NodeJS или о том, нужно ли вам устанавливать PHP и все зависимости, которые есть у вашего приложения. Он действительно упакован вместе. Это образ Docker. Вы запускаете его, открываете порт, и у вас работает фронтенд. Вам не нужно знать, что внутри. И это замечательно для DevOps-команд, потому что внезапно они могут взять все эти образы и поместить их в систему оркестрации контейнеров. Система оркестрации контейнеров обычно имеет конвейер развертывания контейнеров, который будет запускать эти контейнеры. И запустить контейнер не так просто, как кажется. Вам может понадобиться балансировка нагрузки. Вы можете захотеть запускать несколько экземпляров параллельно и иметь возможность, если контейнер выходит из строя, очень быстро запустить другой. Таким образом, вся эта сложная работа встроена в такие системы, как Kubernetes. Вы, вероятно, видели это в вакансиях.

Нужно ли вам знать Kubernetes как фронтенд-инженеру? Нет. Но вам нужно объяснить, вам нужно знать об этом. И вы увидите много фронтенд-позиций, в которых упоминается либо Kubernetes, либо системы контейнеризации, либо ECS, которая является альтернативой Kubernetes от AWS, в описании вакансии. Не бойтесь. Вам не нужно становиться DevOps-инженером завтра. Но вы должны быть в состоянии на высоком уровне понять, куда это вписывается в вашу архитектуру.

Наконец, более фронтенд-концепция — CDN, сеть доставки контента. Так что же такое CDN? Возвращаясь к модели клиент-сервер. Представьте, что вы клиент. Вы хотите зайти на веб-сайт. Обычно вы идете на сервер и получаете статические файлы, такие как JavaScript и CSS, и загружаете их и запускаете в своем веб-браузере. Представим гипотетический случай, когда вы пользователь из США и хотите посетить приложение, расположенное в Европе. Чтобы получить JavaScript, CSS и весь HTML, вам придется пройти весь путь до Атлантического океана и обратно. И этот раунд-трип добавляет задержку, и нет физического способа обойти это. Независимо от того, насколько производительно ваше приложение, существует предел скорости света, потому что данные перемещаются только со скоростью света. И на больших расстояниях свет очень быстр, но он все равно добавит примерно, скажем, 200-250 миллисекунд задержки к каждому запросу.

Решение этой проблемы — попытаться увеличить расстояние между вами и местом, где размещены эти файлы. И самый простой способ — использовать сеть доставки контента. И поэтому, по сути, CDN будет сетью этих периферийных расположений, расположенных по всему миру. И происходит то, что сервер отправляет туда статические ресурсы, и всякий раз, когда вы делаете запрос, вы будете перенаправлены в ближайшее к вам периферийное расположение. Этот механизм того, как именно вы перенаправляетесь в ближайшее к вам периферийное расположение, очень интересен, и я хочу сделать видео об этом, но я не уверен, интересно ли это вам. Если вы хотите видео об этом, дайте мне знать в комментариях. Это немного более технически, и это не то, что вы получите на собеседованиях, но, честно говоря, это очень интересно. Так что, если вы хотите, чтобы я сделал видео об этом, просто дайте мне повод, сообщив мне об этом в комментариях, и я сделаю это.

CDN — это распределенный кэш. Так что технический термин для получения ресурса из CDN — это попадание в кэш. А когда сервер отправляет новую версию, это называется инвалидацией кэша. И эта концепция очень тесно связана с тем, что мы называем кэш-бастингом, который является механизмом, который модульные бандлеры используют для инвалидации ваших ресурсов. Таким образом, мы гарантируем, что при развертывании новой версии пользователи действительно получают последнюю версию, а не предыдущую версию, которая, вероятно, все еще находится где-то в CDN. У меня есть другие видео на канале, посвященные этому. Так что я не буду углубляться в это, но убедитесь, что вы можете передавать эти вещи через стек. Имейте в виду, что CDN — это самый быстрый и экономичный способ повысить производительность веб-сайта, обслуживая оптимизированные ресурсы с правильной политикой кэширования "из коробки", потому что в наши дни CDN делают гораздо больше, чем просто размещают ресурс рядом с клиентом. Они также сжимают его и заботятся о политике кэширования. Так что это действительно дешевый способ исправить большинство проблем с производительностью.

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

Поэтому решение — иметь систему дизайна. И в системе дизайна первое, что вы делаете, — это определяете свои токены дизайна. Это, по сути, информация вашей команды. Какой ваш основной цвет, какое определение границы, какие шрифты и так далее. И современный способ сделать это — добавить их как пользовательские свойства CSS, по сути, переменные на глобальном уровне в вашем CSS, вероятно, в оболочке микрофронтенда, а затем они будут переданы во все остальные микрофронтенды. Но вы можете пойти немного дальше и создать повторно используемые компоненты, которые затем команды по функциям могут использовать для сборки своих функций. Таким образом, вы могли бы создавать поля ввода и кнопки, и, по сути, они бы использовали этот пакет UI и использовали его во всех этих различных микрофронтендах. Таким образом, по сути, вы достигаете того, что UI выглядит последовательно, но также вы не повторяете свой код.

Крутость системы дизайна в том, что вы можете позаботиться о доступности. У вас могут быть все эти компоненты, протестированные на единицу, и, по сути, вы применяете на архитектурном уровне принцип "не повторяйся" (DRY). И возвращаясь к ИИ-кодированию, надежная система дизайна действительно имеет значение между генерацией некоторой замены компонента и наличием несогласованных стилей и ошибок, требующих много переделок, и действительно получением последовательного надежного вывода агента кодирования. Поверьте мне, я много занимался ИИ в последние несколько месяцев. И первое, что я делаю, когда получаю новый проект, — это пытаюсь извлечь из дизайна систему дизайна, потому что затем вы передаете это своему агенту кодирования в различных сессиях, и вы все равно получаете последовательный вывод. Если вы этого не сделаете, агент будет выдумывать, и ваш UI будет выглядеть иначе, и будет очевидно, что он был закодирован с помощью ИИ.

Наша следующая концепция — "дизайн в код MCP". Это сервер модели контекстного протокола. И поэтому, по сути, помните, у нас есть наша система дизайна, и обычно вы импортируете ее и начинаете с ней работать. Но в наши дни, представьте, что ваши дизайнеры изначально создали вашу систему дизайна в Figma, а затем вы реализовали ее в своей библиотеке. Вы можете использовать сервер Figma MCP с агентом кодирования для очень быстрого создания функций для команд по функциям в вертикальных срезах вашего продукта. И это тот тип рабочего процесса, к которому движутся большинство компаний. Поэтому, если вы фронтенд-инженер, вы должны убедиться, что вы знаете, как использовать сервер MCP, вы знаете, что такое сервер MCP. И в идеале, какой бы инструмент дизайна ни использовала ваша команда, вы можете подключить к нему MCP, или вам, возможно, придется даже самостоятельно создать это соединение, а затем подключить его к вашему агенту ИИ-кодирования, такому как Cloud Code, который наиболее часто используется в корпоративной среде, или Codex.

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

Следующая концепция проектирования фронтенд-систем — монорепозиторий. Таким образом, по сути, наши приложения сейчас находятся во всех этих разных репозиториях, где у нас есть репозиторий GitHub для оболочки, один для микрофронтенда платежей, микрофронтенда инвентаризации, и все это распределено по тысячам репозиториев. И проблема здесь в том, что у вас в конечном итоге возникают разные стили кода, разные зависимости и разные стандарты качества. Некоторые люди могут использовать TypeScript. Они могут использовать разные конфигурации линтера. И очень легко получить то, что мы называем архитектурным дрейфом или дрейфом стиля кода, когда два проекта слишком сильно расходятся. В чем проблема с этим? Если вы разработчик и меняете команды, вам приходится заново изучать все. Таким образом, вы на самом деле не используете стандартизацию. И способ исправить это — поместить все в один репозиторий. Таким образом, по сути, у вас есть большой репозиторий, который содержит все остальные небольшие приложения, и у вас есть инструменты, которые работают с обоими. Например, когда вы запускаете npm run build в монорепозитории, все ваши приложения будут собираться индивидуально.

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

Теперь одна вещь, которая меня действительно волнует, — это также MCP UI. MCP UI — это, по сути, объединение традиционного веб-сайта с приложением на основе LLM. И это, по сути, клей между ними, скотч, скажем так. И поэтому, по сути, в чат-приложении вы обычно отправляете чат-запрос, а затем он идет к LLM, и LLM отвечает. Но в этом случае он может отвечать компонентами UI. Он фактически отображает продукты, например, в ответе, а не только текст, но для этого ему приходится каким-то образом общаться с бэкендом, и тогда нам нужно отобразить некоторые компоненты, и именно это решает MCP UI. И это пример, который я нашел на недавнем веб-сайте, когда искал места для мероприятий, потому что мы организуем наши ежегодные встречи Senior Dev в Европе. Мы встретимся со всеми инженерами, с которыми работаем по всей Европе, вероятно, в США. И я искал разные классные места, где мы могли бы встретиться. И вот, я разговаривал с этим чат-интерфейсом, и внезапно, после пары вопросов, он начал отображать эти места. И он спрашивает меня о вводе, а затем он будет проводить глубокий поиск и предоставит мне еще больше мест. И, как вы видите там, он даже может отобразить карту. И все это происходит в чат-приложении. И я думаю, что именно в этом направлении фронтенд будет развиваться с LLM, где мы будем интегрировать LLM в приложения. Многие говорили, что UI больше не нужен, теперь, когда у нас есть чат. Я не согласен. Я думаю, что UI — это очень полезный способ передачи информации, и я думаю, что вам нужны фронтенд-разработчики. Но мы сможем объединить подход LLM с традиционным веб-подходом. Таким образом, этот компонент был отображен, потому что фронтенд разобрал его из ответов LLM.

На высоком уровне, как это работает: модель harness предоставит в контексте два реестра и все серверы MCP, а также пользовательский запрос, и LLM отправит ответ обратно в UI с текстовым ответом, но также с инструкцией отобразить определенный div, а затем инструмент, веб-приложение, должен следовать этому при отображении. Если вы хотите, чтобы я сделал подробное видео о том, как именно работает MCP UI, дайте мне знать. Но это один из тех паттернов, которые, если вы хорошо их знаете, действительно выделяют вас, потому что я считаю, что он выходит далеко за рамки текущего ажиотажа вокруг ИИ и на самом деле очень полезен для конкретных крайних случаев, и вы увидите, как многие приложения реализуют эти гибридные решения.

Чтобы завершить видео, давайте немного поговорим о производительности. И самый важный паттерн, который вы даже сейчас увидите в описаниях вакансий для фронтенд-инженеров, — это Core Web Vitals. И поэтому, по сути, Core Web Vitals — это три метрики, которые количественно определяют три измерения, по которым мы измеряем производительность веб-сайта, и это скорость загрузки, скорость интерактивности и визуальная стабильность. И три Core Web Vitals, которые это измеряют, — это Largest Contentful Paint (LCP), Interaction to Next Paint (INP) и Cumulative Layout Shift (CLS). По сути, эти показатели измеряются Google, и они говорят нам, каким должно быть хорошее число.

Таким образом, когда вы смотрите на Largest Contentful Paint, это время от нажатия Enter до отрисовки самого большого элемента на веб-странице. Interaction to Next Paint немного отличается и связан с взаимодействием. То есть вы что-то делаете, а затем когда мы перерисовываем UI. А CLS — это, по сути, насколько сильно меняется UI при загрузке. Таким образом, LCP и CLS связаны с начальной отрисовкой, а INP связан с повторными отрисовками, что очень важно, когда вы говорите о фреймворках компонентов.

Чтобы понять их, вам нужно понять критический путь рендеринга, который представляет собой все шаги, которые вы проходите от загрузки HTML до фактического отображения чего-либо на экране, и это включает построение DOM, затем построение CSS, затем построение дерева рендеринга, затем вычисление дерева макета, которое, по сути, является деревом, где все ваши узлы находятся в позициях и ширине, а затем преобразование этого в то, что мы называем операциями рисования, которые идут прямо на GPU. Затем переход к фазе композиции, которая имеет свою собственную сложность, и я не буду об этом говорить, но, чтобы суммировать, все эти шаги будут выполнены, а затем у вас будут некоторые рендеры, потому что мы используем фреймворки компонентов, вы получаете данные, начинаете рендеринг и, наконец, завершаете, и тогда вы рисуете LCP. Таким образом, все это время измеряется в LCP. И главное здесь то, что если вы отправляете много JavaScript, если вы отправляете много CSS, если вам нужно получить много данных, а ваш сервер медленный, ваше приложение будет медленным, и вы получите очень плохую оценку LCP.

Другое, что произойдет, если ваше приложение полностью оптимизировано, — это то, что у вас постоянно будут переключения макета при загрузке вещей, потому что CSS приходит слишком поздно, затем появляются шрифты, затем появляются некоторые данные, и браузер будет делать снимки этого и пытаться выяснить, не перемещаете ли вы вещи слишком сильно, это будет уровень сдвига макета. И, наконец, Interaction to Next Paint — это, по сути, всякий раз, когда происходит событие пользователя, и вам приходится проходить через реконсиляцию и повторный рендеринг после обновления состояния вашего фреймворка, а затем вы начинаете снова, вы модифицируете DOM, и это вызывает перерисовку. Таким образом, все это время количественно определяется как Interaction to Next Paint. Таким образом, по сути, если у вас очень медленные рендеры или вы повторно рендерите слишком много компонентов, когда пользователи что-то делают, у вас очень медленный INP. Вы можете измерить эти медицинские показатели, такие как Lighthouse, и я углубляюсь в это в видео на этом канале. Так что обязательно посмотрите его.

Наша следующая концепция также связана с производительностью, и это разделение кода. Традиционно модульный бандлер берет весь наш JavaScript и объединяет его в один большой файл. Но загрузка этого одного файла полностью испортит наши Core Web Vitals, потому что мы загружаем слишком много JavaScript. Таким образом, разделение кода позволяет нам разделять наш JavaScript по мере необходимости. Таким образом, вы можете действительно отправлять только тот JavaScript, который нужен для конкретной страницы. И самый простой способ разделить код — по маршруту. Таким образом, по сути, вы будете отправлять на страницу /login только компоненты, которые конкретно нужны для входа. И если у вас есть панель управления, которая очень тяжелая с большим количеством графиков, например, вы не отправляете все это. Таким образом, вы выборочно отправляете свой JavaScript туда, где он нужен, вместо того, чтобы объединять его в один файл. И все это достигается с помощью модульного бандлера, такого как Webpack или Vite, который понимает ваш бандл, разделяет его, а затем динамически загружает его на основе страницы, на которой вы находитесь, работая вместе с вашим маршрутизатором приложений.

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

И, наконец, давайте поговорим о стратегиях рендеринга. И в наши дни мы обычно работаем с современными фреймворками компонентов, такими как React, Vue и Angular. И проблема с ними в том, что когда вы попадаете на страницу, вы видите пустой экран. И причина этого в том, что, вы знаете, вы загружаете HTML, и пока вы не запустите функцию рендеринга, которую они имеют, вы на самом деле ничего не видите на экране. Это то, что мы называем клиентским рендерингом. Таким образом, в архитектуре SPA (одностраничного приложения) с клиентским рендерингом вы получаете статический файл, затем вам нужно получить некоторые динамические данные, а затем, наконец, отрисовать, и все это занимает много времени. Поэтому, если вы хотите иметь очень производительный веб-сайт или веб-сайт, готовый для сканирования поисковыми системами, то клиентский рендеринг — не лучший выбор для вас.

Альтернатива этому, если у вас есть статический веб-сайт, где нет большого количества интерактивности, — это предварительно отрисовать его на сервере и отправить уже отрисованным. Таким образом, когда ваш клиент получает статические файлы, он уже получает HTML и CSS, и ему не нужно запускать так много JavaScript. Опять же, это работает только со статическими сайтами. Если ваш статический сайт часто меняется, скажем, у вас есть блог, и вы хотите публиковать новые статьи, то вы можете использовать инкрементальную статическую генерацию. Это означает, что вы только перегенерируете измененные страницы. Таким образом, по сути, ваша CMS инициирует пересборку при добавлении нового сообщения в блог, и это получит динамические данные, перейдет к конвейеру сборки и перегенерирует только ту часть статических файлов, которая изменилась. Таким образом, это своего рода частичная пересборка веб-сайта. Преимущество здесь не только в том, что это ускоряет сборку. Но если я клиент и я уже загрузил часть вашего CSS и JavaScript, мне не нужно повторно загружать все, только те части, которые изменились. Теперь в большинстве случаев это встроено во фреймворк, такой как Next.js. Так что вам никогда не придется беспокоиться об этом самостоятельно.

И, наконец, у нас есть серверный рендеринг. И при серверном рендеринге клиент отправляет запрос на фронтенд-сервер, но затем фронтенд-сервер запрашивает бэкенд-сервер, получает некоторые данные, затем отрисовывает приложение на сервере, а затем отправляет его обратно предварительно отрисованным. Таким образом, клиент получает полную HTML-страницу. Таким образом, у вас нет этой проблемы пустого экрана. Проблема, которая у вас все еще есть, заключается в том, что эта страница еще не интерактивна, потому что вы не прошли через построение виртуального DOM и прикрепление, например, в случае React, вашего виртуального DOM к фактическому DOM, предварительно построенному, и именно поэтому вам нужно гидратировать. И поэтому, по сути, вы отрисовываете эту HTML-страницу, а затем вам нужно выполнить свой JavaScript, внутренне создать виртуальный DOM, а затем этот виртуальный DOM прикрепляется к существующему HTML-разметке, это то, что мы называем гидратацией. И, наконец, когда вы гидратируете, вам, возможно, придется выполнить дополнительное получение данных, поэтому вам, возможно, все еще придется обращаться к бэкенду. Опять же, это один из самых сложных подходов. Так что будьте осторожны с ним. Он полезен только тогда, когда вам нужна очень, очень высокая производительность или вам нужен SEO. И многие люди во многих компаниях перешли к этому и используют серверный рендеринг, но это как строить гоночный автомобиль F1, чтобы ходить за продуктами. Это избыточное проектирование, и оно создает много проблем, а затем все становится медленнее. Возникает так много проблем при такой настройке. Технология для ее работы очень сложна. Ошибки труднее решать. Поэтому вы хотите избегать этого и действительно предпочитать простые решения, если только ваш сценарий использования действительно не требует максимальной производительности.

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

Таким образом, опросы означали бы, что вы продолжаете вызывать определенный эндпоинт, пока что-то не произойдет. Например, у нас есть транзакция, которая обрабатывается. Я могу продолжать вызывать эндпоинт статуса, пока он не станет завершенным. Это очень легко сделать с помощью обычного JavaScript и setTimeout. В этом нет ничего сложного. Проблема в том, что вы вызываете свой сервер слишком часто, и поэтому он плохо масштабируется. У вас могут быть условия гонки. Так что это очень простое решение, но не самое производительное.

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

Теперь с ИИ нам нужна коммуникация в реальном времени, но она только в одном направлении, потому что обычно, когда вы отправляете запрос в чат-приложение, вы просто ждете, и они начинают отправлять вам токены обратно. Таким образом, сервер отправляет много сообщений, но вы обычно отправляете только одно. Таким образом, существует большая асимметрия между клиентом и сервером. И способ сделать это — с помощью серверных событий, где вы отправляете текстовое сообщение, и это создает разговор, и по этому эндпоинту вы можете получать обновления от сервера. Таким образом, вы получаете все эти токены, но вы не отправляете так много. И этот подход используется большинством приложений LLM. Если вы когда-либо использовали пакет OpenAI npm в приложении React для создания чат-приложения с LLM, это именно то, что они используют под капотом. И вы можете фактически выяснить это, если зайдете в свою сеть, когда используете ChatGPT или Claude, и найдете запрос на разговор, и увидите, что ответом на него являются все эти потоки событий. Таким образом, вы получаете фрагменты ответа, и именно так вы создаете эти классные UI для ссылок. Это не веб-сокеты, и это не опросы, это API серверных событий. Обязательно поищите его, потому что он поможет вам создавать программные продукты, программные приложения с встроенным ИИ.

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