Расширения FEOD для продвинутых проектов
Расширения для FEOD — это способ адаптации под конкретные подходы и нужды. В отличие от модификаций, они не предполагают альтернативный подход, а просто добавляют возможности или делают правила более строгими.
SSR (Server-Side Rendering)
FEOD изначально спроектирован так, чтобы поддерживать SSR. Для работы с ним есть готовый пример, и, что важно, ключевые отличия кроются в организации приложения.
Вместо единого App мы можем вынести отдельный уровень Server. Туда попадают части, связанные исключительно с сервером и недоступные нигде больше.
Клиент и сервер имеют отдельные точки входа:
entry.server.ts— точка входа сервераentry.client.ts— точка входа клиента
Общая фабрика создаёт приложение, роутер и Pinia заново на каждый серверный запрос. В примере страница статическая; для серверной загрузки данных нужно также передать клиенту начальное состояние с безопасной сериализацией перед hydration.
Если же мы говорим об использовании готовых фреймворков, то вы можете использовать их встроенные возможности для SSR и не подстраиваться схожим образом.
Пример структуры для SSR
Dependency Injection
В FEOD акцент сделан на минимизацию кросс-модульных взаимодействий в пользу Inversion of Control. Одной из наиболее эффективных реализаций является Dependency Injection. Для этого можно использовать различные инструменты, например SDI (Simple Dependency Injection) или другие полноценные DI-решения.
Подход делает зависимости явными и управляемыми. Это особенно заметно, когда проект разрастается, и поддержка структуры становится критически важной.
Пример использования IoC
// modules/Notifications/index.ts
export interface NotificationService {
show(message: string): void
}
export function createNotifications(service: NotificationService) {
return { success: (message: string) => service.show(message) }
}// modules/ToastUI/index.ts
export function createToastService(container: HTMLElement) {
return {
show(message: string) {
const toast = document.createElement('p')
toast.textContent = message
container.append(toast)
}
}
}// app/integrations/notifications.ts
import { createNotifications } from '@/modules/Notifications'
import { createToastService } from '@/modules/ToastUI'
const container = document.querySelector<HTMLElement>('#notifications')
if (!container) throw new Error('Нет контейнера уведомлений')
export const notifications = createNotifications(createToastService(container))
notifications.success('Изменения сохранены')Контейнер #notifications находится в HTML приложения. На каждый запуск создаётся отдельный сервис; модули не хранят общую изменяемую зависимость.
Преимущества IoC в FEOD
- Явные зависимости — легко понять, от чего зависит модуль
- Упрощённое тестирование — можно легко подменить реализации
- Слабая связанность — модули не зависят от конкретных реализаций
- Гибкость — можно менять реализации без изменения модулей
Интеграция с DDD (Domain-Driven Design)
Если в вашей команде много бэкенд-разработчиков и они привыкли мыслить в терминах DDD, FEOD легко подстраивается под этот подход. В модулях можно выделить дополнительные сущности — Entities и Adapters.
Для тех, кто знаком с DDD, назначение этих частей очевидно. Они помогают ещё чётче определять входы и выходы модуля, обеспечивая строгую структуру и понятные границы.
Пример структуры модуля с DDD
Ниже показана загрузка пользователя: адаптер преобразует ответ API в сущность. Остальные части модуля здесь опущены.
Entities в контексте FEOD
Entities в DDD-подходе представляют бизнес-сущности с их поведением. В FEOD они размещаются внутри модуля в папке domain/entities/.
// modules/UserManagement/domain/entities/User.ts
export class User {
constructor(readonly id: string, private email: string) {
this.updateEmail(email)
}
updateEmail(email: string): void {
const normalized = email.trim()
if (!normalized) throw new Error('Email не может быть пустым')
this.email = normalized
}
getEmail(): string {
return this.email
}
}Adapters в контексте FEOD
Adapters обеспечивают взаимодействие модуля с внешним миром:
- Primary adapters (входные) — UI-компоненты, которые используют модуль
- Secondary adapters (выходные) — API-клиенты, репозитории, внешние сервисы
// modules/UserManagement/adapters/secondary/api/userApi.ts
import { User } from '../../../domain/entities/User'
interface UserResponse {
id: string
email: string
}
export class UserApi {
async fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${encodeURIComponent(id)}`)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const dto: UserResponse = await response.json()
return new User(dto.id, dto.email)
}
}Такой способ интеграции FEOD особенно полезен для проектов, где требуется дисциплинированная архитектура и чёткое разделение между бизнес-логикой и техническими деталями.
Использование FEOD за пределами фронтенд-приложений
Открытый вопрос: можно ли использовать FEOD за пределами фронтенд-приложений? Например, в разработке инструментов для линтинга, UI-библиотек или любых других пакетов, которые сами по себе не являются фронтенд-приложениями.
Эта идея пока остаётся в стадии обсуждения и проработки. Но направление интересное: если удастся масштабировать FEOD на такие кейсы, мы получим универсальный архитектурный инструмент, применимый гораздо шире, чем просто в рамках интерфейсных проектов.
Потенциальные области применения
- UI-библиотеки — организация компонентов и утилит
- Инструменты разработки — линтеры, плагины, утилиты
- BFF и полноценный бэкенд