Auth
Every application has business logic related with the current authorized user.
Usually such an entity is called
Viewer
/Principle
/Session
- but within the framework of the article, we will focus onviewer
, but it all depends on your project and team
Also, this is one of the illustrative examples when a business entity generates business features, then pages, and even business processes
Let's look at them in more detail below with examples
-
The names of the directories inside the segments (ui, model) may differ from project to project
The methodology does not regulate this level of nesting in any way yet
-
It should also be understood that the examples below are abstract and synthetic, and are formed to demonstrate only the concepts of the methodology
FSD does not regulate the best practices of a particular data-fetcher or state-manager
Entities
The business entity of the user
- Represents the most atomic abstraction for design
- Here the authorization context is formed, which is then usually relied on by all the overlying layers of the application
It should be understood that often there is a public "external" user (entities/user
) in the application, and there is an authorized "internal" user (entities/viewer
)
Do not forget to take this difference into account when designing architecture and logic
Examples
# Segments can be both files and directories
|
โโโ entities/viewer # Layer: Business entities
| | # Slice: Current user
| โโโ ui/ # Segment: UI-logic (components)
| โโโ lib/ # Segment: Infrastructure-logic (helpers/utils)
| โโโ model/ # Segment: Business Logic
| โโโ index.ts # [Public API Declaration]
| ...
entities/viewer
- the entity of the current user (Session / Principle)entities/user
- the essence of public user (not necessarily associated with the current)- Depending on the complexity of your application - you can use the
user
for naming the current user - But it can cause serious problems when/if I have to separate the logic of a normal user and current, who came into the system
- Depending on the complexity of your application - you can use the
index.ts
The usual Public API of the module
Largely repeats the API declaration for the layers described below
export { ViewerCard } from "./card";
export { ViewerAvatar } from "./avatar";
...
- Redux
- Effector
In redux, the redux-ducks approach is generally accepted when its units (selectors/actions/...) they lie side by side and are clearly decomposed
But explicit decomposition is not required
export * as selectors from "./selectors";
export * as events from "./events";
export * as stores from "./stores";
...
The effector model will most often consist of a single file - because it is customary to store all units side by side there
If the units in the model can be schematically divided into several submodels, then you can explicitly do this denote in the Public API
export * as submodel1 from "./submodel1"
export * as submodel2 from "./submodel2"
...
export * from "./ui"
export * as viewerModel from "./model";
ui
It may contain components that are not related to a specific page/feature, but directly to the user's entity
import { Card } from "shared/ui/card";
// It is considered a good practice not to link ui components from entitites directly to the model
// So that it can be used not only for the current model,
// But also for externally received props
export type UserCardProps = {
data: User;
className?: string;
// And other card-props
};
export const UserCard = ({ data, ... }: UserCardProps) => {
return (
<Card
title={data.fullName}
description={data.bio}
...
/>
)
}
model
At this level, the entity of the current user is usually created, with the re-export of hooks/contracts/selectors for use by the overlying layers
- Redux
- Effector
// 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(...)
// entities/viewer/model/index.ts
export const $viewer = createStore(...);
export const $isAuth = $viewer.map((viewer) => !!viewer);
// **/**/ui.tsx
const viewer = useStore($viewer);
Also, other logic can be implemented here
updateUserDetails
logoutUser
- ...
Features
Features tied to the current user
- Uses business entities (often
entities/viewer
) and shared resources in the implementation - Features may not be directly related to the viewer, but they can use its context when implementing logic
Examples
# Segments can be both files and directories
|
โโโ features/auth # Layer: Business features
| | # Slice Group: The structural group "User authorization"
| โโโ by-phone/ # Slice: Feature "Authorization by phone"
| | โโโ ui/ # Segment: UI-logic (components)
| | โโโ lib/ # Segment: Infrastructure-logic (helpers/utils)
| | โโโ model/ # Segment: Business Logic
| | โโโ index.ts # [Public API Declaration]
| |
| โโโ by-oauth/ # Slice: Feature "Authorization by an external resource"
| ...
features/auth/{by-phone, by-oauth, logout ...}
- structural group of authorization features (by phone, by external resource, logout,...)features/wallet/{add-funds,...}
- structural group of features for working with the user's internal account (adding funds to the account,...)
ui
- Authorization by an external resource
import { viewerModel } from "entities/viewer";
export const AuthByOAuth = () => {
return (
<OAuth
domain={...}
scope={...}
...
// for redux, you additionally need dispatch
onSuccess={(user) => viewerModel.setUser(user)}
/>
)
}
- Using the user's context in features
import { viewerModel } from "entities/viewer";
export const Wallet = () => {
const viewer = viewerModel.useViewer();
const { moneyCount } = viewer;
...
}
- Using the viewer components
import { ViewerAvatar } from "entities/viewer";
...
export const Header = () => {
...
return (
<Layout.Header>
...
<ViewerAvatar
onClick={...}
onLogout={...}
...
/>
</Layout.Header>
)
}
Pages
Pages related to the current user in one way or another
- They can both directly affect the functionality of the viewer
- And use it indirectly (including its context / features)
Examples
# Segments can be both files and directories
|
โโโ pages/viewer # Layer: Application pages
| | # Slice Group: The "Current User" structural group
| โโโ profile/ # Slice: The viewer profile page
| | โโโ ui.tsx # Segment: UI-logic (components)
| | โโโ lib.ts # Segment: Infrastructure-logic (helpers/utils)
| | โโโ model.ts # Segment: Business Logic
| | โโโ index.ts # [Public API Declaration]
| |
| โโโ settings/ # Slice: The viewer account settings page
| ...
pages/viewer/profile
- the user's LC pagepages/viewer/settings
- user account settings pagepages/user
- the user's page (not necessarily the current one)pages/auth/{sign-in, sign-up, reset}
structural group of authorization pages (login / registration / password recovery)
ui
- Using the viewer components and viewer-based features on the pages
import { Wallet } from "features/wallet";
import { ViewerCard } from "entities/viewer";
...
export const UserPage = () => {
...
return (
<Layout>
<Header
extra={<Wallet.AddFunds />}
/>
...
<ViewerCard />
</Layout>
)
}
- Using the viewer model
import { viewerModel } from "entities/viewer";
...
export const SomePage = () => {
...
return (
<Layout>
...
<Settings onSave={(payload) => viewerModel.saveChanges(payload)} />
</Layout>
)
}
Processes
Business processes affecting the current user
- Affects user cases that permeate the pages of the system
- The process layer is optional, and is usually used only when the logic grows in pages and you need separate context management on several pages at once
Examples
# Segments can be both files and directories
|
โโโ processes # Layer: Business processes
| โโโ auth/ # Slice: User authorization process
| | โโโ lib.ts # Segment: Infrastructure-logic (helpers/utils)
| | โโโ model.ts # Segment: Business Logic
| | โโโ index.ts # [Public API Declaration]
| |
| โโโ quick-tour/ # Slice: The process of onboarding a new user
| ...
processes/auth
- the business process of user authorizationprocesses/quick-tour
- a business process for familiarizing the user with the system (~ UserOnboard)
App
Initialization of user account data
- There is usually a check on whether the user was already logged in before he entered the service
- If yes - the provider replenishes the user's context for further use in the system
- If not - the authorization process is started or the context of the viewer is changed so that the authorization page takes the necessary actions
Examples
# The `app` structure is unique for each project and is not regulated by the methodology
|
โโโ app/providers # Layer: Initializing the application (HOCs providers)
| โโโ withAuth.tsx # HOC: Initializing the authorization context
| | ... #
| ...
app/providers/withAuth
- HOC for user authorization- Used only at the top level, as a provider with logic initialization, to which only app-layer
- Not to be confused with the
useViewer
hook, which is accessed by all other layers (processes / pages / features)
Conclusions
As we can see in the examples above - all business logic begins to be built from a single entity, and can spread to the very top layer.
Therefore, you need to be able to correctly allocate the scope of the module's influence, and depending on this, choose the appropriate layer for the location of the logic.
Thus, we will get the most supported, readable and reused code.
FAQ
Maqola yozilish jarayonida
Uning yaratilishini tezlashtirish uchun siz:
- ๐ข Fikr-mulohaza ulashing chiptada (sharhlar/reaksiya emoji)
- ๐ฌ Mavzu boyicha to'plamlarni chiptaga yig'ish suhbatdan olingan material
- โ๏ธ Hissa qoโshish har qanday boshqa yo'l bilan
๐ฐ Stay tuned!
How to pass a token
Login
Logout
See also
- Discussion "The applicability of Feature-Sliced Design in combat" (there are also examples with viewer inside)
- Question about the user's profile and identity (community-chat)
- Explanation about UIKIT#Card and User#Card (community-chat)