Перейти к основному содержимому

Auth

Во всех приложениях так или иначе есть бизнес-логика, завязанная на текущем авторизованном пользователе.

Обычно такая сущность называется Viewer / Principle / Session - но в рамках статьи, остановимся именно на viewer, но все зависит от вашего проекта и команды

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

Рассмотрим их подробнее ниже с примерами

  1. Названия директорий внутри сегментов (ui, model) могут отличаться от проекта к проекту

    Методология пока никак не регламентирует этот уровень вложенности

  2. Стоит также понимать, что приведенные ниже примеры - абстрактны и синтетичны, и сформированы для демонстрации только концепций методологии

    FSD не регламентирует в себе бест-практисы конкретного дата-фетчера или стейт-менеджера

Entities

Бизнес-сущность пользователя

  • Представляет собой наиболее атомарную абстракцию для проектирования
  • Здесь формируется контекст авторизации, на который потом обычно полагаются все вышележащие слои приложения

Стоит понимать, что нередко в приложении есть публичный "внешний" пользователь (user), а есть авторизованный "внутренний" пользователь (viewer)

Не забывайте учитывать эту разницу при проектировании архитектуры и логики

Примеры

# Сегменты могут быть как файлами, так и директориями
|
├── entities/viewer              # Layer: Бизнес-сущности
|         |                      #     Slice: Текущий пользователь
|         ├── ui/                #      Segment: UI-логика (компоненты)
|         ├── lib/               #      Segment: Инфраструктурная-логика (helpers/utils)
|         ├── model/             #      Segment: Бизнес-логика
|         └── index.ts           #      [Декларация Public API]
|   ...           
  • entities/viewer - сущность текущего пользователя (Session / Principle)
  • entities/user - сущность публичного пользователя (не обязательно связанная с текущим)
    • В зависимости от сложности приложения - можно использовать и user для нейминга текущего пользователя
    • Но это может вызвать серьезные проблемы, когда/если придется разделять логику обычного пользователя и текущего, который зашел в систему

index.ts

Обычный Public API модуля

Во многом повторяет декларацию API и для описанных ниже слоев

export { ViewerCard } from "./card";
export { ViewerAvatar } from "./avatar";
...

В редаксе общепринят подход redux-ducks, когда его юниты (selectors/actions/...) лежат рядом и явно выделены

Но явное выделение далеко не всегда обязательно

export * as selectors from "./selectors";
export * as events from "./events";
export * as stores from "./stores";
...
export * from "./ui"
export * as viewerModel from "./model";

ui

Здесь могут содержаться компоненты, относящиеся не к конкретной странице/фиче, а напрямую к сущности пользователя

import { Card } from "shared/ui/card";

// Считается хорошей практикой - не связывать напрямую с моделью ui-компоненты из entitites
// Чтобы можно было использовать не только для текущей модели,
// Но и для поступивших извне пропсов

export type UserCardProps = {
    data: User;
    className?: string;
    // И прочие card-пропсы
};

export const UserCard = ({ data, ... }: UserCardProps) => {
    return (
        <Card 
            title={data.fullName}
            description={data.bio}
            ...
        />
    )
}

model

На этом уровне обычно создается сущность текущего пользователя, с реэкспортом хуков/контрактов/селекторов для использования вышележащими слоями

// entities/viewer/model/selectors.ts
export const useViewer = () => {
    return useSelector((store) => store.entities.userSlice);
}
export const useAuth = () => {
    const viewer = useViewer();
    return !!viewer
}
// entities/viewer/model/store.ts
export const userSlice = createSlice(...)

Также тут может быть реализована и другая логика

  • updateUserDetails
  • logoutUser
  • ...

Features

Фичи, завязанные на текущем пользователе

  • Использует в реализации бизнес-сущности (зачастую - entities/viewer) и shared ресурсы
  • Фичи могут не быть напрямую связаны с вьювером, но при этом могут использовать его контекст при реализации логики

Примеры

# Сегменты могут быть как файлами, так и директориями
|
├── features/auth                # Layer: Бизнес-фичи
|        |                       #    Slice Group: Структурная группа "Авторизация пользователя"
|        ├── by-phone/           #      Slice: Фича "Авторизация по телефону"
|        |     ├── ui/           #         Segment: UI-логика (компоненты)
|        |     ├── lib/          #         Segment: Инфраструктурная-логика (helpers/utils)
|        |     ├── model/        #         Segment: Бизнес-логика
|        |     └── index.ts      #         [Декларация Public API]
|        |
|        ├── by-oauth/           #      Slice: Фича "Авторизация по внешнему ресурсу"
|   ...           
  • features/auth/{by-phone, by-oauth, logout ...} - структурная группа фич авторизации (по телефону, по внешнему ресурсу, выход из системы, ...)
  • features/wallet/{add-funds, ...} - структурная группа фич по работе со внутренним счетом пользователя (пополнение счета, ...)

ui

  • Авторизация по внешнему ресурсу
import { viewerModel } from "entities/viewer";

