Skip to content

Instantly share code, notes, and snippets.

@crutch12
Last active January 28, 2026 07:50
Show Gist options
  • Select an option

  • Save crutch12/2731900aab5237af236521f59e091e57 to your computer and use it in GitHub Desktop.

Select an option

Save crutch12/2731900aab5237af236521f59e091e57 to your computer and use it in GitHub Desktop.
Ресурс - Архитектура (микросервисы)

Инструменты/библиотеки

Общее

  • основной стек
    • typescript
    • react
    • webpack/vite module federation
  • основной стек библиотек
  • (возможно) используем bit.dev для разработки библиотек компонентов
  • отказываемся от next.js - надо раскурить, как у него дела с mf сейчас. UPD: немного раскурил - стало лучше, но loadRemote НЕ РАБОТАЕТ
  • (возможно) используем фреймворк для mf - piral или tramvai.js

Сборка

Module Federation

  • vite
  • webpack
  • next.js

Shared зависимости

  • react
  • react-dom
  • react-router
  • react-routed-dom
  • styled-components / emotion
  • @resource/ui-config

Свои библиотеки (npm)

Для работы микрофронтов вместе нужно реализовать несколько библиотек

  • @resource/configs - конфиги (eslint/prettier/standard version/stylelint/etc.)
  • @resource/ui-app - конфиги сборки (vite, webpack, next)
  • @resource/ui-config - общий Context между микрофронтами
Предоставляет
- компоненты, завязанные на общий Context (AuthProvider, LogsProvider, Router, etc.)
- инстансы общих сервисов (eventBusService, logsService, etc.)
  • @resource/ui-base - общие компоненты (кнопочки и т.д.)
  • @resource/api-schemas - api swagger схемы (+ ts) всех микросервисов
    • @resource/api-schemas-* - api swagger схемы (+ ts) для каждого микросервиса
  • @resource/fetchers - методы для генерации http/graphql/etc. клиентов (в основном - taxios)
  • @resource/federated-types - typescript декларации каждого *-mf микрофронта
  • (позже)
    • @resource/ui-utils - утилитарные функции фронта
    • @resource/generator - генерация файловой структруы микрофронтов через cli

Используемые библиотеки

  • @module-federation/enhanced vite-plugin-federation - сборка микрофронта
  • @module-federation/runtime - подключение микрофронтов
  • @module-federation/native-federation-typescript - генерация типов микрофронтов
  • @mui/material - UI kit

Организация фронта

Микрофронты

Мы используем “Стандартный подход”. Это значит, что каждому микрофронту выделяется свой кусок URL

Однако это не отменяет того, что микрофронты могут предоставлять дополнительные компоненты, которые могут использовать другие микрофронты.

image

