Выводы из реального кейса
Краеугольным камнем почти каждого веб-приложения средней и крупной компании будет архитектура микрофронтенда. Это особенно актуально для распределенной структуры команды НИОКР, где у каждой команды есть своя область, задача или часть всего веб-приложения.
Хотя многие клиентские разработчики согласны с архитектурным принципом, реализация и решение, найденное каждым из них, имеют тенденцию различаться, это верно, будь то каркас инфраструктуры (например, bit.dev, Gatsby или собственное решение) или службы, поддерживающие расширенные варианты использования, существующие в различных фрагментах приложения.
Последняя работа, в которой я принимал участие на Pecan.ai, использовала собственный подход, и я хотел бы поделиться с вами частью этой работы, целью которой была поддержка общего контекста между различными фрагментами приложения (т.н. микроинтерфейсные приложения).
Прежде чем мы углубимся, давайте убедимся, что мы знаем, чего мы ожидали достичь с помощью решения:
- Единое место для обмена общими зависимостями, службами и утилитами приложения.
- Разрешить насмешку над указанным контекстом во время локальной разработки одного микроинтерфейсного приложения.
- Поддержка локальных сред, сред разработки и производственных сред.
- Поддержка различных фреймворков/библиотек (например, Angular и React).
- Возможность изменения реализации общего решения прозрачным образом, так как не оказывает прямого влияния на активных потребителей.
Имея в виду эти цели, мы можем теперь копнуть глубже.
Определение контекста
У веб-приложений есть определенные потребности для правильной работы. Примерами таких потребностей могут быть параметры конфигурации, механизмы аутентификации или даже поддержание определенного состояния во время использования. Мы решили использовать имя в качестве контекста нашего приложения. Поскольку архитектура микроинтерфейсов вводит несколько приложений, требующих оркестровки, составленной в единое целое, эти потребности обычно являются общими для большинства (если не для всех) из них.
Нам нужно было определиться с подходом:
- Каждое приложение микроинтерфейса управляет своим собственным контекстом.
- Совместное использование общего контекста между всеми микроинтерфейсными приложениями.
Изучение подхода №1:
Естественно, в рамках здорового подхода к разработке каждое приложение извлекает и управляет своим собственным контекстом приложения. Это обеспечило разделение, к которому мы стремились при распределенной структуре нашей команды разработчиков.
Минусы такого подхода быстро стали очевидны:
— Перегрузка наших серверов из-за дублирования выборки общих данных (например, выборка конфигурации во время выполнения, опрос флагов функций).
— Повторяющиеся обновления в каждом отдельном случае. приложение при обновлении общей зависимости.
Мы искали способ смягчить это, чтобы обеспечить как здоровый цикл разработки, так и более здоровую среду выполнения для нашего приложения.
Изучение подхода 2.
После множества проблем мы пришли к выводу, что лучшим подходом является совместное использование общего контекста между различными приложениями. Например, дублирование механизма опроса продемонстрировало проблемы гораздо лучше, чем мы теоретически могли ожидать.
Приняв решение поделиться общим контекстом наших приложений, мы воспользовались преимуществами построения микроинтерфейсов.
Архитектура микроинтерфейса включает в себя два основных объекта:
- Хост (также известный как Shell) — организация и обслуживание нескольких удаленных приложений.
- Удаленное (также известное как микроинтерфейсное приложение) — небольшое приложение, обслуживаемое хостом.
Это решение, основанное на архитектуре, поставило нас на путь, по которому наше хост-приложение определяло и управляло общим контекстом, в то время как наше удаленное приложение использовало его.
Шаблон поставщика/потребителя
Во время разработки решения мы стремились к небольшому удаленному приложению (микроинтерфейсному приложению), которое взяло бы на себя ответственность за общий контекст. Затем мы удачно назвали его Host Context Service. Хотя удаленная служба Host Context Service отвечает за создание, управление и обслуживание контекстных данных и инструментов, к ней должен обращаться SDK, определяемый двумя уровнями:
1. Уровень провайдера — будет использоваться нашим хост-приложением, взяв на себя ответственность за настройку и обновление контекста, будь то прямо или косвенно, путем запуска действий во время выполнения (например, входа в систему). Следовательно, наше решение должно предоставить функциональность «записи» для управляемого общего контекста.
2. Уровень потребителя — будет иметь роль «чтения» управляемого контекста, который затем будет использоваться нашими удаленными приложениями, а также нашим хостом.
После определения желаемой концепции мы создали SDK, который будет служить фасадом для нашей упомянутой службы контекста хоста. Этот SDK будет упакован как библиотека, которая инициирует службу контекста хоста, а также предоставляет доступ к созданному ею контексту. Таким образом, наши различные приложения, как Host, так и Remotes, будут иметь SDK в качестве своей зависимости: хост использует уровень SDK поставщика, а удаленные приложения используют уровень SDK потребителя.
Этот фасад SDK позволил нам, чтобы наши различные приложения, независимо от того, являются ли они хост-типом или удаленными, на самом деле не «знали», что такое служба контекста хоста или как она работает. Приложения будут полагаться на представленный интерфейс SDK, который ограничивает доступ к артефактам Host Context Service. Таким образом, обновление и обогащение, с учетом обратной совместимости, Host Context Service может быть построено без прямого воздействия на используемые приложения.
Заглянув под капот
На самом деле методы, позволяющие обмениваться данными между веб-приложениями, несколько ограничены. Либо они будут основаны на событиях, либо будут обслуживаться через глобальную область. Нам больше подходило использование глобальной области видимости, как в объекте браузера window, в качестве агрегатора контекста. Это означало, что наша служба Host Context размещала свои артефакты в самом верхнем окне, в то время как SDK предоставляет средства для доступа и управления им с использованием строгого интерфейса.
Поддержка асинхронного характера выборки данных или манипулирования ими во время выполнения была реализована с помощью промисов. Поэтому SDK гарантировал, что любой доступ к потреблению контекста будет основан на промисах, позволяя потребителям получать нужные данные надежным способом.
На самом деле асинхронный характер решения исходит из базовой потребности SDK, загружающего Host Context Service. Поскольку наш сервис был создан как удаленное приложение, его динамическая загрузка была непростым делом, поскольку динамический импорт не является стандартной функцией в экосистеме JavaScript, когда вы зависите от Webpack.
function importHostContextService() { return import(/* webpackIgnore: true */ '/_host-context/remoteEntry.js') .then((m) => m.get('./HostContextService')) .then((m) => m().HostContextService) .catch((_) => Promise.reject(new Error('Failed retrieving HostContextService'))); }
К счастью, Webpack предоставляет средства для переопределения его поведения. Используя комментарий webpackIgnore «взломать», мы смогли создать нашу библиотеку SDK, которая загружает нашу удаленную службу контекста хоста, где ее фактическое местоположение будет определяться нашими разными прокси-серверами.
Идти вперед
Pecan.ai использует доменно-ориентированный подход к разработке приложений. Клиентская сторона не осталась без внимания и считалась частью того же подхода. Несмотря на то, что этот распределенный подход имеет тенденцию создавать свои собственные проблемы, мы, разработчики, поощряемся к поиску решений, которые помогут справиться с этими проблемами.
Хост-контекст — это лишь один из примеров такого решения, и мы уверены, что он послужит нам прочной основой для решения других подобных проблем, связанных с общими данными и инструментами в мире микроинтерфейса. Мы стремимся найти решение, оказывающее значительное влияние на поведение инфраструктуры и минимально влияющее на цикл разработки.
Хотя разные методологии могут различаться, особенно с более современными технологиями (например, микроинтерфейсами), проблемы, как правило, похожи друг на друга, и решения могут быть немного скорректированы, чтобы приспособиться к ним. Я предлагаю подумать о том, что подходит для каждого решения, с которым вы сталкиваетесь, и убедиться, что концепции правильно согласованы для решения конкретных проблем вашего приложения.