Skip to main content

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 on viewer, 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

  1. 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

  2. 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

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";
...

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";
...
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

// 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(...)

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 page
  • pages/viewer/settings - user account settings page
  • pages/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 authorization
  • processes/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

The article is in the process of writing

To bring the release of the article closer, you can:


๐Ÿฐ Stay tuned!

How to pass a token

https://t.me/feature_sliced/4618

Login

https://t.me/feature_sliced/3227

Logout

https://t.me/feature_sliced/3227

See also