Сервисы

  • @resource/*-host-frontend - точка входа в ui приложение (host)
    • @resource/resource-host-frontend - точка входа в ui приложение ресурс
    • @resource/burnee-host-frontend - точка входа в ui приложение burnee
  • @resource/sandbox-frontend - среда для отладки микрофронтов
  • @resource/core-mf - предоставляет служебные сервисы (eventbus/etc.), авторизацию, ролевую систему, конфиги
  • @resource/*-mf - микрофронт под конкретный домен/бизнес функцию

Взаимодействие

  • Роутинг: предоставляет @resource/core-mf
  • Запросы: через api-gw
  • Обмен данными/событиями (между микрофронтами): eventbus
  • Общие данные (user/configs/etc.): предоставляет @resource/core-mf

(полезное) Статья: https://holyjs.ru/talks/0beb987452b747b28fe522bf7ecdd384/

Общая схема

См. https://excalidraw.com/#json=MjcGDwkHuzjtgI50gw5-T,jaoLSrtlFK_Tfj7rmUVx1g

image

С шиной и api gateway

image

Подробнее

@resource/resource-host-frontend

@TODO: Надо придумать, как нормально работать с конфигами

import { loadRemote, init } from '@module-federation/runtime';

// .env
// # CORE_MF_ENTRY=http://localhost:3000
// # CORE_MF_ENTRY=https://sut.web-beee.ru

// vite.config.ts
// proxy: { // (!) LOCAL ONLY (!)
//   '/apps/@resource/core-mf/**': process.env.CORE_MF_ENTRY,
//   '/apps/@resource/resource-mf/**': process.env.RESOURCE_MF_ENTRY,
//   '/apps/@resource/burnee-mf/**': process.env.BURNEE_MF_ENTRY,
// }

// @TODO - На подумать
// let config = {}
// window.setConfig = (_config) => {
//   config = _config
// }

init({
  name: '@resoruce/host',
  remotes: [
	  {
		  name: '@resource/core-mf',
		  entry: '/apps/@resource/core-mf/remoteEntry.js'
	  },
	  // ...
  ]
})

const CoreProvider = React.lazy(() => loadRemote('@resource/core-mf'))
const ResourceApp = React.lazy(() => loadRemote('@resource/resource-mf'))
const BurneeApp = React.lazy(() => loadRemote('@resource/burnee-mf'))

return <CoreProvider>
  <Routes>
    <Route path="/resource"> // config.resource.path
      <React.Suspense>
        <ResourceApp />
      </React.Suspense>
    </Route>
    <Route path="/burnee">
      <React.Suspense>
        <BurneeApp />
      </React.Suspense>
    </Route>
  </Routes>
</CoreProvider>

@resource/core-mf

image

@resource/*-mf

image

sandbox (standalone) режим

image
const CoreProvider = React.lazy(() => loadRemote('@resource/core-mf'))

ReactDOM.render(
    <CoreProvider>
        <App basePath={config.APP_BASE_URL} />
    </CoreProvider>
, document.getElementById(APP_ID));

Использование внутри Host

image

Использование компонентов из одних микрофронтов в других микрофронтах

image

Пример микрофронта

Файловая структура

├── public
│   ├── static
│   │   ├── **/*.*
│   ├── index.html
├── src
│   ├── index.ts
├── dist # ignored
├── node_modules # ignored
├── tsconfig.json
├── federation.config.json
├── vite.config.js # webpack.config.js
└── package.json

Зависимости

Обязательные

# npm
react
react-router

# custom
@resource/ui-app
@resource/ui-config
@resource/ui-base
@resource/ui-utils
@resource/api-schemas
@resource/federated-types
@resource/configs

Создание микрофронта (в будущем)

# Запускает процесс инициализации кодовой базы микрофронта для репозитория (https://github.com/yeoman/yo)
$ npx @resource/generator mf

Подключение микрофронта (react)

Подключение компонента (пример host)

// federationRuntime.ts

import { name, version } from '../../package.json';
import { createFederationRuntime, resolveEntryPath } from '@resource/ui-config';

// настраиваем подключение микрофронтов
const federationRuntime = createFederationRuntime({
  name, // '@resoruce/users-mf',
  version,
  remotes: [ // здесь указываем только те микрофронты, которые нам нужны
    // @NOTE: Возможно можно вынести в библу общим списком (?)
    {
      name: '@resource/core-mf',
      // (dev) proxy /apps/@resource/core-mf -> http://localhost:3001 (core-microfrontend)
      entry: resolveEntryPath('/apps/@resource/core-mf/remoteEntry.js'),
    },
  ],
});

export default federationRuntime;
// App.tsx

import React from 'react';
import federationRuntime from '~/lib/federationRuntime';

const Users = React.lazy(() => federationRuntime.loadRemote('@resoruce/users-mf/Users'))

const App = () => {
	return <React.Suspense fallback="Загрузка">
	  <Users />
	</React.Suspense>
}
// пример нахера это надо

// Button.tsx // @resource/core-mf

const Button = () => {
  return <button />
}

export default Button;

// Card.tsx // @resource/users-mf

import { loadRemote} from '@module-federation/runtime';

const Button = React.lazy(() => loadRemote('@resource/core-mf/Button'))

const Card = () => {
  return <div><Button /></div>
}

export default Card;

