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

Auth

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

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

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

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

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

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

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

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

Entities

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

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

Стоит понимать, что нередко в приложении есть публичный "внешний" пользователь (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 и для описанных ниже слоев

entities/user/ui/index.ts
export { ViewerCard } from "./card";
export { ViewerAvatar } from "./avatar";
...

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

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

entities/user/model/index.ts
export * as selectors from "./selectors";
export * as events from "./events";
export * as stores from "./stores";
...
entities/user/index.ts
export * from "./ui"
export * as viewerModel from "./model";

ui

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

entities/user/ui/card/index.tsx
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

  • Авторизация по внешнему ресурсу
features/auth/by-oauth/ui.tsx
import { viewerModel } from "entities/viewer";

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

export const Wallet = () => {
const viewer = viewerModel.useViewer();
const { moneyCount } = viewer;

...
}
  • Использование компонентов вьювера
features/header/ui.tsx
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 фич на страницах
pages/user/ui.tsx
import { Wallet } from "features/wallet";
import { ViewerCard } from "entities/viewer";
...
export const UserPage = () => {
...
return (
<Layout>
<Header
extra={<Wallet.AddFunds />}
/>
...
<ViewerCard />
</Layout>
)
}
  • Использование модели вьювера
pages/some/ui.tsx
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

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

https://t.me/feature_sliced/4618

Login

https://t.me/feature_sliced/3227

Logout

https://t.me/feature_sliced/3227

См. также

[01/12] logo-mini