{"id":471340,"date":"2025-08-20T21:00:15","date_gmt":"2025-08-20T21:00:15","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=471340"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=471340","title":{"rendered":"<span>Clean Architecture \u0432\u043e frontend: \u043f\u043e\u0447\u0435\u043c\u0443 \u044f \u0443\u0448\u0451\u043b \u043e\u0442 FSD<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442! \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0445\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441 \u0442\u043e\u0431\u043e\u0439 \u043e\u043f\u044b\u0442\u043e\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043e\u0442 Feature-Sliced Design \u043a Clean Architecture \u0432\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435. \u041f\u043e\u0447\u0435\u043c\u0443 \u044f \u0441\u0447\u0438\u0442\u0430\u044e Clean Architecture \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0434\u043b\u044f \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0438 \u043a\u0430\u043a \u043e\u043d\u0430 \u0440\u0435\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0442\u044b \u0442\u043e\u0447\u043d\u043e \u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u043b\u0441\u044f.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0442\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0448\u044c FSD \u0438\u043b\u0438 \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u043f\u0438\u0448\u0435\u0448\u044c \u0432\u0441\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u0445 React \u2014 \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u0442\u043e\u0447\u043d\u043e \u0434\u043b\u044f \u0442\u0435\u0431\u044f.<\/p>\n<h3>FSD: \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u043e, \u043d\u043e \u043d\u0435 \u0431\u0435\u0437 \u043f\u0440\u043e\u0431\u043b\u0435\u043c<\/h3>\n<p>Feature-Sliced Design \u0441\u0435\u0439\u0447\u0430\u0441 \u043e\u0434\u043d\u0430 \u0438\u0437 \u0441\u0430\u043c\u044b\u0445 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u043b\u043e\u0433\u0438\u0439 \u0432\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435. \u0418 \u043d\u0435 \u0437\u0440\u044f \u2014 \u043e\u043d\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u043b\u0443\u0447\u0448\u0435, \u0447\u0435\u043c \u0445\u0430\u043e\u0442\u0438\u0447\u043d\u043e\u0435 \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432.<\/p>\n<h4>\u0427\u0442\u043e \u0445\u043e\u0440\u043e\u0448\u0435\u0433\u043e \u0432 FSD?<\/h4>\n<ul>\n<li>\n<p><strong>\u041f\u043e\u043d\u044f\u0442\u043d\u043e\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435<\/strong> \u043f\u043e \u0444\u0438\u0447\u0430\u043c \u0438 \u0441\u043b\u0430\u0439\u0441\u0430\u043c<\/p>\n<\/li>\n<li>\n<p><strong>\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/strong> \u2014 \u043b\u044e\u0431\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0431\u044b\u0441\u0442\u0440\u043e \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u0442\u0441\u044f<\/p>\n<\/li>\n<li>\n<p><strong>\u0418\u0437\u043e\u043b\u044f\u0446\u0438\u044f \u0444\u0438\u0447<\/strong> \u2014 \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0444\u0438\u0447\u0438 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0437\u0430\u0432\u0438\u0441\u0435\u0442\u044c \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430<\/p>\n<\/li>\n<\/ul>\n<h4>\u041d\u043e \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0438 \u043e\u043d\u0438 \u0441\u0435\u0440\u044c\u0451\u0437\u043d\u044b\u0435<\/h4>\n<p>\u0417\u0430 2 \u0433\u043e\u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 FSD \u044f \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0441 \u0440\u044f\u0434\u043e\u043c \u0431\u043e\u043b\u0435\u0432\u044b\u0445 \u0442\u043e\u0447\u0435\u043a:<\/p>\n<p><strong>1. Cross-\u0438\u043c\u043f\u043e\u0440\u0442\u044b \u2014 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u0430\u044f \u0433\u043e\u043b\u043e\u0432\u043d\u0430\u044f \u0431\u043e\u043b\u044c<\/strong><\/p>\n<pre><code class=\"typescript\">\/\/ \u0425\u043e\u0447\u0435\u0442\u0441\u044f \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a, \u043d\u043e \u043d\u0435\u043b\u044c\u0437\u044f: import { useAuth } from '@\/features\/auth\/model' import { PostsList } from '@\/features\/posts\/ui'  \/\/ FSD \u0437\u0430\u043f\u0440\u0435\u0449\u0430\u0435\u0442 \u043f\u0440\u044f\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043c\u0435\u0436\u0434\u0443 \u0444\u0438\u0447\u0430\u043c\u0438 \/\/ \u041f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u0437\u0433\u0430\u043b\u044f\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 shared \u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0441\u043b\u043e\u0438 <\/code><\/pre>\n<p><strong>2. \u041d\u0435\u044f\u0441\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u043d\u043e\u0441\u0442\u0438 \u043c\u043e\u0434\u0443\u043b\u0435\u0439<\/strong><br \/> \u041a\u0443\u0434\u0430 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c <code>NotificationService<\/code>? \u0412 shared? \u041d\u043e \u043e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0445 \u0444\u0438\u0447\u0430\u0445. \u0412 \u043e\u0434\u043d\u0443 \u0438\u0437 \u0444\u0438\u0447? \u041d\u043e \u0442\u043e\u0433\u0434\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u0444\u0438\u0447\u0438 \u043d\u0435 \u043c\u043e\u0433\u0443\u0442 \u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u041f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0442 \u0441\u043f\u043e\u0440\u044b \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u043e \u0442\u043e\u043c, \u043a\u0443\u0434\u0430 \u0447\u0442\u043e \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0441\u044f.<\/p>\n<p><strong>3. \u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u2014 \u043d\u0435 \u0441\u0430\u043c\u043e\u0435 \u0443\u0434\u043e\u0431\u043d\u043e\u0435<\/strong><\/p>\n<pre><code class=\"typescript\">\/\/ \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0443, \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0443\u0447\u0443 \u0444\u0430\u0439\u043b\u043e\u0432 import { loginModel } from '..\/model' import { api } from '..\/..\/shared\/api' import { router } from '..\/..\/shared\/router'  \/\/ \u0418 \u043c\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043d\u0438\u0445 jest.mock('..\/..\/shared\/api') jest.mock('..\/..\/shared\/router') <\/code><\/pre>\n<p><strong>4. \u041f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043b\u043e\u0433\u0438\u043a\u0438 \u2014 \u0431\u043e\u043b\u044c<\/strong><br \/> \u041a\u043e\u0433\u0434\u0430 \u043f\u043e\u0445\u043e\u0436\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u043d\u0443\u0436\u043d\u0430 \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0444\u0438\u0447\u0430\u0445, \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043b\u0438\u0431\u043e \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434, \u043b\u0438\u0431\u043e \u0432\u044b\u043d\u043e\u0441\u0438\u0442\u044c \u0432 shared \u0438 \u0442\u0435\u0440\u044f\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0444\u0438\u0447\u0438.<\/p>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u044f \u0432\u044b\u0431\u0440\u0430\u043b Clean Architecture<\/h3>\n<p>\u041f\u0440\u0438\u0448\u0451\u043b \u044f \u0432\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u0438\u0437 Android \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 (Kotlin + Compose), \u0433\u0434\u0435 Clean Architecture \u2014 \u044d\u0442\u043e \u0443\u0436\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430 \u0441\u043b\u043e\u0432\u0430\u0445, \u0430 \u0432 \u0441\u0430\u043c\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u043e\u0442 google. \u0418 \u043f\u043e\u043d\u044f\u043b, \u0447\u0442\u043e \u043c\u043d\u043e\u0433\u0438\u0435 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b \u043e\u0442\u0442\u0443\u0434\u0430 \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0438 \u0432\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435.<\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f Clean Architecture \u2014 <strong>\u0438\u043d\u0432\u0435\u0440\u0441\u0438\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439<\/strong>. \u0411\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430 \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0434\u0435\u0442\u0430\u043b\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 (UI, API, \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435), \u0430 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442.<\/p>\n<h3>\u041c\u043e\u044f \u0432\u0435\u0440\u0441\u0438\u044f Clean Architecture<\/h3>\n<p>\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043c\u043e\u0434\u0435\u0440\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e Clean Architecture, \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0443\u044e \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u043d\u0443\u0436\u043d\u044b. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0442\u0440\u0451\u0445 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0447\u0430\u0441\u0442\u0435\u0439:<\/p>\n<ul>\n<li>\n<p><code><strong>core\/<\/strong><\/code> \u2014 \u0431\u0430\u0437\u043e\u0432\u0430\u044f \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 (DI, MVVM, Flow, UI \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b)<\/p>\n<\/li>\n<li>\n<p><code><strong>app\/<\/strong><\/code> \u2014 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0440\u043e\u0443\u0442\u0438\u043d\u0433, \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435<\/p>\n<\/li>\n<li>\n<p><code><strong>features\/<\/strong><\/code> \u2014 \u0431\u0438\u0437\u043d\u0435\u0441-\u0444\u0438\u0447\u0438 \u0441\u043e \u0441\u0442\u0440\u043e\u0433\u043e\u0439 \u0438\u0437\u043e\u043b\u044f\u0446\u0438\u0435\u0439<\/p>\n<\/li>\n<\/ul>\n<blockquote>\n<p>\u041f\u0440\u0438 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0447\u0442\u0435\u043d\u0438\u0438 \u0435\u0441\u043b\u0438 \u043d\u0435 \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u043d\u044f\u0442\u043d\u043e \u043f\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043b\u043e\u0435\u0432, \u0442\u043e \u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u0435\u0441\u0442\u044c \u043e\u0431\u0449\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c \u0442\u0443\u0434\u0430<\/p>\n<\/blockquote>\n<p>\u041a\u0430\u0436\u0434\u0430\u044f \u0444\u0438\u0447\u0430 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u0438\u0437 \u0442\u0440\u0451\u0445 \u0441\u043b\u043e\u0451\u0432:<\/p>\n<h4>1. Domain \u0441\u043b\u043e\u0439 \u2014 \u0441\u0435\u0440\u0434\u0446\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/h4>\n<p>\u0417\u0434\u0435\u0441\u044c \u0436\u0438\u0432\u0443\u0442 \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u043e\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0445 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0439:<\/p>\n<pre><code class=\"typescript\">\/\/ domain\/repository\/AuthRepository.ts export abstract class AuthRepository {   abstract tokensData: StateFlow&lt;TokensData | null&gt; | null   abstract getTokens(): TokensData | null   abstract setTokens(tokens: TokensData): void   abstract removeTokens(): void } <\/code><\/pre>\n<blockquote>\n<p>\u043f\u0440\u043e \u0442\u043e \u0447\u0442\u043e \u0442\u0430\u043a\u043e\u0435 StateFlow \u043c\u044b \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435<\/p>\n<\/blockquote>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0432 domain \u0441\u043b\u043e\u0435 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0442\u0430\u043a \u0436\u0435 use cases \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<br \/> <strong>Use Cases<\/strong> \u2014 \u044d\u0442\u043e \u043e\u0440\u043a\u0435\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438. \u041a\u0430\u0436\u0434\u044b\u0439 Use Case \u0440\u0435\u0448\u0430\u0435\u0442 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443:<\/p>\n<p>\u0423 use case \u0435\u0441\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044f executor \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043f\u0440\u0438\u043d\u044f\u0442\u043d\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c execute. \u042d\u0442\u043e \u043a\u0430\u043a \u0440\u0430\u0437 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0449\u0430\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0437\u0443\u044e\u0449\u0435\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f<\/p>\n<pre><code class=\"typescript\">\/\/ domain\/use_case\/LoginUseCase.ts export class LoginUseCase {   constructor(     @Inject(AuthNetwork) private readonly _authNetwork: AuthNetwork,     @Inject(AuthRepository) private readonly _authRepository: AuthRepository,     @Inject(UserStorage) private readonly _userStorage: UserStorage, \/\/ \u041c\u043e\u0436\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435   ) {}    async execute(body: LoginBody): Promise&lt;void&gt; {     \/\/ 1. \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043a\u044d\u0448     const cachedUser = await this._userStorage.getUser(body.email)     if (cachedUser?.isValid()) {       this._authRepository.setTokens(cachedUser.tokens)       return     }      \/\/ 2. \u0414\u0435\u043b\u0430\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u043a API     const tokens = await this._authNetwork.login(body)          \/\/ 3. \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0442\u043e\u043a\u0435\u043d\u044b     this._authRepository.setTokens({       accessToken: tokens.accessToken,       refreshToken: tokens.refreshToken,     })      \/\/ 4. \u041a\u044d\u0448\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f     await this._userStorage.saveUser(body.email, { tokens, timestamp: Date.now() })   } } <\/code><\/pre>\n<p><strong>\u041f\u0440\u043e\u0444\u0438\u0442<\/strong>: Use Case \u2014 \u044d\u0442\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041c\u043e\u0436\u0435\u0448\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443 \u2014 \u0432\u0441\u0451 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u043e.<\/p>\n<h4>2. Data \u0441\u043b\u043e\u0439 \u2014 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438<\/h4>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438 \u0438\u0437 Domain \u0441\u043b\u043e\u044f. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 Network \u0441\u043b\u043e\u0439 \u0441 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u043e\u0439 \u0442\u0438\u043f\u043e\u0432:<\/p>\n<pre><code class=\"typescript\">\/\/ data\/network\/AuthNetwork.ts import * as v from 'valibot'  \/\/ \u0421\u0445\u0435\u043c\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u043e\u0442 API const LoginResponseSchema = v.object({   accessToken: v.string(),   refreshToken: v.string(),   user: v.object({     id: v.string(),     email: v.string(),   }) })  export class AuthNetwork {   constructor(@Inject(Axios) private readonly _httpClient: Axios) {}    async login(body: LoginBody): Promise&lt;TokensDataWithRoleDto&gt; {     const response = await this._httpClient.post(\"\/api\/signin\", body)     return parseAsync(TokensDataWithRoleDtoSchema, response.data)   } } <\/code><\/pre>\n<p><strong>Runtime-\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445<\/strong> \u2014 \u0432\u0430\u0436\u043d\u043e \u043d\u0435 \u0434\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e TS-\u0442\u0438\u043f\u0430\u043c, \u0435\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0438\u0437\u0432\u043d\u0435. \u0412 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435 \u0442\u0438\u043f\u043e\u0432 \u0443 \u043d\u0430\u0441 \u043d\u0435\u0442. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e valibot \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0442\u0438\u043f\u0430 \u0438 \u043f\u0440\u043e\u0431\u0440\u043e\u0441\u0430 \u043e\u0448\u0438\u0431\u043a\u0438<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c Repository \u2014 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<pre><code class=\"typescript\">\/\/ data\/repository\/AuthRepositoryImpl.ts export class AuthRepositoryImpl extends AuthRepository {   private readonly _tokensData = new MutableStateFlow&lt;TokensData | null&gt;(     localStorage.getItem(\"refreshToken\")       ? {           accessToken: null,           refreshToken: localStorage.getItem(\"refreshToken\"),         }       : null,   )    public tokensData = this._tokensData.asStateFlow()    setTokens(tokens: TokensData): void {     if (tokens.refreshToken) {       localStorage.setItem(\"refreshToken\", tokens.refreshToken)     }     this._tokensData.set(tokens)   }    \/\/ \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b... } <\/code><\/pre>\n<blockquote>\n<p>\u0412\u0430\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c \u0447\u0442\u043e \u043c\u044b \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c\u0441\u044f \u043e\u0442 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0432 domain \u0441\u043b\u043e\u0435. \u0414\u0430\u043b\u0435\u0435 DI \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043a\u0430\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u043c\u0435\u043d\u043d\u043e \u043f\u043e \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438 \u0432 domain \u0441\u043b\u043e\u0435. \u0412\u0441\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u0431\u0438\u0437\u043d\u0435\u0441 \u0441\u043b\u043e\u044f \u043a\u0430\u043a \u0447\u0435\u0440\u043d\u044b\u0439 \u044f\u0449\u0438\u043a. \u0411\u0438\u0437\u043d\u0435\u0441 \u0441\u043b\u043e\u044e \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u0437\u043d\u0430\u0442\u044c \u0447\u0442\u043e \u0443\u043c\u0435\u0435\u0442 \u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0430 \u043a\u0430\u043a, \u0443\u0436\u0435 \u043d\u0435 \u0432\u0430\u0436\u043d\u043e, \u0445\u043e\u0442\u044c \u0438\u0437 \u043b\u043e\u043a\u043b\u0430\u044c\u043d\u043e\u0439 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0435 \u0431\u0435\u0440\u0443\u0442\u0441\u044f, \u0445\u043e\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f.<\/p>\n<\/blockquote>\n<p><strong>\u041b\u0435\u0433\u043a\u0430\u044f \u043f\u043e\u0434\u043c\u0435\u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0439<\/strong>. \u0417\u0430\u0445\u043e\u0442\u0435\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c WASM Postgres \u0432\u043c\u0435\u0441\u0442\u043e localStorage?<\/p>\n<pre><code class=\"typescript\">\/\/ data\/repository\/PostgresAuthRepository.ts export class PostgresAuthRepository extends AuthRepository {   constructor(     @Inject(PostgresWasm) private readonly _postgres: PostgresWasm   ) {}    async setTokens(tokens: TokensData): Promise&lt;void&gt; {     await this._postgres.query(       'INSERT INTO tokens (access_token, refresh_token) VALUES ($1, $2)',       [tokens.accessToken, tokens.refreshToken]     )     this._tokensData.set(tokens)   } } <\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u0448\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0441 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 postgres, \u043c\u0435\u043d\u044f\u0435\u0448\u044c \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u0447\u043a\u0443 \u0432 DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435 \u2014 \u0438 \u0433\u043e\u0442\u043e\u0432\u043e! Domain \u0441\u043b\u043e\u0439 \u0434\u0430\u0436\u0435 \u043d\u0435 \u0437\u0430\u043c\u0435\u0442\u0438\u0442 \u0440\u0430\u0437\u043d\u0438\u0446\u044b.<\/p>\n<h4>3. Presentation \u0441\u043b\u043e\u0439 \u2014 MVVM + MVI \u043f\u0430\u0442\u0442\u0435\u0440\u043d<\/h4>\n<p>\u0422\u0443\u0442 \u044f \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0443\u0432\u043b\u0435\u043a\u0441\u044f \u0438 \u0432\u0437\u044f\u043b \u043d\u0435\u0439\u043c\u0438\u043d\u0433 \u0438\u0437 kotlin \ud83d\ude42<\/p>\n<p><strong>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 Flow \u2014 \u0447\u0442\u043e \u044d\u0442\u043e \u0438 \u0437\u0430\u0447\u0435\u043c?<\/strong><\/p>\n<p>Flow (\u043e\u0442 \u0430\u043d\u0433\u043b. &#171;\u041f\u043e\u0442\u043e\u043a&#187;) \u2014 \u044d\u0442\u043e \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u043f\u043e\u0442\u043e\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445. \u0422\u0443\u0442 \u043c\u044b \u043f\u043e \u0441\u0443\u0442\u0438 \u0434\u0435\u043b\u0430\u0435\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e observers.<\/p>\n<blockquote>\n<p>\u0422\u0443\u0442 \u0434\u043b\u044f \u0441\u0435\u0431\u044f \u0441\u0434\u0435\u043b\u0430\u043b \u043f\u0440\u043e\u0441\u0442\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e. \u041d\u0435 \u043d\u0443\u0436\u043d\u043e \u044d\u0442\u043e \u0432\u043e\u0441\u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u043a\u0430\u043a \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e. \u0414\u0443\u043c\u0430\u044e \u0442\u0443\u0442 \u0434\u0430\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0432\u0437\u044f\u0442\u044c \u043d\u0430\u0442\u0438\u0432\u043d\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e zustand \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u043e \u044f\u0432\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u0434\u0430 \u0438 \u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u043e\u0442 \u0435\u0449\u0435 \u043e\u0434\u043d\u043e\u0439 \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438.<\/p>\n<\/blockquote>\n<pre><code class=\"typescript\">\/\/ core\/lib\/flow\/Flow.ts export type Listener&lt;T&gt; = (value: T) =&gt; void  \/\/ \u041f\u0440\u043e\u0441\u0442\u043e\u0439 \u043f\u043e\u0442\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f export class Flow&lt;T&gt; {   protected listeners: Map&lt;Listener&lt;T&gt;, () =&gt; void&gt; = new Map()    subscribe(listener: Listener&lt;T&gt;): () =&gt; void {     this.listeners.set(listener, () =&gt; {       this.listeners.delete(listener)     })      return () =&gt; {       this.listeners.delete(listener)     }   } }  \/\/ \u041f\u043e\u0442\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \/\/ \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0438 \u043e\u0442 Flow \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0432\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \/\/ \u041d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u044f\u0446\u0438\u0438 \u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e export class MutableFlow&lt;T&gt; extends Flow&lt;T&gt; {   constructor() {     super()   }    emit(value: T): void {     this.listeners.forEach((_, listener) =&gt; listener(value))   }    asFlow(): Flow&lt;T&gt; {     return this   } }  \/\/ \u041f\u043e\u0442\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043a\u0435\u0448\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u044d\u043c\u0438\u0442\u0438\u0442 \u0435\u0433\u043e \u043f\u0440\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0435 export class StateFlow&lt;T&gt; extends Flow&lt;T&gt; {   protected lastValue: T    constructor(initialValue: T) {     super()     this.lastValue = initialValue   }    get value() {     return this.lastValue   }    override subscribe(listener: Listener&lt;T&gt;): () =&gt; void {     this.listeners.set(listener, () =&gt; {       this.listeners.delete(listener)     })      listener(this.lastValue) \/\/ Immediately emit current value      return () =&gt; {       this.listeners.delete(listener)     }   }    asFlow(): Flow&lt;T&gt; {     return this   } }  \/\/ \u041f\u043e\u0442\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043a\u0435\u0448\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u044d\u043c\u0438\u0442\u0438\u0442 \u0435\u0433\u043e \u043f\u0440\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0435 \/\/ \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0438 \u043e\u0442 StateFlow \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0432\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \/\/ \u041d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u044f\u0446\u0438\u0438 \u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e export class MutableStateFlow&lt;T&gt; extends StateFlow&lt;T&gt; {   constructor(initialValue: T) {     super(initialValue)   }    get value() {     return this.lastValue   }    update(value: Partial&lt;T&gt;) {     if (this.lastValue !== value) {       this.lastValue = { ...this.lastValue, ...value }       this.listeners.forEach((_, listener) =&gt; listener(this.lastValue))     }   }    set(value: T): void {     if (this.lastValue !== value) {       this.lastValue = value       this.listeners.forEach((_, listener) =&gt; listener(value))     }   }    asStateFlow(): StateFlow&lt;T&gt; {     return this \/\/ Upcast to immutable version   } } <\/code><\/pre>\n<p><strong>\u0420\u0430\u0437\u043d\u0438\u0446\u0430 \u043c\u0435\u0436\u0434\u0443 \u0442\u0438\u043f\u0430\u043c\u0438 Flow:<\/strong><\/p>\n<ul>\n<li>\n<p><strong>Flow<\/strong> \u2014 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u0441\u043e\u0431\u044b\u0442\u0438\u0439 (\u043a\u043b\u0438\u043a\u0438, HTTP \u043e\u0442\u0432\u0435\u0442\u044b)<\/p>\n<\/li>\n<li>\n<p><strong>StateFlow<\/strong> \u2014 \u043f\u043e\u0442\u043e\u043a \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f (\u0432\u0441\u0435\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435)<\/p>\n<\/li>\n<li>\n<p><strong>MutableFlow<\/strong> \u2014 \u043c\u043e\u0436\u0435\u0448\u044c \u044d\u043c\u0438\u0442\u0438\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><strong>MutableStateFlow<\/strong> \u2014 \u043c\u043e\u0436\u0435\u0448\u044c \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d MVVM &#8212; Model View ViewModel<\/p>\n<p><strong>ViewModel \u2014 \u0445\u0440\u0430\u043d\u0438\u0442\u0435\u043b\u044c \u043b\u043e\u0433\u0438\u043a\u0438 \u0441 \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u044f\u0446\u0438\u0435\u0439<\/strong><\/p>\n<p>\u041a\u043b\u044e\u0447\u0435\u0432\u0430\u044f \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u2014 <strong>\u0441\u0442\u0440\u043e\u0433\u0430\u044f \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u044f\u0446\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f<\/strong>. ViewModel \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0438\u0442\u0430\u0442\u044c:<\/p>\n<pre><code class=\"typescript\">\/\/ presentation\/view_model\/LoginViewModel.ts export class LoginViewModel   implements ViewModel&lt;LoginPageState, LoginPageUiEventType&gt; {   constructor(     @Inject(LoginUseCase) private readonly _loginUseCase: LoginUseCase,   ) {}    \/\/ \u041f\u0420\u0418\u0412\u0410\u0422\u041d\u042b\u0419 MutableStateFlow \u2014 \u0442\u043e\u043b\u044c\u043a\u043e ViewModel \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c   private readonly _state = new MutableStateFlow&lt;LoginPageState&gt;({     isLoading: false,     email: '',     emailError: null,   })      \/\/ \u041f\u0423\u0411\u041b\u0418\u0427\u041d\u042b\u0419 StateFlow \u2014 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043c\u043e\u0436\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0438\u0442\u0430\u0442\u044c   public readonly state = this._state.asStateFlow()    \/\/ MVI \u043f\u0430\u0442\u0442\u0435\u0440\u043d: \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0434\u043b\u044f UI (\u0442\u043e\u0441\u0442\u044b, \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f, \u0430\u043b\u0435\u0440\u0442\u044b)   private readonly _uiEvent = new MutableFlow&lt;LoginPageUiEventType&gt;()   public readonly uiEvent = this._uiEvent.asFlow()    \/\/ \u041c\u0435\u0442\u043e\u0434\u044b ViewModel \u041d\u0415 \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0440\u0435\u043d\u0434\u0435\u0440\u0435!   public login = async (data: LoginFormSchemaType) =&gt; {     try {       this._state.update({ isLoading: true, emailError: null })              await this._loginUseCase.execute({         email: data.email,         password: data.password,       })              \/\/ \u0423\u0441\u043f\u0435\u0445 \u2014 \u044d\u043c\u0438\u0442\u0438\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e. UI \u0443\u0436\u0435 \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u0435\u0442 \u043d\u0430 \u044d\u0442\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0441\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f       this._uiEvent.emit(LoginPageUiEvent.Succsess())     } catch (error) {       \/\/ \u041e\u0448\u0438\u0431\u043a\u0430 \u2014 \u044d\u043c\u0438\u0442\u0438\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043f\u043e\u043a\u0430\u0437\u0430 \u0442\u043e\u0441\u0442\u0430       this._uiEvent.emit(LoginPageUiEvent.ShowToast(error.message, 'error'))       this._state.update({ isLoading: false })     }   }    public validateEmail = (email: string) =&gt; {     this._state.update({ email })          const isValid = email.includes('@') &amp;&amp; email.includes('.')     this._state.update({        emailError: isValid ? null : '\u041d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 email'      })          return isValid   }    \/\/ \u0411\u043e\u043b\u044c\u0448\u0435 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 useCallback\/useMemo! \ud83c\udf89 } <\/code><\/pre>\n<p><strong>MVI (Model-View-Intent) \u043f\u0430\u0442\u0442\u0435\u0440\u043d \u0447\u0435\u0440\u0435\u0437 uiEvent<\/strong><\/p>\n<p>\u0412\u0430\u0436\u043d\u0430\u044f \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f: \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0435\u043c <strong>\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435<\/strong> \u0438 <strong>\u0441\u043e\u0431\u044b\u0442\u0438\u044f<\/strong>:<\/p>\n<ul>\n<li>\n<p><strong>\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435<\/strong> (<code>state<\/code>) \u2014 \u0442\u043e, \u0447\u0442\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f (loading, \u0434\u0430\u043d\u043d\u044b\u0435, \u043e\u0448\u0438\u0431\u043a\u0438 \u0444\u043e\u0440\u043c)<\/p>\n<\/li>\n<li>\n<p><strong>\u0421\u043e\u0431\u044b\u0442\u0438\u044f<\/strong> (<code>uiEvent<\/code>) \u2014 \u0442\u043e, \u043d\u0430 \u0447\u0442\u043e UI \u0434\u043e\u043b\u0436\u0435\u043d \u043e\u0442\u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0434\u0438\u043d \u0440\u0430\u0437 (\u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f, \u0442\u043e\u0441\u0442\u044b, \u0430\u043b\u0435\u0440\u0442\u044b) (\u0442\u043e\u0435\u0441\u0442\u044c \u044d\u0442\u043e \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u044b \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043a\u0430\u043a \u043b\u0438\u0431\u043e \u043c\u043e\u0436\u0435\u0442 \u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c UI)<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"typescript\">\/\/ \u0422\u0438\u043f\u044b \u0441\u043e\u0431\u044b\u0442\u0438\u0439 const LoginPageUiEvent = {   NavigateToMain: () =&gt; ({ type: 'navigate_to_main' as const }),   ShowToast: (message: string, type: 'success' | 'error') =&gt; ({      type: 'show_toast' as const,      payload: { message, type }    }),   OpenEmailConfirmation: () =&gt; ({ type: 'open_email_confirmation' as const }), }  type LoginPageUiEventType =    | ReturnType&lt;typeof LoginPageUiEvent.NavigateToMain&gt;   | ReturnType&lt;typeof LoginPageUiEvent.ShowToast&gt;   | ReturnType&lt;typeof LoginPageUiEvent.OpenEmailConfirmation&gt; <\/code><\/pre>\n<p><strong>\u0417\u0430\u0447\u0435\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0442\u044c?<\/strong><\/p>\n<ul>\n<li>\n<p>\u0422\u043e\u0441\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f <strong>\u043e\u0434\u0438\u043d \u0440\u0430\u0437<\/strong>, \u0430 \u043d\u0435 \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0440\u0435\u0440\u0435\u043d\u0434\u0435\u0440\u0435<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u0434\u043e\u043b\u0436\u043d\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0442\u0438 <strong>\u043e\u0434\u043d\u043e\u043a\u0440\u0430\u0442\u043d\u043e<\/strong> \u043f\u0440\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u043c \u043b\u043e\u0433\u0438\u043d\u0435<\/p>\n<\/li>\n<li>\n<p>\u0410\u043b\u0435\u0440\u0442 \u0434\u043e\u043b\u0436\u0435\u043d <strong>\u0432\u0441\u043f\u043b\u044b\u0442\u044c<\/strong> \u043e\u0434\u0438\u043d \u0440\u0430\u0437, \u0430 \u043d\u0435 \u0432\u0438\u0441\u0435\u0442\u044c \u0432 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438<\/p>\n<\/li>\n<\/ul>\n<p><strong>React \u0445\u0443\u043a\u0438 \u0434\u043b\u044f \u0441\u0432\u044f\u0437\u0438 \u0441 ViewModel<\/strong><br \/> ViewModel \u0434\u043e\u043b\u0436\u0435\u043d \u043a\u0430\u043a\u0442\u043e \u0436\u0438\u0442\u044c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0445\u0443\u043a \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u043c \u0446\u0438\u043a\u043b\u043e\u043c \u0432\u044c\u044e \u043c\u043e\u0434\u0435\u043b\u0438.<\/p>\n<pre><code class=\"typescript\">\/\/ core\/lib\/mvvm\/react\/hooks\/useViewModel.ts export function useViewModel&lt;T extends ViewModel&gt;(   ViewModelClass: Constructor&lt;T&gt; ): T {   \/\/ ViewModel \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0438 \u0436\u0438\u0432\u0451\u0442 \u043f\u043e\u043a\u0430 \u0436\u0438\u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442   const viewModel = useMemo(() =&gt; DIContainer.createInstance(ViewModelClass), [])      useEffect(() =&gt; {     viewModel.init?.()     return () =&gt; viewModel.destroy?.()   }, [])      return viewModel }  \u041f\u0440\u043e\u0441\u0442\u044b\u0435 \u0445\u0443\u043a\u0438 \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438 \u044d\u0432\u0435\u043d\u0442\u044b.  \/\/ core\/lib\/mvvm\/react\/hooks\/useStateFlow.ts export function useStateFlow&lt;T&gt;(stateFlow: StateFlow&lt;T&gt;): T {   const [state, setState] = useState(stateFlow.value)      useEffect(() =&gt; {     \/\/ \u041f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f     const unsubscribe = stateFlow.subscribe(setState)     return unsubscribe   }, [stateFlow])      return state }  \/\/ core\/lib\/mvvm\/react\/hooks\/useFlow.ts export function useFlow&lt;T&gt;(   flow: Flow&lt;T&gt;,    handler: (value: T) =&gt; void ): void {   useEffect(() =&gt; {     \/\/ \u041f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f (\u043d\u043e \u043d\u0435 \u043d\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435!)     const unsubscribe = flow.subscribe(handler)     return unsubscribe   }, [flow, handler]) } <\/code><\/pre>\n<p><strong>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u2014 \u0442\u0443\u043f\u043e\u0439 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u0435\u043b\u044c \u0431\u0435\u0437 \u043b\u043e\u0433\u0438\u043a\u0438<\/strong><\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u0430\u043c\u043e\u0435 \u0433\u043b\u0430\u0432\u043d\u043e\u0435: \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f <strong>\u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u0435\u043b\u0435\u043c<\/strong>. \u041d\u0438\u043a\u0430\u043a\u043e\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438, \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u043f\u043e\u0431\u043e\u0447\u043d\u044b\u0445 \u044d\u0444\u0444\u0435\u043a\u0442\u043e\u0432:<\/p>\n<pre><code>\/\/ view\/LoginPage.tsx export const LoginPage = () =&gt; {   const viewModel = useViewModel(LoginViewModel)   const state = useStateFlow(viewModel.state)    \/\/ \u041f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f UI (\u043d\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435!)   useFlow(viewModel.uiEvent, (event) =&gt; {     switch (event.type) {       case 'navigate_to_main':         navigate('\/dashboard')         break       case 'show_toast':         toast[event.payload.type](event.payload.message)         break       case 'open_email_confirmation':         openModal('email-confirmation')         break     }   })    return (     &lt;LoginForm        \/\/ \u0422\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f       isLoading={state.isLoading}       email={state.email}       emailError={state.emailError}              \/\/ \u0422\u043e\u043b\u044c\u043a\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 ViewModel (\u043d\u0438\u043a\u0430\u043a\u0438\u0445 useCallback!)       onSubmit={viewModel.login}       onEmailChange={viewModel.validateEmail}       onForgotPassword={viewModel.openForgotPassword}     \/&gt;   ) } <\/code><\/pre>\n<blockquote>\n<p>\u0422\u0430\u043a \u0436\u0435 \u043c\u043e\u0436\u0435\u043c \u043d\u0435 \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043e useCallback \u0438\u043b\u0438 useMemo \u0442\u0430\u043a \u043a\u0430\u043a \u0432\u044c\u044e \u043c\u043e\u0434\u0435\u043b\u044c \u0436\u0438\u0432\u0435\u0442 \u0432\u0441\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u043a\u0430 \u043c\u0430\u0443\u043d\u0447\u0435\u043d \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442. \u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0432\u0435\u043d\u043d\u043e \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0435\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f \u043d\u0435 \u0431\u0443\u0434\u0443\u0442.<\/p>\n<\/blockquote>\n<p><strong>\u041f\u0440\u0438\u043d\u0446\u0438\u043f\u044b \u0442\u0443\u043f\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430:<\/strong><\/p>\n<ul>\n<li>\n<p>\u2705 <strong>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442<\/strong> \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438\u0437 ViewModel<\/p>\n<\/li>\n<li>\n<p>\u2705 <strong>\u0420\u0435\u0430\u0433\u0438\u0440\u0443\u0435\u0442<\/strong> \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438\u0437 uiEvent<\/p>\n<\/li>\n<li>\n<p>\u2705 <strong>\u041f\u0435\u0440\u0435\u0434\u0430\u0451\u0442<\/strong> \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0432 ViewModel<\/p>\n<\/li>\n<li>\n<p>\u274c <strong>\u041d\u0415 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442<\/strong> \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0443<\/p>\n<\/li>\n<li>\n<p>\u274c <strong>\u041d\u0415 \u0434\u0435\u043b\u0430\u0435\u0442<\/strong> HTTP \u0437\u0430\u043f\u0440\u043e\u0441\u044b<\/p>\n<\/li>\n<li>\n<p>\u274c <strong>\u041d\u0415 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442<\/strong> \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u041f\u0440\u043e\u0444\u0438\u0442\u044b \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0435:<\/strong><\/p>\n<ul>\n<li>\n<p><strong>\u041d\u0438\u043a\u0430\u043a\u0438\u0445 useCallback\/useMemo<\/strong> \u2014 \u043c\u0435\u0442\u043e\u0434\u044b ViewModel \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u044b \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435<\/p>\n<\/li>\n<li>\n<p><strong>\u041f\u0440\u043e\u0441\u0442\u043e\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/strong> \u2014 ViewModel \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0431\u0435\u0437 UI, \u0430 UI \u2014 \u0431\u0435\u0437 \u043b\u043e\u0433\u0438\u043a\u0438<\/p>\n<\/li>\n<li>\n<p><strong>MVI \u043f\u0430\u0442\u0442\u0435\u0440\u043d<\/strong> \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u043e\u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0439 \u043f\u043e\u0442\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438 \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u044b<\/p>\n<\/li>\n<\/ul>\n<h3>Dependency Injection \u2014 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0439 \u0441\u0442\u0440\u0430\u0436\u043d\u0438\u043a, \u043a\u0430\u043a \u0435\u0433\u043e \u043b\u044e\u0431\u044f\u0442 \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c<\/h3>\n<p>DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u2014 \u044d\u0442\u043e \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c\u0438 \u043c\u0435\u0436\u0434\u0443 \u0441\u043b\u043e\u044f\u043c\u0438 \u0438 \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438. \u041e\u043d \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0438\u0447\u0438, \u0432\u043d\u0435\u0434\u0440\u044f\u0442\u044c \u043c\u043e\u043a\u0438 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u043b\u0435\u043d\u0438\u0432\u043e\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u0439 \u0446\u0438\u043a\u043b \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439.<\/p>\n<h4>Namespaces \u2014 \u0438\u0437\u043e\u043b\u044f\u0446\u0438\u044f \u0441\u043b\u043e\u0451\u0432<\/h4>\n<p>\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0444\u0438\u0447\u0430 \u0432 \u043c\u043e\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 DI \u2014 \u0438\u0437\u043e\u043b\u044f\u0446\u0438\u044f \u0447\u0435\u0440\u0435\u0437 namespaces. \u041a\u0430\u0436\u0434\u0430\u044f \u0444\u0438\u0447\u0430 \u0436\u0438\u0432\u0451\u0442 \u0432 \u0441\u0432\u043e\u0451\u043c \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0435 \u0438\u043c\u0451\u043d \u0438 \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0435\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043d\u043d\u044b\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439:<\/p>\n<pre><code class=\"typescript\">\/\/ features\/auth\/di\/AuthModule.di.ts DiModule.register({   nameSpace: \"auth\",   nameSpaceDependencies: [\"core\"], \/\/ \u041c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e core   builder: (builder) =&gt; {     builder.register({       token: AuthRepository,       implementation: AuthRepositoryImpl,       isSingleton: true, \/\/ \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0447\u0442\u043e \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0438 \u0431\u0443\u0434\u0435\u0442 \u0432\u0435\u0437\u0434\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0434\u0438\u043d instance       lazy: true, \/\/ \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0447\u0442\u043e instance \u0441\u0437\u0434\u0430\u0441\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 \u043a \u043d\u0435\u043c\u0443, \u0430 \u043d\u0435 \u043f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435.     })      builder.register({       token: LoginUseCase,       implementation: LoginUseCase,     })   }, }) <\/code><\/pre>\n<p>\u0423 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 DiModule \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u043e\u0436\u0435\u043c \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c DI \u043c\u043e\u0434\u0443\u043b\u0438 \u0432 registry. \u0412 \u043c\u0435\u0442\u043e\u0434\u0435 builder \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u0441\u0430\u043c builder \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0448\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0432 registry.<\/p>\n<p><strong>Token<\/strong> &#8212; \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044f \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u043e\u0436\u0435\u043c \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e.<br \/> <strong>implementation<\/strong> &#8212; \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043a\u043e\u0442\u043e\u0440\u0443\u044e DI \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u0443\u0434\u0435\u0442 \u0438\u043d\u0436\u0435\u043a\u0442\u0438\u0442\u044c.<br \/> <strong>isSingleton<\/strong> &#8212; \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 DI \u0447\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u0434\u0438\u043d \u0440\u0430\u0437<br \/> <strong>lazy<\/strong> &#8212; \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 DI \u0447\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043d\u0443\u0436\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u043a \u043d\u0435\u043c\u0443 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u043f\u0430\u0440\u0435 \u0441 isSingleton: true)<\/p>\n<pre><code class=\"typescript\">\/\/ features\/posts\/di\/PostsModule.di.ts DiModule.register({   nameSpace: \"posts\",   nameSpaceDependencies: [\"core\", \"auth\"], \/\/ \u041c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c core \u0438 auth   builder: (builder) =&gt; {     builder.register({       token: PostsRepository,       implementation: PostsRepositoryImpl,       isSingleton: true,     })   }, }) <\/code><\/pre>\n<p><strong>\u0427\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442, \u0435\u0441\u043b\u0438 \u0434\u0436\u0443\u043d \u043f\u043e\u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u043d\u0430\u0440\u0443\u0448\u0438\u0442\u044c \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443?<\/strong><\/p>\n<pre><code class=\"typescript\">\/\/ features\/auth\/domain\/use_case\/LoginUseCase.ts export class LoginUseCase {   constructor(     @Inject(AuthRepository) private readonly _authRepository: AuthRepository,     @Inject(PostsRepository) private readonly _postsRepository: PostsRepository, \/\/ \ud83d\udca5 \u041e\u0428\u0418\u0411\u041a\u0410!   ) {} }  \/\/ Runtime error: \/\/ Namespace conflict: Cannot inject 'PostsRepository' from namespace 'posts'  \/\/ into requesting namespace 'auth'. Add 'posts' to nameSpaceDependencies. <\/code><\/pre>\n<p>DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0432\u044b\u0431\u0440\u043e\u0441\u0438\u0442 \u043e\u0448\u0438\u0431\u043a\u0443! \u0422\u0435\u043f\u0435\u0440\u044c \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u0430 \u043e\u0442 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0445 \u043d\u0430\u0440\u0443\u0448\u0435\u043d\u0438\u0439.<\/p>\n<h4>Lazy loading \u0438 performance<\/h4>\n<pre><code class=\"typescript\">builder.register({   token: HeavyAnalyticsService,   implementation: HeavyAnalyticsService,   lazy: true, \/\/ \u041d\u0435 \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0434\u043e \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f   isSingleton: true, \/\/ \u041d\u043e \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 }) <\/code><\/pre>\n<h4>\u0411\u0443\u0434\u0443\u0449\u0435\u0435: Code splitting \u0447\u0435\u0440\u0435\u0437 DI<\/h4>\n<p>\u0412 \u043f\u043b\u0430\u043d\u0430\u0445 \u2014 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 code splitting \u0447\u0435\u0440\u0435\u0437 DI \u043c\u043e\u0434\u0443\u043b\u0438:<\/p>\n<pre><code class=\"typescript\">\/\/ \u041c\u043e\u0434\u0443\u043b\u0438 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c\u0441\u044f \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 const authModule = () =&gt; import('.\/features\/auth\/di\/AuthModule.di.ts') const postsModule = () =&gt; import('.\/features\/posts\/di\/PostsModule.di.ts')  \/\/ DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0441\u0430\u043c \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0447\u0442\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c <\/code><\/pre>\n<h4>\u0418\u043d\u044a\u0435\u043a\u0446\u0438\u044f \u0447\u0435\u0440\u0435\u0437 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b<\/h4>\n<pre><code class=\"typescript\">export class LoginUseCase {   constructor(     @Inject(AuthNetwork) private readonly _authNetwork: AuthNetwork,     @Inject(AuthRepository) private readonly _authRepository: AuthRepository,   ) {} } <\/code><\/pre>\n<p>\u0412 \u0441\u0447\u0435\u0442 \u0442\u043e\u0433\u043e \u0447\u0442\u043e \u0432 js \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0442\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u0445 \u0442\u0438\u043f\u0430\u0445, \u043d\u0430\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u044e. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 reflect-metadata \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0441\u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435.<\/p>\n<p>\u0417\u0430 \u0441\u0447\u0435\u0442 \u043d\u0435\u0437\u0430\u043c\u0443\u0434\u0440\u0435\u043d\u043e\u0433\u043e \u0441\u0430\u043c\u043e\u043f\u0438\u0441\u043d\u043e\u0433\u043e \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u0430 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0432\u0441\u044e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0434\u043b\u044f DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430, \u0447\u0442\u043e\u0431\u044b \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435 \u043c\u044b \u043f\u043e\u043d\u0438\u043c\u0430\u043b\u0438 \u043a\u0430\u043a \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0430\u043d\u0441.<\/p>\n<pre><code class=\"typescript\">import \"reflect-metadata\"  import { type Token } from \".\/DIContainer\"  export const INJECT_METADATA_SYMBOL = Symbol(\"inject\")  \/\/ \u0414\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440 \u0434\u043b\u044f \u0438\u043d\u044a\u0435\u043a\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0430 export function Inject(token: Token): ParameterDecorator {   return (     \/\/ eslint-disable-next-line @typescript-eslint\/no-explicit-any     target: any,     _: string | symbol | undefined,     parameterIndex: number,   ) =&gt; {     const existingTokens: Token[] =       Reflect.getMetadata(INJECT_METADATA_SYMBOL, target) || []     existingTokens[parameterIndex] = token      Reflect.defineMetadata(INJECT_METADATA_SYMBOL, existingTokens, target)   } }  <\/code><\/pre>\n<blockquote>\n<p>\u041f\u043e\u043a\u0430 \u043f\u0438\u0448\u0443 \u0441\u0442\u0430\u0442\u044c\u044e \u0437\u0430\u0434\u0443\u043c\u0430\u043b\u0441\u044f \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043b\u044f \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432 \u0432\u0438\u0434\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435 \u0441\u0430\u043c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441, \u0432\u0435\u0434\u044c \u043f\u0440\u0438 \u0442\u0430\u043a\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0443 \u043d\u0430\u0441 \u0442\u043e\u043a\u0435\u043d\u043e\u043c \u044f\u0432\u043d\u043b\u044f\u0435\u0442\u0441\u044f js \u043a\u043e\u0434 \u0432\u0441\u0435\u0433\u043e \u043e\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0432 \u0432\u0438\u0434\u0435 \u0441\u0442\u0440\u043e\u043a\u0438, \u0430 \u0441\u0430\u043c\u043e\u043c\u0443 \u043f\u0438\u0441\u0430\u0442\u044c \u0432 \u0432\u0438\u0434\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u0430:<\/p>\n<\/blockquote>\n<pre><code class=\"typescript\"> @Inject(\"AuthNetwork\") <\/code><\/pre>\n<blockquote>\n<p>\u041d\u043e \u0442\u043e\u0433\u0434\u0430 \u0438 \u0432 di \u043c\u043e\u0434\u0443\u043b\u044f\u0445 \u043f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043d\u0443\u0436\u043d\u043e \u0442\u043e\u0435\u043a\u043d\u044b \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u043c \u0436\u0435 \u043e\u0431\u0440\u0430\u0437\u043e\u043c. \u041c\u0435\u043d\u0435\u0435 \u0443\u0434\u043e\u0431\u043d\u043e, \u043d\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b \u0432 \u0441\u043e\u0432\u0441\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0434\u043b\u044f \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0438 \u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<\/blockquote>\n<p><strong>\u041f\u0440\u043e\u0444\u0438\u0442\u044b \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0435:<\/strong><\/p>\n<ul>\n<li>\n<p><strong>\u0417\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u043d\u0430\u0440\u0443\u0448\u0435\u043d\u0438\u0439<\/strong> \u2014 DI \u043d\u0435 \u0434\u0430\u0441\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438<\/p>\n<\/li>\n<li>\n<p><strong>Lazy loading<\/strong> \u2014 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438<\/p>\n<\/li>\n<li>\n<p><strong>\u0418\u0437\u043e\u043b\u044f\u0446\u0438\u044f<\/strong> \u2014 \u0444\u0438\u0447\u0438 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430<\/p>\n<\/li>\n<li>\n<p><strong>\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u043e\u0441\u0442\u044c<\/strong> \u2014 \u043b\u0435\u0433\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u043c\u043e\u0434\u0443\u043b\u0438<\/p>\n<\/li>\n<\/ul>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u043a\u0440\u0443\u0442\u043e \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438?<\/h3>\n<h4>1. \u041c\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u2014 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0431\u0435\u0437 \u0431\u044d\u043a\u0435\u043d\u0434\u0430<\/h4>\n<p>\u042d\u0442\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u0431\u043e\u043b\u044c \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438: \u0431\u044d\u043a\u0435\u043d\u0434 \u0435\u0449\u0451 \u043d\u0435 \u0433\u043e\u0442\u043e\u0432, \u0430 UI \u043d\u0430\u0434\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0443\u0436\u0435 \u0441\u0435\u0433\u043e\u0434\u043d\u044f. \u0421 Clean Architecture \u0442\u044b \u043c\u043e\u0436\u0435\u0448\u044c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u043d\u043e\u043c\u043d\u043e:<\/p>\n<pre><code class=\"typescript\">\/\/ data\/repository\/MockAuthRepository.ts class MockAuthRepository extends AuthRepository {   private _tokens = new MutableStateFlow&lt;TokensData | null&gt;(null)   public tokensData = this._tokens.asStateFlow()    async setTokens(tokens: TokensData): Promise&lt;void&gt; {     \/\/ \u0418\u043c\u0438\u0442\u0438\u0440\u0443\u0435\u043c \u0440\u0435\u0430\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0443 \u0441\u0435\u0442\u0438     await new Promise((resolve) =&gt; setTimeout(resolve, 1500))          \/\/ \u041c\u043e\u0436\u0435\u043c \u044d\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438     \/\/ \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438     if (Math.random() &lt; 0.1) {       throw new Error('Network timeout')     }          this._tokens.set(tokens)   }    async getTokens(): Promise&lt;TokensData | null&gt; {     return this._tokens.value   } }  \/\/ data\/network\/MockAuthNetwork.ts   class MockAuthNetwork extends AuthNetwork {   private users = [     { email: 'admin@test.com', password: 'admin', role: 'admin' },     { email: 'user@test.com', password: 'user', role: 'user' }   ]    async login(body: LoginBody): Promise&lt;LoginResponse&gt; {     await new Promise(resolve =&gt; setTimeout(resolve, 1000))          const user = this.users.find(u =&gt;        u.email === body.email &amp;&amp; u.password === body.password     )          if (!user) {       throw new Error('Invalid credentials')     }          return {       accessToken: `mock-access-${Date.now()}`,       refreshToken: `mock-refresh-${Date.now()}`,       user: {         id: user.email,         email: user.email,         role: user.role       }     }   } } <\/code><\/pre>\n<p><strong>\u0421\u043e\u0437\u0434\u0430\u0451\u043c development \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435:<\/strong><\/p>\n<pre><code class=\"typescript\">\/\/ di\/DevModule.di.ts \/\/ \u044d\u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u043d\u0435 \u0441\u0432\u0435\u0440\u043a\u043e\u0439 \u0441 ENV, \u0430 \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0442\u0438\u043f\u0430 EnvMockImplemetations: boolean if (process.env.NODE_ENV === 'development') {   DiModule.register({     nameSpace: \"auth\",     nameSpaceDependencies: [\"core\"],     builder: (builder) =&gt; {       \/\/ \u041f\u043e\u0434\u043c\u0435\u043d\u044f\u0435\u043c \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0430 \u043c\u043e\u043a\u0438       builder.register({         token: AuthRepository,         implementation: MockAuthRepository,         isSingleton: true,       })              builder.register({         token: AuthNetwork,         implementation: MockAuthNetwork,         isSingleton: true,       })     },   }) } <\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0442\u044b \u043c\u043e\u0436\u0435\u0448\u044c:<\/p>\n<ul>\n<li>\n<p><strong>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438<\/strong> (\u0443\u0441\u043f\u0435\u0445, \u043e\u0448\u0438\u0431\u043a\u0438, \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u044b)<\/p>\n<\/li>\n<li>\n<p><strong>\u0420\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043e\u0444\u0444\u043b\u0430\u0439\u043d<\/strong> \u2014 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443<\/p>\n<\/li>\n<li>\n<p><strong>\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0438\u0447\u0438<\/strong> \u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a\u0443 \u0431\u0435\u0437 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0431\u044d\u043a\u0435\u043d\u0434\u0443<\/p>\n<\/li>\n<\/ul>\n<h4>2. \u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u2014 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u043b\u043e\u0439 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e<\/h4>\n<p><strong>\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c Use Case \u0441 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u043c\u0438 \u043c\u043e\u043a\u0430\u043c\u0438:<\/strong><\/p>\n<pre><code class=\"typescript\">\/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u043c\u043e\u043a\u043e\u0432\u044b\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 class MockAuthNetwork extends AuthNetwork {   private shouldFail = false   private responseDelay = 0    setShouldFail(value: boolean) {     this.shouldFail = value   }    setResponseDelay(delay: number) {     this.responseDelay = delay   }    async login(body: LoginBody): Promise&lt;LoginResponse&gt; {     if (this.responseDelay &gt; 0) {       await new Promise(resolve =&gt; setTimeout(resolve, this.responseDelay))     }      if (this.shouldFail) {       throw new Error('Network error')     }      return {       accessToken: 'mock-access-token',       refreshToken: 'mock-refresh-token',       user: {         id: 'mock-user-id',         email: body.email       }     }   } }  class MockAuthRepository extends AuthRepository {   private tokens: TokensData | null = null   private readonly _tokensData = new MutableStateFlow&lt;TokensData | null&gt;(null)   public tokensData = this._tokensData.asStateFlow()    getTokens(): TokensData | null {     return this.tokens   }    setTokens(tokens: TokensData): void {     this.tokens = tokens     this._tokensData.set(tokens)   }    removeTokens(): void {     this.tokens = null     this._tokensData.set(null)   }    \/\/ \u041c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f   getStoredTokens(): TokensData | null {     return this.tokens   } }  class MockUserStorage {   private users = new Map&lt;string, any&gt;()    async getUser(email: string) {     return this.users.get(email) || null   }    async saveUser(email: string, data: any) {     this.users.set(email, data)   }    \/\/ \u041c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f   clearUsers() {     this.users.clear()   }    getUserCount() {     return this.users.size   } }  \/\/ \u0422\u0435\u0441\u0442\u044b describe('LoginUseCase', () =&gt; {   let useCase: LoginUseCase   let mockAuthNetwork: MockAuthNetwork   let mockAuthRepository: MockAuthRepository   let mockUserStorage: MockUserStorage    beforeEach(() =&gt; {     \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u043c\u043e\u043a\u043e\u0432\u044b\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b     mockAuthNetwork = new MockAuthNetwork()     mockAuthRepository = new MockAuthRepository()     mockUserStorage = new MockUserStorage()      \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c Use Case \u0441 \u043c\u043e\u043a\u0430\u043c\u0438     useCase = new LoginUseCase(mockAuthNetwork, mockAuthRepository, mockUserStorage)   })    it('should use cached user if available', async () =&gt; {     \/\/ Arrange     const cachedUser = {        tokens: { accessToken: 'cached', refreshToken: 'cached' },       isValid: () =&gt; true      }     await mockUserStorage.saveUser('test@test.com', cachedUser)      \/\/ Act     await useCase.execute({ email: 'test@test.com', password: '123' })      \/\/ Assert     expect(mockAuthRepository.getStoredTokens()).toEqual(cachedUser.tokens)     expect(mockUserStorage.getUserCount()).toBe(1)   })    it('should make API call if cache is empty', async () =&gt; {     \/\/ Arrange     mockAuthNetwork.setResponseDelay(100) \/\/ \u0418\u043c\u0438\u0442\u0438\u0440\u0443\u0435\u043c \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0443 \u0441\u0435\u0442\u0438      \/\/ Act     await useCase.execute({ email: 'test@test.com', password: '123' })      \/\/ Assert     expect(mockAuthRepository.getStoredTokens()).toEqual({       accessToken: 'mock-access-token',       refreshToken: 'mock-refresh-token'     })     expect(mockUserStorage.getUserCount()).toBe(1)   })    it('should handle network errors', async () =&gt; {     \/\/ Arrange     mockAuthNetwork.setShouldFail(true)      \/\/ Act &amp; Assert     await expect(       useCase.execute({ email: 'test@test.com', password: '123' })     ).rejects.toThrow('Network error')   }) }) <\/code><\/pre>\n<p><strong>\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c ViewModel \u0441 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u043c\u0438 \u043c\u043e\u043a\u0430\u043c\u0438:<\/strong><\/p>\n<pre><code class=\"typescript\">class MockLoginUseCase {   private shouldFail = false   private executionTime = 0    setShouldFail(value: boolean) {     this.shouldFail = value   }    setExecutionTime(time: number) {     this.executionTime = time   }    async execute(body: LoginBody): Promise&lt;void&gt; {     if (this.executionTime &gt; 0) {       await new Promise(resolve =&gt; setTimeout(resolve, this.executionTime))     }      if (this.shouldFail) {       throw new Error('Login failed')     }   } }  describe('LoginViewModel', () =&gt; {   let viewModel: LoginViewModel   let mockUseCase: MockLoginUseCase    beforeEach(() =&gt; {     mockUseCase = new MockLoginUseCase()     viewModel = new LoginViewModel(mockUseCase)   })    it('should show loading during login', async () =&gt; {     \/\/ Arrange     mockUseCase.setExecutionTime(100)      \/\/ Act     const loginPromise = viewModel.login({ email: 'test@test.com', password: '123' })      \/\/ Assert - \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0437\u043e\u0432\u0430 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c loading     expect(viewModel.state.value.isLoading).toBe(true)          \/\/ \u0416\u0434\u0451\u043c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f     await loginPromise          \/\/ \u041f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f loading \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u0441\u0447\u0435\u0437\u043d\u0443\u0442\u044c     expect(viewModel.state.value.isLoading).toBe(false)   })    it('should emit success event on successful login', async () =&gt; {     \/\/ Arrange     const events: any[] = []     viewModel.uiEvent.subscribe(event =&gt; events.push(event))      \/\/ Act     await viewModel.login({ email: 'test@test.com', password: '123' })      \/\/ Assert     expect(events).toContainEqual({ type: 'success' })   })    it('should emit error event on failed login', async () =&gt; {     \/\/ Arrange     mockUseCase.setShouldFail(true)     const events: any[] = []     viewModel.uiEvent.subscribe(event =&gt; events.push(event))      \/\/ Act     await viewModel.login({ email: 'test@test.com', password: '123' })      \/\/ Assert     expect(events).toContainEqual({        type: 'show_toast',        payload: { message: 'Login failed', type: 'error' }      })   })    it('should validate email correctly', () =&gt; {     \/\/ Act &amp; Assert     expect(viewModel.validateEmail('valid@email.com')).toBe(true)     expect(viewModel.validateEmail('invalid-email')).toBe(false)     expect(viewModel.state.value.emailError).toBe('\u041d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 email')   }) }) <\/code><\/pre>\n<p><strong>\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u0441 DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u043c:<\/strong><\/p>\n<pre><code class=\"typescript\">describe('LoginViewModel with DI', () =&gt; {   beforeEach(() =&gt; {     \/\/ \u041e\u0447\u0438\u0449\u0430\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043f\u0435\u0440\u0435\u0434 \u043a\u0430\u0436\u0434\u044b\u043c \u0442\u0435\u0441\u0442\u043e\u043c     DIContainer.clear()          \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043c\u043e\u043a\u043e\u0432\u044b\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438     DIContainer.register({       token: AuthNetwork,       implementation: MockAuthNetwork,       isSingleton: true,     })          DIContainer.register({       token: AuthRepository,       implementation: MockAuthRepository,       isSingleton: true,     })          DIContainer.register({       token: LoginUseCase,       implementation: LoginUseCase,       lazy: true,     })   })    it('should work with DI container', async () =&gt; {     \/\/ Arrange     const viewModel = DIContainer.createInstance(LoginViewModel)     const events: any[] = []     viewModel.uiEvent.subscribe(event =&gt; events.push(event))      \/\/ Act     await viewModel.login({ email: 'test@test.com', password: '123' })      \/\/ Assert     expect(events).toContainEqual({ type: 'success' })   }) }) <\/code><\/pre>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440\u044b \u0442\u0435\u0441\u0442\u043e\u0432 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043b \u0447\u0435\u0440\u0435\u0437 \u0418\u0418 \u043a\u0430\u043a \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f, \u0432 \u0446\u0435\u043b\u043e\u043c \u0434\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0431\u043e\u043b\u0435\u0435 \u0447\u0435\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u0421 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0435 \u0441\u0442\u0430\u043b \u0432\u044b\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0442\u044c \u0447\u0442\u043e\u0442\u043e, \u0443\u0431\u0438\u0440\u0430\u0442\u044c \u043e\u0442\u0442\u0443\u0434\u0430 \u0432\u0441\u0435 \u043b\u0438\u0448\u043d\u0435\u0435. \u0414\u0443\u043c\u0430\u044e \u0441\u0443\u0442\u044c \u043f\u043e\u043d\u044f\u0442\u043d\u0430.<\/p>\n<h4>3. \u041f\u043e\u0434\u043c\u0435\u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0439 \u2014 \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c \u043d\u0430 \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c<\/h4>\n<p>\u041d\u0430\u0447\u0430\u043b \u0441 localStorage, \u043d\u043e \u043f\u0440\u043e\u0435\u043a\u0442 \u0432\u044b\u0440\u043e\u0441 \u0438 \u043d\u0443\u0436\u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u043c\u043e\u0449\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f?<\/p>\n<pre><code class=\"typescript\">\/\/ \u0411\u044b\u043b\u043e class LocalStorageAuthRepository extends AuthRepository {   setTokens(tokens: TokensData): void {     localStorage.setItem('tokens', JSON.stringify(tokens))   } }  \/\/ \u0421\u0442\u0430\u043b\u043e class IndexedDBAuthRepository extends AuthRepository {   async setTokens(tokens: TokensData): Promise&lt;void&gt; {     const db = await this.openDB()     const transaction = db.transaction(['tokens'], 'readwrite')     await transaction.objectStore('tokens').put(tokens, 'current')   } }  \/\/ \u0415\u0449\u0451 \u043b\u0443\u0447\u0448\u0435 \u2014 WASM Postgres class PostgresAuthRepository extends AuthRepository {   async setTokens(tokens: TokensData): Promise&lt;void&gt; {     await this._postgres.execute(       'INSERT OR REPLACE INTO auth_tokens (id, access_token, refresh_token) VALUES (1, ?, ?)',       [tokens.accessToken, tokens.refreshToken]     )   } } <\/code><\/pre>\n<p>\u041c\u0435\u043d\u044f\u0435\u0448\u044c <strong>\u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u0447\u043a\u0443<\/strong> \u0432 DI \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u2014 \u0432\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439!<\/p>\n<h3>\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u2014 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0432\u044b\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442<\/h3>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 1: \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043c\u0438\u043a\u0440\u043e\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u044b<\/h4>\n<p>\u041f\u0440\u043e\u0435\u043a\u0442 \u0432\u044b\u0440\u043e\u0441, \u043a\u043e\u043c\u0430\u043d\u0434 \u0441\u0442\u0430\u043b\u043e \u0431\u043e\u043b\u044c\u0448\u0435. \u041d\u0443\u0436\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430 \u043c\u0438\u043a\u0440\u043e\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u044b:<\/p>\n<pre><code class=\"typescript\">\/\/ \u041a\u0430\u0436\u0434\u044b\u0439 \u043c\u0438\u043a\u0440\u043e\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442 \u0441\u0432\u043e\u0438 \u043c\u043e\u0434\u0443\u043b\u0438 \/\/ micro-auth\/src\/AuthMicroapp.ts export class AuthMicroapp {   static init() {     \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e auth \u043c\u043e\u0434\u0443\u043b\u0438     import('.\/di\/AuthModule.di.ts')     return {       routes: authRoutes,       diModules: ['auth']     }   } }  \/\/ micro-posts\/src\/PostsMicroapp.ts   export class PostsMicroapp {   static init() {     \/\/ \u0417\u0430\u0432\u0438\u0441\u0438\u043c \u043e\u0442 auth, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0435\u0433\u043e \u0447\u0435\u0440\u0435\u0437 DI     import('.\/di\/PostsModule.di.ts')     return {       routes: postsRoutes,       diModules: ['posts'],       dependencies: ['auth'] \/\/ DI namespaces \u0437\u0430\u0449\u0438\u0442\u044f\u0442 \u043e\u0442 \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439     }   } } <\/code><\/pre>\n<p><strong>\u041f\u0440\u043e\u0444\u0438\u0442<\/strong>: Clean Architecture \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043a\u043e\u0434 \u043d\u0430 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0435 \u0447\u0430\u0441\u0442\u0438. DI namespace&#8217;\u044b \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u044e\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043c\u0435\u0436\u0434\u0443 \u043c\u0438\u043a\u0440\u043e\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430\u043c\u0438.<\/p>\n<blockquote>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u043c\u0438\u043a\u0440\u043e\u0444\u0440\u043e\u043d\u0442\u0430\u043c\u0438 \u0443 \u043c\u0435\u043d\u044f \u043d\u0435\u0442, \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0447\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0435\u0442, \u043a\u043e\u0434 \u0432\u044b\u0448\u0435 \u0440\u0430\u0441\u0446\u0435\u043d\u0438\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u043f\u0441\u0435\u0432\u0434\u043e\u043a\u043e\u0434<\/p>\n<\/blockquote>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 2: \u041c\u0435\u043d\u044f\u0435\u043c React \u043d\u0430 Vue \u2014 \u043d\u0438\u043a\u043e\u0433\u043e \u043d\u0435 \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c<\/h4>\n<p>\u0421\u0430\u043c\u044b\u0439 \u0436\u0438\u0440\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440. \u041d\u0443\u0436\u043d\u043e \u043c\u0438\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 Vue?<\/p>\n<blockquote>\n<p>VUE \u043d\u0435 \u043c\u043e\u0439 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u0442\u0435\u043a, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0433\u0434\u0435\u0442\u043e \u0447\u0442\u043e\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043b\u0443\u0447\u0448\u0435.<\/p>\n<\/blockquote>\n<p><strong>1. \u0421\u043e\u0437\u0434\u0430\u0451\u043c Vue \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e MVVM \u0445\u0443\u043a\u043e\u0432:<\/strong><\/p>\n<pre><code class=\"typescript\">\/\/ core\/lib\/mvvm\/vue\/composables\/useViewModel.ts import { ref, onMounted, onUnmounted } from 'vue' import { DIContainer } from '..\/..\/di'  export function useViewModel&lt;T extends ViewModel&gt;(ViewModelClass: Constructor&lt;T&gt;): T {   const viewModel = DIContainer.createInstance(ViewModelClass)      onMounted(() =&gt; {     viewModel.init?.()   })      onUnmounted(() =&gt; {     viewModel.destroy?.()   })      return viewModel }  \/\/ core\/lib\/mvvm\/vue\/composables\/useStateFlow.ts import { ref, onMounted, onUnmounted } from 'vue'  export function useStateFlow&lt;T&gt;(stateFlow: StateFlow&lt;T&gt;) {   const state = ref(stateFlow.value)      onMounted(() =&gt; {     const unsubscribe = stateFlow.subscribe((newValue) =&gt; {       state.value = newValue     })          onUnmounted(() =&gt; {       unsubscribe()     })   })      return state } <\/code><\/pre>\n<p><strong>2. \u041f\u0435\u0440\u0435\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043d\u0430 Vue:<\/strong><\/p>\n<pre><code>&lt;!-- view\/LoginPage.vue --&gt; &lt;template&gt;   &lt;LoginForm      :isLoading=\"state.isLoading\"     @submit=\"viewModel.login\"     @validate-email=\"viewModel.validateEmail\"   \/&gt; &lt;\/template&gt;  &lt;script setup lang=\"ts\"&gt; import { useViewModel, useStateFlow, useFlow } from '@\/core\/lib\/mvvm\/vue' import { LoginViewModel } from '..\/view_model\/LoginViewModel'  \/\/ \u0422\u0430 \u0436\u0435 ViewModel! \u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439! const viewModel = useViewModel(LoginViewModel) const state = useStateFlow(viewModel.state)  useFlow(viewModel.uiEvent, (event) =&gt; {   if (event.type === \"success\") {     router.push(\"\/dashboard\")   } else if (event.type === \"error\") {     toast.error(event.payload)   } }) &lt;\/script&gt; <\/code><\/pre>\n<p><strong>3. ViewModel \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043d\u0435\u0438\u0437\u043c\u0435\u043d\u043d\u043e\u0439:<\/strong><\/p>\n<pre><code class=\"typescript\">\/\/ \u0422\u043e\u0442 \u0436\u0435 \u0441\u0430\u043c\u044b\u0439 \u0444\u0430\u0439\u043b! \u041d\u0438 \u0441\u0442\u0440\u043e\u0447\u043a\u0438 \u043a\u043e\u0434\u0430 \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u043c! export class LoginViewModel implements ViewModel&lt;LoginPageState, LoginPageUiEventType&gt; {   \/\/ ... \u0432\u0441\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a\u043e\u0439 \u0436\u0435 } <\/code><\/pre>\n<p><strong>4. Use Cases, Repository, Network \u2014 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u0435\u043c:<\/strong><\/p>\n<pre><code class=\"typescript\">\/\/ \u0412\u0441\u0435 \u0441\u043b\u043e\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u044b\u043c\u0438 export class LoginUseCase { \/* \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 *\/ } export class AuthRepositoryImpl { \/* \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 *\/ } export class AuthNetwork { \/* \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 *\/ } <\/code><\/pre>\n<p><strong>\u0427\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c?<\/strong> \u0422\u043e\u043b\u044c\u043a\u043e UI \u0441\u043b\u043e\u0439! \u0412\u0441\u044f \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430, \u0432\u0441\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u0432\u0441\u0451 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u2014 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0442\u043e\u0447\u043d\u043e \u0442\u0430\u043a \u0436\u0435.<\/p>\n<p>\u0412 \u0442\u0430\u043a\u0438\u0445 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044f\u0445 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c framework \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0435 \u043f\u0430\u043f\u043a\u0438. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044f \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u043b \u043f\u0430\u043f\u043a\u0443 <code>core\/lib\/mvvm\/react\/<\/code> \u0434\u043b\u044f React-\u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0445 \u0445\u0443\u043a\u043e\u0432. \u0412 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0435\u0441\u043b\u0438 \u0432\u0434\u0440\u0443\u0433 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0439 framework \u0442\u043e \u043c\u043d\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0435\u0449\u0435 \u043e\u0434\u043d\u0443 \u043f\u0430\u043f\u043a\u0443 \u043f\u043e\u0434 \u0435\u0435 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0443.<\/p>\n<p>\u0418 \u0442\u0443\u0442 \u043c\u044b \u0443\u0436\u0435 \u044f\u0440\u043a\u043e \u0432\u0438\u0434\u0438\u043c \u043f\u043e\u0447\u0435\u043c\u0443 \u0432\u0430\u0436\u043d\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438, \u0432 \u0434\u0430\u043d\u043d\u043e\u0441 \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u043e\u0447\u0435\u043c\u0443 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438 MVVM + MVI \u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0442\u0434\u0435\u043b\u0438\u043b\u0438 Ui \u0431\u0438\u0437\u043d\u0435\u0441 \u0441\u043b\u043e\u044f. \u0412\u0435\u0434\u044c UI \u043e\u0447\u0435\u043d\u044c \u0447\u0430\u0441\u0442\u043e \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f, \u0430 \u0435\u0441\u043b\u0438 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u0441\u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u043e \u043c\u044b \u0443\u0445\u043e\u0434\u0438\u043c \u043e\u0442 \u0440\u0435\u0430\u043a\u0442 \u043d\u0430 \u0432\u044c\u044e, \u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0431\u043e\u043b\u044c\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u043c\u0435\u043d\u044f\u0442\u044c \u0432\u0435\u0437\u0434\u0435 \u0438 \u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0443, \u0432\u0435\u0434\u044c \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u0445\u0443\u043a\u0438 \u0440\u0435\u0430\u043a\u0442\u0430 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c  \u0432\u043e VUE.<\/p>\n<h3>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h3>\n<p>Clean Architecture \u2014 \u044d\u0442\u043e \u043d\u0435 \u0441\u0435\u0440\u0435\u0431\u0440\u044f\u043d\u0430\u044f \u043f\u0443\u043b\u044f. \u042d\u0442\u043e \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0434\u043b\u044f \u0441\u043b\u043e\u0436\u043d\u044b\u0445, \u0434\u043e\u043b\u0433\u043e\u0436\u0438\u0432\u0443\u0449\u0438\u0445 \u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432, \u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u0435\u043d \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0437\u0430\u0434\u0430\u0447. \u0412\u0430\u0436\u043d\u043e \u0443\u043c\u0435\u0442\u044c \u0432\u044b\u0431\u0438\u0440\u0430\u0442\u044c \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443 \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u0440\u0435\u0430\u043b\u0438\u0438, \u043d\u0435 \u0431\u043e\u044f\u0442\u044c\u0441\u044f \u043c\u0438\u043a\u0441\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434\u044b \u0438 \u043d\u0435 \u0433\u043d\u0430\u0442\u044c\u0441\u044f \u0437\u0430 \u043c\u043e\u0434\u043e\u0439. \u0414\u0435\u043b\u0438\u0442\u0435\u0441\u044c \u0441\u0432\u043e\u0438\u043c \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u043c \u043e\u043f\u044b\u0442\u043e\u043c, \u043e\u0431\u0441\u0443\u0436\u0434\u0430\u0439\u0442\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0441 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 \u0438 \u043d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0439\u0442\u0435: \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u2014 \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0435\u043a\u0442 \u0431\u044b\u043b\u043e \u0443\u0434\u043e\u0431\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0438 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0442\u044c \u0442\u0432\u043e\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u0435.<\/p>\n<h4>\u041a\u043e\u0433\u0434\u0430 \u041d\u0415 \u043d\u0443\u0436\u043d\u0430 Clean Architecture:<\/h4>\n<p><strong>\u041c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b<\/strong> \u2014 \u043b\u0435\u043d\u0434\u0438\u043d\u0433\u0438, \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u0441\u0430\u0439\u0442\u044b, \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f\u044b<\/p>\n<ul>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043f\u0430\u043f\u043a\u0438 <code>app\/<\/code>, <code>components\/<\/code>, <code>pages\/<\/code>, <code>hooks\/<\/code> \u2014 \u044d\u0442\u043e\u0433\u043e \u0441\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e (\u043e\u043f\u044f\u0442\u044c \u0436\u0435 \u044d\u0442\u043e \u043d\u0435 \u043f\u0430\u043d\u0430\u0446\u0435\u044f, \u0432\u0441\u0435 \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u0443\u0439 \u043f\u043e\u0434 \u0441\u0435\u0431\u044f)<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435 \u0443\u0441\u043b\u043e\u0436\u043d\u044f\u0439 \u0442\u0430\u043c, \u0433\u0434\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u0430<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u041a\u043e\u0440\u043e\u0442\u043a\u043e\u0441\u0440\u043e\u0447\u043d\u044b\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b \/ \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b<\/strong> \u2014 MVP, \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438, \u0433\u0434\u0435 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u0430\u0436\u043d\u0435\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b. MVP \u044d\u0442\u043e \u043d\u0435 \u0442\u043e\u0442 \u043f\u0440\u043e\u0435\u043a\u0442 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u043b\u0438\u0437\u044b\u0432\u0430\u0442\u044c, \u044d\u0442\u043e \u0442\u043e \u0447\u0442\u043e \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u043d\u043e.<\/p>\n<p><strong>\u041f\u0440\u043e\u0435\u043a\u0442\u044b &#171;\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u043b\u043a\u0438&#187;<\/strong> &#8212; \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0438\u043c\u0435\u044e\u0442 \u043d\u0438\u043a\u0430\u043a\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438, \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u0438. Clean \u044f\u0432\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u043d\u044b\u043c \u0432 \u0442\u0430\u043a\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435.<\/p>\n<h4>\u041a\u043e\u0433\u0434\u0430 Clean Architecture \u043e\u043f\u0440\u0430\u0432\u0434\u0430\u043d\u0430:<\/h4>\n<p><strong>\u0421\u0440\u0435\u0434\u043d\u0438\u0435\/\u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b<\/strong> \u2014 SaaS, \u0430\u0434\u043c\u0438\u043d\u043a\u0438, \u0441\u043b\u043e\u0436\u043d\u044b\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/p>\n<ul>\n<li>\n<p>\u0414\u043e\u043b\u0433\u043e\u0441\u0440\u043e\u0447\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0438\u0437 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432, \u0438 \u0443\u0436 \u0442\u0435\u043c \u0431\u043e\u043b\u0435\u0435 \u0435\u0441\u043b\u0438 \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043a\u043e\u043c\u0430\u043d\u0434<\/p>\n<\/li>\n<li>\n<p>\u0421\u043b\u043e\u0436\u043d\u0430\u044f \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u0414\u043e\u043b\u0433\u043e\u0441\u0440\u043e\u0447\u043d\u044b\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b<\/strong> \u2014 \u043a\u043e\u0434 \u0431\u0443\u0434\u0435\u0442 \u0436\u0438\u0442\u044c \u0433\u043e\u0434\u0430\u043c\u0438<\/p>\n<ul>\n<li>\n<p>\u041c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f, \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438, \u043a\u043e\u043c\u0430\u043d\u0434\u044b<\/p>\n<\/li>\n<li>\n<p>\u041d\u0443\u0436\u043d\u0430 \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c \u0438 \u043f\u0440\u0435\u0434\u0441\u043a\u0430\u0437\u0443\u0435\u043c\u043e\u0441\u0442\u044c<\/p>\n<\/li>\n<\/ul>\n<h4>\u0414\u043e\u043b\u0433\u0438\u0439 \u0441\u0442\u0430\u0440\u0442, \u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430<\/h4>\n<p>\u0414\u0430, \u043f\u0435\u0440\u0432\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0431\u0443\u0434\u0435\u0442 \u043d\u0435\u043f\u0440\u0438\u0432\u044b\u0447\u043d\u043e. \u0414\u0430, \u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u0444\u0430\u0439\u043b\u043e\u0432. \u0414\u0430, \u043d\u0443\u0436\u043d\u043e \u0432\u0440\u0435\u043c\u044f \u043d\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 DI \u0438 \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432.<br \/> \u0422\u0430\u043a \u0436\u0435 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u043d\u0430\u0431\u0440\u0430\u0442\u044c \u0434\u0436\u0443\u043d\u043e\u0432 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442.<\/p>\n<p>\u041d\u043e \u0447\u0435\u0440\u0435\u0437 \u043f\u0430\u0440\u0443 \u043b\u0435\u0442 \u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0442\u0440\u0430\u0442\u0438\u0442\u044c \u043a\u0443\u0447\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0430 \u0434\u0435\u0431\u0430\u0433. \u041a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043b\u043e\u043c\u0430\u0442\u044c\u0441\u044f \u0434\u0440\u0443\u0433\u043e\u0435. \u041e\u0447\u0435\u043d\u044c \u043c\u043d\u043e\u0433\u043e \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 \u0432\u0430\u043c \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 \u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<\/p>\n<h4>\u0427\u0442\u043e \u043d\u0435 \u0440\u0430\u0441\u043a\u0440\u044b\u0442\u043e \u0432 \u0441\u0442\u0430\u0442\u044c\u0435<\/h4>\n<p>\u042d\u0442\u043e \u0431\u0430\u0437\u043e\u0432\u0430\u044f \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f. \u0412 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0435\u0441\u0442\u044c \u0435\u0449\u0451 \u043c\u043d\u043e\u0433\u043e \u043d\u044e\u0430\u043d\u0441\u043e\u0432:<\/p>\n<p><strong>DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440:<\/strong><\/p>\n<ul>\n<li>\n<p>\u041f\u043e\u0447\u0435\u043c\u0443 \u043d\u0435 \u0433\u043e\u0442\u043e\u0432\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f? \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u044b \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0435 \u0444\u0438\u0447\u0438 (namespaces, lazy loading)<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u043b\u043e\u0438:<\/strong><\/p>\n<ul>\n<li>\n<p>Analytics \u0441\u043b\u043e\u0439 \u0434\u043b\u044f \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0438<\/p>\n<\/li>\n<li>\n<p>Error handling \u0441\u043b\u043e\u0439 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043e\u0448\u0438\u0431\u043e\u043a<\/p>\n<\/li>\n<li>\n<p>Logging \u0441\u043b\u043e\u0439 \u0434\u043b\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p>Caching \u0441\u043b\u043e\u0439 \u0434\u043b\u044f \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u041e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438:<\/strong><\/p>\n<ul>\n<li>\n<p>Code splitting \u0447\u0435\u0440\u0435\u0437 DI \u043c\u043e\u0434\u0443\u043b\u0438<\/p>\n<\/li>\n<li>\n<p>Lazy loading \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>Memoization \u0434\u043b\u044f \u0442\u044f\u0436\u0451\u043b\u044b\u0445 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0430\u043a \u0436\u0435 \u0435\u0441\u043b\u0438 \u0445\u043e\u0442\u0438\u0442\u0435 \u0441\u043e\u0432\u043c\u0435\u0449\u0430\u0442\u044c react, vue \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0438 \u0432 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435, \u0442\u043e \u0435\u0449\u0435 \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0440\u043e\u0443\u0442\u0435\u0440 \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u044f\u0449\u0438\u0439 \u043e\u0442 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430.<\/p>\n<p>Mappers \u043c\u0435\u0436\u0434\u0443 \u0441\u043b\u043e\u044f\u043c\u0438 (DTO&#8217;s) &#8212; \u0434\u0435\u043b\u0430\u0435\u043c DTO \u0434\u043b\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 (API, wasm postgress, indexed db) \u0438 \u043c\u0430\u043f\u0438\u043c \u0432 \u0431\u0438\u0437\u043d\u0435\u0441 \u043c\u043e\u0434\u0435\u043b\u0438 (\u043f\u043e \u0442\u0438\u043f\u0443 toUserModel \u043c\u0430\u043f\u043f\u0435\u0440 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 dto \u043c\u0430\u043f\u0438\u0442 \u0432 domain \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u0430\u043d\u043d\u044b\u0445).<\/p>\n<h3>\u0418\u0442\u043e\u0433\u043e\u0432\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h3>\n<pre><code>src\/ \u251c\u2500\u2500 core\/                          # \u0411\u0430\u0437\u043e\u0432\u0430\u044f \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u2502   \u251c\u2500\u2500 lib\/ \u2502   \u2502   \u251c\u2500\u2500 di\/                   # DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0441 namespaces \u2502   \u2502   \u251c\u2500\u2500 flow\/                 # \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f flow \u2502   \u2502   \u251c\u2500\u2500 mvvm\/ \u2502   \u2502   \u2502   \u251c\u2500\u2500 react\/           # React-\u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0435 \u0445\u0443\u043a\u0438 \u2502   \u2502   \u2502   \u2514\u2500\u2500 vue\/             # Vue-\u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0435 \u0445\u0443\u043a\u0438 \u2502   \u2502   \u2514\u2500\u2500 logger\/              # \u041b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u2502   \u251c\u2500\u2500 ui\/                       # \u041f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 UI \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b (UI kit\\lib) \u2502   \u2514\u2500\u2500 network\/                  # \u0411\u0430\u0437\u043e\u0432\u044b\u0435 HTTP \u043a\u043b\u0438\u0435\u043d\u0442\u044b \u251c\u2500\u2500 app\/                          # \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u2502   \u251c\u2500\u2500 bootstrap.tsx            # \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, DI, \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u2502   \u251c\u2500\u2500 router\/                  # \u0420\u043e\u0443\u0442\u0438\u043d\u0433 \u2502   \u2514\u2500\u2500 styles\/                  # \u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0438\u043b\u0438 \u2514\u2500\u2500 features\/                    # \u0411\u0438\u0437\u043d\u0435\u0441-\u0444\u0438\u0447\u0438     \u251c\u2500\u2500 auth\/                    # \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f     \u2502   \u251c\u2500\u2500 domain\/             # \u0411\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430     \u2502   \u2502   \u251c\u2500\u2500 model\/          # \u0414\u043e\u043c\u0435\u043d\u043d\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438     \u2502   \u2502   \u251c\u2500\u2500 repository\/     # \u0410\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u0432     \u2502   \u2502   \u2514\u2500\u2500 use_case\/       # Use Cases     \u2502   \u251c\u2500\u2500 data\/               # \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438     \u2502   \u2502   \u251c\u2500\u2500 network\/        # API \u043a\u043b\u0438\u0435\u043d\u0442\u044b     \u2502   \u2502   \u2514\u2500\u2500 repository\/     # \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u0432     \u2502   \u251c\u2500\u2500 presentation\/       # UI \u0441\u043b\u043e\u0439     \u2502   \u2502   \u251c\u2500\u2500 view_model\/     # ViewModels     \u2502   \u2502   \u2514\u2500\u2500 view\/           # UI \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b     \u2502   \u2514\u2500\u2500 di\/                 # DI \u043c\u043e\u0434\u0443\u043b\u044c \u0444\u0438\u0447\u0438     \u2514\u2500\u2500 posts\/                  # \u0414\u0440\u0443\u0433\u0430\u044f \u0444\u0438\u0447\u0430 (\u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e) <\/code><\/pre>\n<p><strong>\u0418\u0437\u043e\u043b\u044f\u0446\u0438\u044f \u0444\u0438\u0447:<\/strong><\/p>\n<ul>\n<li>\n<p>\u041a\u0430\u0436\u0434\u0430\u044f \u0444\u0438\u0447\u0430 \u0432 \u0441\u0432\u043e\u0451\u043c namespace<\/p>\n<\/li>\n<li>\n<p>DI \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043c\u0435\u0436\u0434\u0443 \u0444\u0438\u0447\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p>\u041c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u043e\u0441\u0442\u044c:<\/strong><\/p>\n<ul>\n<li>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0441\u043b\u043e\u0439 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e<\/p>\n<\/li>\n<li>\n<p>\u041c\u043e\u043a\u0438 \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0442 \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0439<\/p>\n<\/li>\n<li>\n<p>\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0447\u0435\u0440\u0435\u0437 DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u043e\u0441\u0442\u044c:<\/strong><\/p>\n<ul>\n<li>\n<p>\u041d\u043e\u0432\u044b\u0435 \u0444\u0438\u0447\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u043c \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430\u043c<\/p>\n<\/li>\n<li>\n<p>\u041b\u0435\u0433\u043a\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438 (React \u2192 Vue)<\/p>\n<\/li>\n<li>\n<p>\u041c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430 \u043c\u0438\u043a\u0440\u043e\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u044b<\/p>\n<\/li>\n<\/ul>\n<hr\/>\n<p><strong>P.S.<\/strong> \u0415\u0441\u043b\u0438 \u0442\u044b \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0448\u044c clean \u0432\u043e forontend \u0438\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0445\u043e\u0436\u0435\u0435 &#8212; \u0434\u0435\u043b\u0438\u0441\u044c \u043e\u043f\u044b\u0442\u043e\u043c \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445, \u0431\u0443\u0434\u0443 \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0434 \u0443\u0437\u043d\u0430\u0442\u044c \u0434\u043b\u044f \u0441\u0435\u0431\u044f \u0447\u0442\u043e\u0442\u043e \u043d\u043e\u0432\u043e\u0435, \u0438\u043b\u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043b\u0443\u0447\u0448\u0435 \u0443\u0436\u0435 \u0432 \u0442\u043e\u043c \u0447\u0442\u043e \u0435\u0441\u0442\u044c! \ud83d\udc47<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/938894\/\"> https:\/\/habr.com\/ru\/articles\/938894\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442! \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0445\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441 \u0442\u043e\u0431\u043e\u0439 \u043e\u043f\u044b\u0442\u043e\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043e\u0442 Feature-Sliced Design \u043a Clean Architecture \u0432\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435. \u041f\u043e\u0447\u0435\u043c\u0443 \u044f \u0441\u0447\u0438\u0442\u0430\u044e Clean Architecture \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0435\u0439 \u0434\u043b\u044f \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0438 \u043a\u0430\u043a \u043e\u043d\u0430 \u0440\u0435\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0442\u044b \u0442\u043e\u0447\u043d\u043e \u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u043b\u0441\u044f.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0442\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0448\u044c FSD \u0438\u043b\u0438 \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u043f\u0438\u0448\u0435\u0448\u044c \u0432\u0441\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u0445 React \u2014 \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u0442\u043e\u0447\u043d\u043e \u0434\u043b\u044f \u0442\u0435\u0431\u044f.<\/p>\n<h3>FSD: \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u043e, \u043d\u043e \u043d\u0435 \u0431\u0435\u0437 \u043f\u0440\u043e\u0431\u043b\u0435\u043c<\/h3>\n<p>Feature-Sliced Design \u0441\u0435\u0439\u0447\u0430\u0441 \u043e\u0434\u043d\u0430 \u0438\u0437 \u0441\u0430\u043c\u044b\u0445 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u043b\u043e\u0433\u0438\u0439 \u0432\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435. \u0418 \u043d\u0435 \u0437\u0440\u044f \u2014 \u043e\u043d\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u043b\u0443\u0447\u0448\u0435, \u0447\u0435\u043c \u0445\u0430\u043e\u0442\u0438\u0447\u043d\u043e\u0435 \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432.<\/p>\n<h4>\u0427\u0442\u043e \u0445\u043e\u0440\u043e\u0448\u0435\u0433\u043e \u0432 FSD?<\/h4>\n<ul>\n<li>\n<p><strong>\u041f\u043e\u043d\u044f\u0442\u043d\u043e\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435<\/strong> \u043f\u043e \u0444\u0438\u0447\u0430\u043c \u0438 \u0441\u043b\u0430\u0439\u0441\u0430\u043c<\/p>\n<\/li>\n<li>\n<p><strong>\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/strong> \u2014 \u043b\u044e\u0431\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0431\u044b\u0441\u0442\u0440\u043e \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u0442\u0441\u044f<\/p>\n<\/li>\n<li>\n<p><strong>\u0418\u0437\u043e\u043b\u044f\u0446\u0438\u044f \u0444\u0438\u0447<\/strong> \u2014 \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0444\u0438\u0447\u0438 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0437\u0430\u0432\u0438\u0441\u0435\u0442\u044c \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430<\/p>\n<\/li>\n<\/ul>\n<h4>\u041d\u043e \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0438 \u043e\u043d\u0438 \u0441\u0435\u0440\u044c\u0451\u0437\u043d\u044b\u0435<\/h4>\n<p>\u0417\u0430 2 \u0433\u043e\u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 FSD \u044f \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0441 \u0440\u044f\u0434\u043e\u043c \u0431\u043e\u043b\u0435\u0432\u044b\u0445 \u0442\u043e\u0447\u0435\u043a:<\/p>\n<p><strong>1. Cross-\u0438\u043c\u043f\u043e\u0440\u0442\u044b \u2014 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u0430\u044f \u0433\u043e\u043b\u043e\u0432\u043d\u0430\u044f \u0431\u043e\u043b\u044c<\/strong><\/p>\n<pre><code class=\"typescript\">\/\/ \u0425\u043e\u0447\u0435\u0442\u0441\u044f \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a, \u043d\u043e \u043d\u0435\u043b\u044c\u0437\u044f: import { useAuth } from '@\/features\/auth\/model' import { PostsList } from '@\/features\/posts\/ui'  \/\/ FSD \u0437\u0430\u043f\u0440\u0435\u0449\u0430\u0435\u0442 \u043f\u0440\u044f\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043c\u0435\u0436\u0434\u0443 \u0444\u0438\u0447\u0430\u043c\u0438 \/\/ \u041f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u0437\u0433\u0430\u043b\u044f\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 shared \u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0441\u043b\u043e\u0438 <\/code><\/pre>\n<p><strong>2. \u041d\u0435\u044f\u0441\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u043d\u043e\u0441\u0442\u0438 \u043c\u043e\u0434\u0443\u043b\u0435\u0439<\/strong><br \/> \u041a\u0443\u0434\u0430 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c <code>NotificationService<\/code>? \u0412 shared? \u041d\u043e \u043e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0445 \u0444\u0438\u0447\u0430\u0445. \u0412 \u043e\u0434\u043d\u0443 \u0438\u0437 \u0444\u0438\u0447? \u041d\u043e \u0442\u043e\u0433\u0434\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u0444\u0438\u0447\u0438 \u043d\u0435 \u043c\u043e\u0433\u0443\u0442 \u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u041f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0442 \u0441\u043f\u043e\u0440\u044b \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u043e \u0442\u043e\u043c, \u043a\u0443\u0434\u0430 \u0447\u0442\u043e \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0441\u044f.<\/p>\n<p><strong>3. \u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u2014 \u043d\u0435 \u0441\u0430\u043c\u043e\u0435 \u0443\u0434\u043e\u0431\u043d\u043e\u0435<\/strong><\/p>\n<pre><code class=\"typescript\">\/\/ \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0443, \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0443\u0447\u0443 \u0444\u0430\u0439\u043b\u043e\u0432 import { loginModel } from '..\/model' import { api } from '..\/..\/shared\/api' import { router } from '..\/..\/shared\/router'  \/\/ \u0418 \u043c\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043d\u0438\u0445 jest.mock('..\/..\/shared\/api') jest.mock('..\/..\/shared\/router') <\/code><\/pre>\n<p><strong>4. \u041f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043b\u043e\u0433\u0438\u043a\u0438 \u2014 \u0431\u043e\u043b\u044c<\/strong><br \/> \u041a\u043e\u0433\u0434\u0430 \u043f\u043e\u0445\u043e\u0436\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u043d\u0443\u0436\u043d\u0430 \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0444\u0438\u0447\u0430\u0445, \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043b\u0438\u0431\u043e \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434, \u043b\u0438\u0431\u043e \u0432\u044b\u043d\u043e\u0441\u0438\u0442\u044c \u0432 shared \u0438 \u0442\u0435\u0440\u044f\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0444\u0438\u0447\u0438.<\/p>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u044f \u0432\u044b\u0431\u0440\u0430\u043b Clean Architecture<\/h3>\n<p>\u041f\u0440\u0438\u0448\u0451\u043b \u044f \u0432\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u0438\u0437 Android \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 (Kotlin + Compose), \u0433\u0434\u0435 Clean Architecture \u2014 \u044d\u0442\u043e \u0443\u0436\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430 \u0441\u043b\u043e\u0432\u0430\u0445, \u0430 \u0432 \u0441\u0430\u043c\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u043e\u0442 google. \u0418 \u043f\u043e\u043d\u044f\u043b, \u0447\u0442\u043e \u043c\u043d\u043e\u0433\u0438\u0435 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b \u043e\u0442\u0442\u0443\u0434\u0430 \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0438 \u0432\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435.<\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f Clean Architecture \u2014 <strong>\u0438\u043d\u0432\u0435\u0440\u0441\u0438\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439<\/strong>. \u0411\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430 \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0434\u0435\u0442\u0430\u043b\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 (UI, API, \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435), \u0430 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442.<\/p>\n<h3>\u041c\u043e\u044f \u0432\u0435\u0440\u0441\u0438\u044f Clean Architecture<\/h3>\n<p>\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043c\u043e\u0434\u0435\u0440\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e Clean Architecture, \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0443\u044e \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u043d\u0443\u0436\u043d\u044b. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0442\u0440\u0451\u0445 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0447\u0430\u0441\u0442\u0435\u0439:<\/p>\n<ul>\n<li>\n<p><code><strong>core\/<\/strong><\/code> \u2014 \u0431\u0430\u0437\u043e\u0432\u0430\u044f \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 (DI, MVVM, Flow, UI \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b)<\/p>\n<\/li>\n<li>\n<p><code><strong>app\/<\/strong><\/code> \u2014 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0440\u043e\u0443\u0442\u0438\u043d\u0433, \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435<\/p>\n<\/li>\n<li>\n<p><code><strong>features\/<\/strong><\/code> \u2014 \u0431\u0438\u0437\u043d\u0435\u0441-\u0444\u0438\u0447\u0438 \u0441\u043e \u0441\u0442\u0440\u043e\u0433\u043e\u0439 \u0438\u0437\u043e\u043b\u044f\u0446\u0438\u0435\u0439<\/p>\n<\/li>\n<\/ul>\n<blockquote>\n<p>\u041f\u0440\u0438 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0447\u0442\u0435\u043d\u0438\u0438 \u0435\u0441\u043b\u0438 \u043d\u0435 \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u043d\u044f\u0442\u043d\u043e \u043f\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043b\u043e\u0435\u0432, \u0442\u043e \u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u0435\u0441\u0442\u044c \u043e\u0431\u0449\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c \u0442\u0443\u0434\u0430<\/p>\n<\/blockquote>\n<p>\u041a\u0430\u0436\u0434\u0430\u044f \u0444\u0438\u0447\u0430 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u0438\u0437 \u0442\u0440\u0451\u0445 \u0441\u043b\u043e\u0451\u0432:<\/p>\n<h4>1. Domain \u0441\u043b\u043e\u0439 \u2014 \u0441\u0435\u0440\u0434\u0446\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/h4>\n<p>\u0417\u0434\u0435\u0441\u044c \u0436\u0438\u0432\u0443\u0442 \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u043e\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0445 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0439:<\/p>\n<pre><code class=\"typescript\">\/\/ domain\/repository\/AuthRepository.ts export abstract class AuthRepository {   abstract tokensData: StateFlow&lt;TokensData | null&gt; | null   abstract getTokens(): TokensData | null   abstract setTokens(tokens: TokensData): void   abstract removeTokens(): void } <\/code><\/pre>\n<blockquote>\n<p>\u043f\u0440\u043e \u0442\u043e \u0447\u0442\u043e \u0442\u0430\u043a\u043e\u0435 StateFlow \u043c\u044b \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435<\/p>\n<\/blockquote>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0432 domain \u0441\u043b\u043e\u0435 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0442\u0430\u043a \u0436\u0435 use cases \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<br \/> <strong>Use Cases<\/strong> \u2014 \u044d\u0442\u043e \u043e\u0440\u043a\u0435\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438. \u041a\u0430\u0436\u0434\u044b\u0439 Use Case \u0440\u0435\u0448\u0430\u0435\u0442 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443:<\/p>\n<p>\u0423 use case \u0435\u0441\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044f executor \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043f\u0440\u0438\u043d\u044f\u0442\u043d\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c execute. \u042d\u0442\u043e \u043a\u0430\u043a \u0440\u0430\u0437 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0449\u0430\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0437\u0443\u044e\u0449\u0435\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f<\/p>\n<pre><code class=\"typescript\">\/\/ domain\/use_case\/LoginUseCase.ts export class LoginUseCase {   constructor(     @Inject(AuthNetwork) private readonly _authNetwork: AuthNetwork,     @Inject(AuthRepository) private readonly _authRepository: AuthRepository,     @Inject(UserStorage) private readonly _userStorage: UserStorage, \/\/ \u041c\u043e\u0436\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435   ) {}    async execute(body: LoginBody): Promise&lt;void&gt; {     \/\/ 1. \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043a\u044d\u0448     const cachedUser = await this._userStorage.getUser(body.email)     if (cachedUser?.isValid()) {       this._authRepository.setTokens(cachedUser.tokens)       return     }      \/\/ 2. \u0414\u0435\u043b\u0430\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u043a API     const tokens = await this._authNetwork.login(body)          \/\/ 3. \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0442\u043e\u043a\u0435\u043d\u044b     this._authRepository.setTokens({       accessToken: tokens.accessToken,       refreshToken: tokens.refreshToken,     })      \/\/ 4. \u041a\u044d\u0448\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f     await this._userStorage.saveUser(body.email, { tokens, timestamp: Date.now() })   } } <\/code><\/pre>\n<p><strong>\u041f\u0440\u043e\u0444\u0438\u0442<\/strong>: Use Case \u2014 \u044d\u0442\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041c\u043e\u0436\u0435\u0448\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443 \u2014 \u0432\u0441\u0451 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u043e.<\/p>\n<h4>2. Data \u0441\u043b\u043e\u0439 \u2014 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438<\/h4>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438 \u0438\u0437 Domain \u0441\u043b\u043e\u044f. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 Network \u0441\u043b\u043e\u0439 \u0441 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u043e\u0439 \u0442\u0438\u043f\u043e\u0432:<\/p>\n<pre><code class=\"typescript\">\/\/ data\/network\/AuthNetwork.ts import * as v from 'valibot'  \/\/ \u0421\u0445\u0435\u043c\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u043e\u0442 API const LoginResponseSchema = v.object({   accessToken: v.string(),   refreshToken: v.string(),   user: v.object({     id: v.string(),     email: v.string(),   }) })  export class AuthNetwork {   constructor(@Inject(Axios) private readonly _httpClient: Axios) {}    async login(body: LoginBody): Promise&lt;TokensDataWithRoleDto&gt; {     const response = await this._httpClient.post(\"\/api\/signin\", body)     return parseAsync(TokensDataWithRoleDtoSchema, response.data)   } } <\/code><\/pre>\n<p><strong>Runtime-\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445<\/strong> \u2014 \u0432\u0430\u0436\u043d\u043e \u043d\u0435 \u0434\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e TS-\u0442\u0438\u043f\u0430\u043c, \u0435\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0438\u0437\u0432\u043d\u0435. \u0412 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435 \u0442\u0438\u043f\u043e\u0432 \u0443 \u043d\u0430\u0441 \u043d\u0435\u0442. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e valibot \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0442\u0438\u043f\u0430 \u0438 \u043f\u0440\u043e\u0431\u0440\u043e\u0441\u0430 \u043e\u0448\u0438\u0431\u043a\u0438<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c Repository \u2014 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<pre><code class=\"typescript\">\/\/ data\/repository\/AuthRepositoryImpl.ts export class AuthRepositoryImpl extends AuthRepository {   private readonly _tokensData = new MutableStateFlow&lt;TokensData | null&gt;(     localStorage.getItem(\"refreshToken\")       ? {           accessToken: null,           refreshToken: localStorage.getItem(\"refreshToken\"),         }       : null,   )    public tokensData = this._tokensData.asStateFlow()    setTokens(tokens: TokensData): void {     if (tokens.refreshToken) {       localStorage.setItem(\"refreshToken\", tokens.refreshToken)     }     this._tokensData.set(tokens)   }    \/\/ \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b... } <\/code><\/pre>\n<blockquote>\n<p>\u0412\u0430\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c \u0447\u0442\u043e \u043c\u044b \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c\u0441\u044f \u043e\u0442 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0432 domain \u0441\u043b\u043e\u0435. \u0414\u0430\u043b\u0435\u0435 DI \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043a\u0430\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u043c\u0435\u043d\u043d\u043e \u043f\u043e \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438 \u0432 domain \u0441\u043b\u043e\u0435. \u0412\u0441\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u0431\u0438\u0437\u043d\u0435\u0441 \u0441\u043b\u043e\u044f \u043a\u0430\u043a \u0447\u0435\u0440\u043d\u044b\u0439 \u044f\u0449\u0438\u043a. \u0411\u0438\u0437\u043d\u0435\u0441 \u0441\u043b\u043e\u044e \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u0437\u043d\u0430\u0442\u044c \u0447\u0442\u043e \u0443\u043c\u0435\u0435\u0442 \u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0430 \u043a\u0430\u043a, \u0443\u0436\u0435 \u043d\u0435 \u0432\u0430\u0436\u043d\u043e, \u0445\u043e\u0442\u044c \u0438\u0437 \u043b\u043e\u043a\u043b\u0430\u044c\u043d\u043e\u0439 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0435 \u0431\u0435\u0440\u0443\u0442\u0441\u044f, \u0445\u043e\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f.<\/p>\n<\/blockquote>\n<p><strong>\u041b\u0435\u0433\u043a\u0430\u044f \u043f\u043e\u0434\u043c\u0435\u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0439<\/strong>. \u0417\u0430\u0445\u043e\u0442\u0435\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c WASM Postgres \u0432\u043c\u0435\u0441\u0442\u043e localStorage?<\/p>\n<pre><code class=\"typescript\">\/\/ data\/repository\/PostgresAuthRepository.ts export class PostgresAuthRepository extends AuthRepository {   constructor(     @Inject(PostgresWasm) private readonly _postgres: PostgresWasm   ) {}    async setTokens(tokens: TokensData): Promise&lt;void&gt; {     await this._postgres.query(       'INSERT INTO tokens (access_token, refresh_token) VALUES ($1, $2)',       [tokens.accessToken, tokens.refreshToken]     )     this._tokensData.set(tokens)   } } <\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u0448\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0441 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 postgres, \u043c\u0435\u043d\u044f\u0435\u0448\u044c \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u0447\u043a\u0443 \u0432 DI \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435 \u2014 \u0438 \u0433\u043e\u0442\u043e\u0432\u043e! Domain \u0441\u043b\u043e\u0439 \u0434\u0430\u0436\u0435 \u043d\u0435 \u0437\u0430\u043c\u0435\u0442\u0438\u0442 \u0440\u0430\u0437\u043d\u0438\u0446\u044b.<\/p>\n<h4>3. Presentation \u0441\u043b\u043e\u0439 \u2014 MVVM + MVI \u043f\u0430\u0442\u0442\u0435\u0440\u043d<\/h4>\n<p>\u0422\u0443\u0442 \u044f \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0443\u0432\u043b\u0435\u043a\u0441\u044f \u0438 \u0432\u0437\u044f\u043b \u043d\u0435\u0439\u043c\u0438\u043d\u0433 \u0438\u0437 kotlin \ud83d\ude42<\/p>\n<p><strong>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 Flow \u2014 \u0447\u0442\u043e \u044d\u0442\u043e \u0438 \u0437\u0430\u0447\u0435\u043c?<\/strong><\/p>\n<p>Flow (\u043e\u0442 \u0430\u043d\u0433\u043b. &#171;\u041f\u043e\u0442\u043e\u043a&#187;) \u2014 \u044d\u0442\u043e \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u043f\u043e\u0442\u043e\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445. \u0422\u0443\u0442 \u043c\u044b \u043f\u043e \u0441\u0443\u0442\u0438 \u0434\u0435\u043b\u0430\u0435\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e observers.<\/p>\n<blockquote>\n<p>\u0422\u0443\u0442 \u0434\u043b\u044f \u0441\u0435\u0431\u044f \u0441\u0434\u0435\u043b\u0430\u043b \u043f\u0440\u043e\u0441\u0442\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e. \u041d\u0435 \u043d\u0443\u0436\u043d\u043e \u044d\u0442\u043e \u0432\u043e\u0441\u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u043a\u0430\u043a \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e. \u0414\u0443\u043c\u0430\u044e \u0442\u0443\u0442 \u0434\u0430\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0432\u0437\u044f\u0442\u044c \u043d\u0430\u0442\u0438\u0432\u043d\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e zustand \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u043e \u044f\u0432\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u0434\u0430 \u0438 \u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u043e\u0442 \u0435\u0449\u0435 \u043e\u0434\u043d\u043e\u0439 \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438.<\/p>\n<\/blockquote>\n<pre><code class=\"typescript\">\/\/ core\/lib\/flow\/Flow.ts export type Listener&lt;T&gt; = (value: T) =&gt; void  \/\/ \u041f\u0440\u043e\u0441\u0442\u043e\u0439 \u043f\u043e\u0442\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f export class Flow&lt;T&gt; {   protected listeners: Map&lt;Listener&lt;T&gt;, () =&gt; void&gt; = new Map()    subscribe(listener: Listener&lt;T&gt;): () =&gt; void {     this.listeners.set(listener, () =&gt; {       this.listeners.delete(listener)     })      return () =&gt; {       this.listeners.delete(listener)     }   } }  \/\/ \u041f\u043e\u0442\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \/\/ \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0438 \u043e\u0442 Flow \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0432\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \/\/ \u041d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u044f\u0446\u0438\u0438 \u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e export class MutableFlow&lt;T&gt; extends Flow&lt;T&gt; {   constructor() {     super()   }    emit(value: T): void {     this.listeners.forEach((_, listener) =&gt; listener(value))   }    asFlow(): Flow&lt;T&gt; {     return this   } }  \/\/ \u041f\u043e\u0442\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043a\u0435\u0448\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u044d\u043c\u0438\u0442\u0438\u0442 \u0435\u0433\u043e \u043f\u0440\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0435 export class StateFlow&lt;T&gt; extends Flow&lt;T&gt; {   protected lastValue: T    constructor(initialValue: T) {     super()     this.lastValue = initialValue   }    get value() {     return this.lastValue   }    override subscribe(listener: Listener&lt;T&gt;): () =&gt; void {     this.listeners.set(listener, () =&gt; {       this.listeners.delete(listener)     })      listener(this.lastValue) \/\/ Immediately emit current value      return () =&gt; {       this.listeners.delete(listener)     }   }    asFlow(): Flow&lt;T&gt; {     return this   } }  \/\/ \u041f\u043e\u0442\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043a\u0435\u0448\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u044d\u043c\u0438\u0442\u0438\u0442 \u0435\u0433\u043e \u043f\u0440\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0435 \/\/ \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0438 \u043e\u0442 StateFlow \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0432\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \/\/ \u041d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u044f\u0446\u0438\u0438 \u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e export class MutableStateFlow&lt;T&gt; extends StateFlow&lt;T&gt; {   constructor(initialValue: T) {     super(initialValue)   }    get value() {     return this.lastValue   }    update(value: Partial&lt;T&gt;) {     if (this.lastValue !== value) {       this.lastValue = { ...this.lastValue, ...value }       this.listeners.forEach((_, listener) =&gt; listener(this.lastValue))     }   }    set(value: T): void {     if (this.lastValue !== value) {       this.lastValue = value       this.listeners.forEach((_, listener) =&gt; listener(value))     }   }    asStateFlow(): StateFlow&lt;T&gt; {     return this \/\/ Upcast to immutable version   } } <\/code><\/pre>\n<p><strong>\u0420\u0430\u0437\u043d\u0438\u0446\u0430 \u043c\u0435\u0436\u0434\u0443 \u0442\u0438\u043f\u0430\u043c\u0438 Flow:<\/strong><\/p>\n<ul>\n<li>\n<p><strong>Flow<\/strong> \u2014 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u0441\u043e\u0431\u044b\u0442\u0438\u0439 (\u043a\u043b\u0438\u043a\u0438, HTTP \u043e\u0442\u0432\u0435\u0442\u044b)<\/p>\n<\/li>\n<li>\n<p><strong>StateFlow<\/strong> \u2014 \u043f\u043e\u0442\u043e\u043a \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f<\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-471340","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/471340","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=471340"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/471340\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=471340"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=471340"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=471340"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}