// App // @resource/resource-mf

import { init, loadRemote } from '@module-federation/runtime';

init(/* ... */) // init remotes: [@resource/users-mf, @resource/users-mf]

// (!) ERROR (!)
// 2й дочерный микрофронт не знает про init родителя -> loadRemote сфейлится
// и не должен (!), у каждого consumer'а свой init
const Card = React.lazy(() => loadRemote('@resource/users-mf/Card '))

const App = () => {
	return <Card />
}

Использование контекста/хуков микрофронтов (react) (пример host)

// Например нам нужен AuthProvider + хук useAuth из другого микрофронта

// Button.tsx

// (!) Используем top level await (!)
const useAuth = await loadRemote('@resoruce/users-mf/AuthProvider').then(module => module.useAuth)

const Button = () => {
  const auth = useAuth()
  // ....
}
// App.tsx

import React from 'react';
import federationRuntime from '~/lib/federationRuntime';

const Button = React.lazy(() => import('./Button'))
const AuthProvider = React.lazy(() => federationRuntime.loadRemote('@resoruce/users-mf/AuthProvider'))

const App = () => {
	return <React.Suspense fallback="Загрузка">
	  <AuthProvider>
		  <Button />
	  </AuthProvider>
  </React.Suspense>
}

Ссылки

Алгоритм перехода на целевую архитектуру фронта

Этап 1 - “ещё не микрофронт”

  • Реализуем библиотеку @resource/configs с eslint/stylelint/prettier/tsconfig/etc. конфигами
  • Реализуем библиотеку @resource/ui-app с vite конфигом
  • Выпиливаем next.js
    • Конфиги заменяем импорт из @resource/configs

    • Сборку заменяем на @resource/ui-app

    • Роутер - react-router@6

      • Берём и , не используем createBrowserRouter
        • UPD: НАДО ИССЛЕДОВАТЬ С useRoutes
      • Все роуты тащим в константу Routes.ts
      // Routes.ts
      
      const APP_BASE_ROUTE = '/ui';
      
      const APP_ROUTES = {
        HOME: `${APP_BASE_ROUTE}`, // можно использовать generateRoute
        USERS: `${APP_BASE_ROUTE}/users`,
        USER_PAGE: `${APP_BASE_ROUTE}/users/:id`,
      } as const; // обязательно, чтобы типы сохранились
      
      export { APP_ROUTES as Routes }
      
      // (См. после настройки типов (!))
      
      // Пример использования в другом микрофронте
      import type { Routes } from '@resource/federated-types'
      import { generateRouting } from '@resource/ui-routing'
      
      const generatePath = generateRouting<Routes>()
      
      return <Link to={generatePath<Routes>('/ui/users/:id', { id: 123 })} />
      // TS ERROR
      return <Link to={generatePath<Routes>('/ui/users1/123123' as Routes[string])} />
      • Настраиваем

        • Формируем массив AppPages
        import React from 'react';
        
        import { Routes } from '~/constants/Routes';
        
        interface AppPage {
          path: typeof Routes[keyof typeof Routes];
          page: React.ElementType;
          exact?: boolean;
          // ...
        }
        
        const AppPages: AppPage[] = [
          {
            path: Routes.HOME,
            page: () => import('../../pages/home/index.tsx'), // заготовка под lazy
            exact: true,
          },
          // ...
        ]
        
        export { AppPages }
      • Инициализируем

        import React from 'react';
        import { Routes, Route } from 'react-router-dom';
        
        interface AppRouterProps {
          appRoutes: typeof AppRoutes;
        }
        
        const AppRoutes = ({ appRoutes }: AppRouterProps) => {
          // где-то выше в AppProvider рисуем <Outlet />, в него пихаем <AppErrorBoundary> и прочее
          return (
            <Routes>
              {appRoutes.map((appRoute) => (
                <Route
                  key={appRoute.path}
                  exact={appRoute.exact}
                  path={appRoute.path}
                  element={appRoute.page} # ТУТ НАДО SUSPENSE (lazy)
                />
              ))}
              // <PageError /> - компонент ошибки
              <Route path="*" element={<PageError statusCode={404} />} />
            </Routes>
          );
        };
        
        export default AppRoutes;
      • Настраиваем lazy

    • Настраиваем proxy до других микрофронтов в dev режиме

      • НЕ используем proxy в прод режиме, больше у фронтов не будет своего сервера (в дев режиме можно использовать)
  • Выносим shared компоненты в библиотеку @resource/ui-base, убираем зависимости от next (если они есть)
    • НУЖЕН АНАЛИЗ, ЧТО ТАЩИМ В БИБЛУ, А ЧТО НЕТ
    • для сборки используем tsup
  • Подготавливаем “модульность”
    • Api

      • Выпиливаем глобальные fetch функции (инкапсулируем в Service классы)
      • taxios инстансы запихиваем в ApiProvider, а сервисы - в ServicesProvider / FetchersProvider
        • ApiProvider обязательно принимает basePath путь до API
        • const { usersFetcher, projectsFetchers } = useFetchers()
    • CoreProvider, AppProvider

      # Примерная (схематичная) файловая структура промежуточного перехода на Module Federation
      
      # Примеры компонентов могут быть разбиты н дополнительные файлы/папки
      
      - /exposed
        - Button.tsx // <AppProvider><Button /></ AppProvider>
      - /src
        - /components
          - /app
            - AppProvider.tsx # тут весь необходимый ВНУТРЕННИЙ Context обвес (ApiProvider, ServicesProvider, ThemeProvider, стор, модальки и прочее)
            - App.tsx # Рисует <Routes> (компоненты берёт из /src/pages)
          - /button
            - Button.tsx
        - /pages
          - ... # Страницы
        - CoreProvider.tsx # тут весь необходимый ВНЕШНИЙ Context обвес (BrowserRouter, UserProvider, AvilityProvider и прочее) (потом переедет в отдельный микрофронт)
        - bootstrap.tsx # Рисует <CoreProvider><AppProvider><App /></AppProvider></CoreProvider>
        # @FIXME: Возможно нужен standalone под каждый Exposed компонент
        - index.ts # импортирует bootstrap.tsx ("standalone режим")
    • Реализуем UserProvider, AbilityProivder, вытаскиваем эту логику из mobx