export const AuthByOAuth = () => {
    return (
        <OAuth
            domain={...}
            scope={...}
            ...
            // для redux - дополнительно нужен dispatch
            onSuccess=((user) => viewerModel.setUser(user))
        />
    )
}
  • Использование контекста пользователя в фичах
import { viewerModel } from "entities/viewer";

export const Wallet = () => {
    const viewer = viewerModel.useViewer();
    const { moneyCount } = viewer;
    
    ...
}
  • Использование компонентов вьювера
import { ViewerAvatar } from "entities/viewer";
...
export const Header = () => {
    ...
    return (
        <Layout.Header>
            ...
            <ViewerAvatar
                onClick={...}
                onLogout={...}
                ...
            />
        </Layout.Header>
    )
}

Pages

Страницы, так или иначе связанные с текущим пользователем

  • Могут как напрямую затрагивать функциональность вьювера
  • Так и использовать его косвенно (в том числе - и его контекст / фичи)

Примеры

# Сегменты могут быть как файлами, так и директориями
|
├── pages/viewer                 # Layer: Страницы приложения
|        |                       #    Slice Group: Структурная группа "Текущий пользователь"
|        ├── profile/            #     Slice: Страница профиля вьювера
|        |     ├── ui.tsx        #         Segment: UI-логика (компоненты)
|        |     ├── lib.ts        #         Segment: Инфраструктурная-логика (helpers/utils)
|        |     ├── model.ts      #         Segment: Бизнес-логика
|        |     └── index.ts      #         [Декларация Public API]
|        |
|        ├── settings/           #     Slice: Страница настроек аккаунта вьювера
|   ...           
  • pages/viewer/profile - страница ЛК пользователя
  • pages/viewer/settings - страница настроек аккаунта пользователя
  • pages/user - страница пользователя (не обязательно текущего)
  • pages/auth/{sign-in, sign-up, reset} - структурная группа страниц авторизации (вход в систему / регистрация / восстановление пароля)

ui

  • Использование компонентов вьювера и viewer-based фич на страницах
import { Wallet } from "features/wallet";
import { ViewerCard } from "entities/viewer";
...
export const UserPage = () => {
    ...
    return (
        <Layout>
            <Header
                extra={<Wallet.AddFunds />}
            />
            ...
            <ViewerCard />
        </Layout>
    )
}
  • Использование модели вьювера
import { viewerModel } from "entities/viewer";
...
export const SomePage = () => {
    ...
    return (
        <Layout>
            ...
            <Settings onSave={(payload) => viewerModel.saveChanges(payload)} />
        </Layout>
    )
}

Processes

Бизнес-процессы, затрагивающие текущего пользователя

  • Затрагивает юзкейсы, пронизывающие страницы системы
  • Слой процессов - опционален, и обычно используется только когда логика разрастается в страницах и нужно отдельное управление контекстом на сразу нескольких страницах

Примеры

# Сегменты могут быть как файлами, так и директориями
|
├── processes                    # Layer: Бизнес процессы
|        ├── auth/               #     Slice: Процесс авторизации пользователя
|        |     ├── lib.ts        #         Segment: Инфраструктурная-логика (helpers/utils)
|        |     ├── model.ts      #         Segment: Бизнес-логика
|        |     └── index.ts      #         [Декларация Public API]
|        |
|        ├── quick-tour/         #     Slice: Процесс онбординга нового пользователя
|   ...           
  • processes/auth - бизнес-процесс авторизации пользователя
  • processes/quick-tour - бизнес-процесс для ознакомления пользователя с системой (~ UserOnboard)

App

Инициализация данных по учетной записи пользователя

  • Как правило, здесь проходит проверка на то, был ли уже авторизован пользователь до того, как зашел в сервис
    • Если да - провайдер пополняет контекст пользователя, для дальнейшего использования в системе
    • Если нет - запускается процесс авторизации или меняется контекст вьювера, чтобы страница авторизации предприняла нужные действия

Примеры

# Структура `app` уникальна для каждого проекта и не регламентируется методологией
|
├── app/providers                # Layer: Инициализация приложения (HOCs провайдеры)
|        ├── withAuth.tsx        #    HOC: Инициализация контекста авторизации
|        |   ...                 #
|   ...           
  • app/providers/withAuth - HOC для авторизации пользователя
    • Используется только на верхнем уровне, как провайдер с инициализацией логики, к которому имеет доступ только app-слой
    • Не путать с хуком useViewer, к которому идет обращение всех остальных слоев (processes / pages / features)

Выводы

Как мы видим на примерах выше - вся бизнес-логика начинает строиться от одной сущности и может распространиться до самого верхнего слоя.

Поэтому нужно уметь грамотно выделять скоуп влияния модуля и, в зависимости от этого, выбирать подходящий слой для расположения логики.

Таким образом, мы получим наиболее поддерживаемый, читаемый и переиспользуемый код

FAQ

Статья находится в процессе написания

Чтобы ускорить ее появление, можно:


🍰 Stay tuned!

Как прокинуть токен

https://t.me/feature_sliced/4618

Login

https://t.me/feature_sliced/3227

Logout

https://t.me/feature_sliced/3227

См. также