Этап 1.1 - “автотесты”

  • @TODO
  • Прикручиваем автотесты в либы
    • vitest (unit, integration)
    • react testing library (snapshot)
    • playwright (e2e)
  • Прикручиваем автотесты в проекты
    • (всё, что сверху)
    • msw (+ kubb faker) (mock)

Этап 2 - “микрофронты”

  • Реализуем Module Federation конфиг в @resource/ui-app
  • Реализуем @resource/ui-config библиотеку
    • Выносим все ВНЕШНИЕ Context’ы, которые предоставляет CoreProvider (AbilityContext, UserContext, etc.)
    • Реализуем методы для работы с Module Federation (на базе @module-federation/runtime)
      • createFederationRuntime
      • resolveEntryPath
  • Реализуем core-mf приложение
    • Тащим конфиг из @resource/ui-app
    • Тащим контексты из @resource/ui-config, реализуем CoreProvider логику
    • Настраиваем Module Federation (createFederationRuntime)
    • Экспортируем CoreProvider в exposed директорию (@resource/core-mf/CoreProvider)
  • Настраиваем Module Federation на фронте (для начала resource и burnee)
    • Тащим конфиг из @resource/ui-app
    • Экспортируем App и другие компоненты в exposed директорию
    • Подключаем CoreProvider из core-mf, выпиливаем собственную реализацию
      • Для подключения используем createFederationRuntime
      • proxy в дев режиме
  • Реализуем host приложение (оно же sandbox?)
    • Тащим конфиг из @resource/ui-app
    • Подключаем CoreProvider из core-mf
    • Настраиваем Routes, Подключаем микрофронты
      • proxy в дев режиме

Этап 3 - “больше библиотек”

  • Реализуем @resource/federated-types для работы с типами микрофронтов
  • Реализуем @resource/api-schemas для работы с taxios схемами бэкенд сервисов
  • Реализуем @resource/fetchers для генерации taxios инстансов

Этап 4 - “удобства”

  • Реализуем @resource/generator библиотеку с шаблонами генерации микрофронтов (и не только)

СУТ и микросервисы

Общие идеи

Сервисы

  • каждый сервис даёт api для ui / internal (в сваггере это два файла)
  • каждый сервис даёт общий интерфейс базовых апишек (/version)
  • каждый сервис версионирует api (/v1)
  • фича флаги можно настраивать для каждого сервиса
  • у фронта и бэка общие фича флаги
  • у каждого сервиса своя база данных (+ pgBouncer)
  • всё работает на общем домене

Фронты

  • каждый фронт использует module federation
    • отказ от Next.js, т.к. он не позволяет module federation. он как бы позволяет, но работает очень плохо и не умеет в loadRemote - module-federation/core#2530)
  • у фронта нету своего веб сервера, он лежит на cdn (или аналоге)
  • не поддерживаем ssr
  • статика фронта версионируется
  • каждый фронт раздаёт базовый компонент App с одинаковым (!) интерфейсом
  • каждый фронт раздаёт ts типы под module federation, которые тоже идут на стенд
  • все фронты используют общий стек, общий ui-kit и общие библиотеки компонентов

Репозитории

  • у каждого фронта и бэка свой репозиторий, со специальным неймингом (см. ниже)
  • у каждой общей библиотеки свой репозиторий, со специальным неймингом
  • каждый репозиторий должен быть платформонезависимым

В целом

  • для фича флагов и настроек нужна админка. возможно своя админка на каждый сервис

Организация кода

https://bitbucket.web-bee.ru/projects/RESOURCE

Шаблон формироавния имён репозиториев: {система}.{домен}.{направление}.{имя}

  • система - resource
  • домен - core/shared/users/integrations/...
  • направление - ui, ml, api, libs
  • имя - users/project/discord-bot/...

Примеры

  • сервис авторизацияя - resource.users.api.auth
  • фронтовый сервис (микрофронт) - resource.users.ui.auth
  • ui библиотека - resource.shared.libs.ui-config
  • node.js/java/python библиотека - resource.shared.libs.my-library
  • сервис распознования эмоций - resource.integrations.ml.emotions
  • дискорд бот - resource.integrations.api.discord-bot

Frontend (Microfrontend)

Frontend

Инструменты

Backend

  • backend:
    • node.js (nest.js)
    • java (spring boot)
    • python (fastapi)
  • база данных: postgres (+ pgBouncer)
  • единый кэш: redis
  • брокер сообщений: kakfa (т.к. нужен опыт) + rabbitmq (для более простых вещей)
  • cdn: ??? (возможно просто в minio будем лить и через nginx раздавать)
  • registry образов: harbor
  • registry пакетов: nexus
  • api gateway: ??? (лучше взять коробочный, а не самописный, например Kong Gateway или Envoy)
  • протокол общения между сервисами: REST (не gRPC, т.к. ещё одна зависимость, не хочется плодить лишние протоколы)
  • tracing: ??? (jaeger/elastic-apm/zipkin)
  • s3 storage: minio
  • service proxy: ??? (Envoy/Traefik)
  • service discovery: ??? (Consul)
  • service mesh: ??? (Istio)

CI/CD

(тут пока пусто)

Библиотеки

Документация

См. https://habr.com/ru/companies/oleg-bunin/articles/649319/

  • нужно создать документ, описывающий принципы межсервисных взаимодействий (swagger, подпись запросов, авторизация, протокол взаимодействия и т.д.)

СУТ

Домены

  • пользователи
  • проекты
  • трудозатраты и планирование
  • интеграции
  • аналитика

Сервисы

  • администрирование
  • авторизация

Основные проблемы

  • согласование данных
  • бойлерплейт проектов
  • общий протокол (например заголовок с id пользователя)
  • трейсинг
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment