📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Backend-архитектура реального проекта: полный разбор

Kirill Sachkov Development1:09:56

Transcription

Всем привет. Меня зовут Кирилл, и я в этом видео хочу показать вам архитектуру реального продакшн-проекта. Я уже давно хотел сделать видео, где я вам расскажу про бэкенд и фронтенд архитектуру. Ну, в целом про фулстек архитектуру таких проектов, которые реально работают, которые интегрируются с внешними системами, которые содержат в себе много микросервисов, которые запущены в продакшене.

Я это всё буду показывать на примере моего проекта, моей платформы, где у меня обучаются реальные люди. То есть всё это, а, сейчас запущено в продакшене. И вот на примере этого я хочу разобрать множество элементов, из которых этот проект состоит. Сразу стоит сказать, что каждая тема, которая сейчас будет разбираться, по сути, это отдельная большая тема, которой нужно уделять очень много внимания. То есть это, если так посмотреть, и кэширование, и разные базы данных, и коммуникация между микросервисами, CI/CD, мониторинг, как фронтенд в принципе устроен, как всё это инфраструктурно настраивается, там работа с полнотекстовым поиском, интеграция с внешними системами, работа с файлами, это всё, а, отдельные большие темы, которые, ну, никак нельзя раскрыть за одно видео.

Поэтому я дам вам именно фундамент, расскажу вам, как всё это устроено, покажу архитектуру, сделаю важные акценты на каких-то вещах. Если вы хотите научиться делать подобные проекты, работать с ними в продакшене, в принципе, для себя делать проекты либо на коммерческом уровне уже, то очень будет вам полезно это видео. И, в принципе, оно может служить вам как и роадмапом, так и хорошей архитектурной базой. Короче, давайте начинать.

Сейчас вы видите список тем, которые будут затронуты в этом видео. Опять же, каждый из этих тем заслуживает отдельного материала, практики, обучения. Поэтому здесь мы рассказываем фундамент, архитектуру, а уже в следующих видео я хочу сделать именно упор на практику и показать, как я реализую, например, какую-то конкретную фичу для моего проекта, чтобы вы увидели путь от начала и до конца. Кстати говоря, если у вас есть идеи для каких-то конкретных фич, то можете писать их в комментарии, либо просто вы хотите посмотреть, как делать какой-то функционал там с платёжной системой, либо просто у вас есть какие-то прикольные идеи, можете написать их в комменты. И, кстати, переходите в Telegram-канал, там у меня довольно интересные посты, там будет дополнительный контент, которого не будет на Ютубе. Поэтому подпишитесь на Telegram-канал и следите за обновлениями.

Итак, перед вами мини-карта моего проекта, как он устроен. Сразу скажу, что это пока не конечная версия, он ещё дорабатывается, в особенности инфраструктура будет ещё сильно улучшаться, масштабироваться, но пока что это устроено так. Ну, мы видим сразу браузер, да, тут ничего необычного. Через браузер у нас отправляются запросы на Nginx, который у нас располагается на продакшн-сервере. У меня одна ВПС, там восемь ядер, значит, для процессора, 11 ГБ оперативной памяти. Всё развёрнуто пока что на Docker Compose. А дальше я буду переходить на Kubernetes, чтобы всё это дело масштабировать. Ну, потому что всё-таки Docker Compose — это уровень начальный. Это не так уж серьёзно. И множество проблем возникает в CI/CD, когда мы когда речь заходит об удобном использовании. Да, конечно, можно прикрутить Symbol и, в принципе, настроить, чтобы это всё было поудобнее, но у меня пока что всё прямолинейно, но тем не менее оно всё работает и для какого-то соло-проекта это может быть достаточно. Но если мы говорим про какую-то большую, э, большой коммерческий проект, большую инфраструктуру, ну, там без Kubernetes никуда не деться. Поэтому сразу скажу, если вы хотите реально стать продвинутым разработчиком сегодня, в эпоху, когда нам нужно разбираться в архитектуре и инфраструктуре, я вам настоятельно рекомендую изучать Kubernetes.

Но мы двигаемся дальше. Потом у нас встречает Nginx — это у нас некий API Gateway, который уже распределяет запросы от браузера, говорит, что нам показывать юзеру, говорит, куда нам направлять а наши запросы, на какие микросервисы. Дальше мы видим фронтенд часть. И на самом деле у меня развёрнуто целых два фронтенда. Значит, у меня потому что есть два поддомена. И один поддомен у меня нужен для того, чтобы генерировать моё мою основную платформу, где основной функционал. И большую часть этого функционала представляет именно, ну, оно представлено как SPA, то есть, по сути, у меня там клиентский рендеринг и большое взаимодействие именно с пользователем. Поэтому серверного рендеринга там довольно мало. А лендинги у меня и вся часть маркетинговая вынесена на отдельный домен.scoflearn.net. Пока что этого в продакшене нет. Это всё у меня тестируется, но таким механизмом пользуются довольно часто. Ниже я про него расскажу.

Здесь стоит сделать небольшую поправку. У меня вот этот фронтенд, который взаимодействует с бэкендом, он отправляет запросы не напрямую на мой бэкенд, а он направляет запросы сначала на Nginx, а он уже проксирует запросы на мой бэкенд. Это значит паттерн Gateway, то есть прокси. Но это делается для того, чтобы наш фронтенд не зависел от наших микросервисов. Допустим, если там поменяется поменяются какие-то роутинги, станет несколько экземпляров, нам нужен функционал балансировщика, нам нужен, может быть, какой-то Backend for Frontend. В общем, это отдельные паттерны в целом. API Gateway — это большая довольно тема, которую можно, опять же, разобрать отдельно. Это чисто обзорная такая секция. Дальше ниже мы разберём именно как работают конкретные детали этого всего.

Так как я бэкенд-разработчик, у меня весь бэкенд написан на .NET, на C#, но, в принципе, это неважно. Это видео будет полезно всем. Потому что, а, весь фундамент, вся база именно архитектуры, проектирования сегодня одна одинаковая для всех веб-приложений, для всего бэкенда, фронтенда. Поэтому это видео будет полезно всем. Ну и тем более сегодня мы ИИшками именно пишем код по большей части, то тогда нам нужно делать именно акцент на архитектуру и инфраструктуру и управлять этим всем. А это уже от стека к стеку почти не меняется. Но если мы говорим именно про веб-разработку.

Значит, на данный момент у меня 13 микросервисов. И дальше я расскажу, почему именно я выбрал микросервисы. То есть почему не модульный монолит, почему не просто монолит, почему именно микросервисы. Это будет дальше. Как мы видим, все они располагаются на своих портах, они запущены, у каждого свой порт. У меня они поделены на такие тематические блоки. Всё, что касается контента обучения, у меня вот здесь такая серия микросервисов. То есть это работа с тегами, это тренажёр, это прогресс, который следит за прогрессом пользователей. Ну и это сам самый, наверное, большой, ну, наравне с прогрессом — это Education Content сервис, где а хранится весь функционал по созданию контента на этой платформе. Вход, доступ, там планы, всё это у меня выделено в отдельные микросервисы. У меня есть отдельный микросервис аутентификации и авторизации, который там реализует OAuth и OpenID Connect протокол. Всё, что касается коммуникации с пользователем, это у меня есть для этого Comment сервис для комментариев, есть Notification сервис для того, чтобы уведомления пользователям приходили. Также у меня есть Telegram-бот, тоже полностью написан на .NET. Он в целом изолирован, но коммуникация с ним происходит через брокер сообщений. Всё, что касается сохранения медиа, файлов, у меня для этого есть отдельные сервисы. Есть также отдельный микросервис, который работает с искусственным интеллектом. У меня тоже есть AI-фичи, например, автоматическая проверка ревью, это транскрипция видео, выставление автоматическое тайм-кодов, ну и так далее. То есть фичи тоже тут будут. А и у тренажёра также есть фичи, которые, а, работают с искусственным интеллектом. Это анализ ответов. Интересная, на самом деле, тема, как внедрять ИИшку в ваши микросервисы, ваши проекты.

Отдельного внимания заслуживают, какие, в принципе, там базы данных и внешние сервисы я использую. Как брокер сообщений я использую RabbitMQ. А для, ну, как основную базу данных я использую PostgreSQL. У меня одна именно база данных, но она разделена на 13 схем. То есть для каждого микросервиса у меня своя схема. Есть Redis, я использую его для кэша и для хранения данных, которым я хочу быстро получить доступ, либо там для распределённых каких-то моментиков, допустим, rate limiting. И Typesense я использую как БД для полнотекстового поиска. То есть видим, стек на самом деле большой. В качестве мониторинга у меня Grafana стек, можно так сказать, Prometheus, Tempo, Grafana, где у меня дашборды, там все метрики, мониторинги, там всё это там хранится. Пользуюсь протоколом OpenTelemetry, да, для того, чтобы там он у меня с бэкендом интегрировался. И Loki у меня тоже собирает логи с разных внешних сервисов, типа базы данных и Redis и остальных.

Значит, файлы у меня все хранятся в S3, а Яндекс Cloud. Я использую облачное хранилище, а видео я храню в Kinesis. То есть там есть и встроенный CDN. Ну и в целом удобно, да, сделать интеграцию с Kinesis. Можно удобно загружать видео, просматривать. Работает довольно быстро, удобно. Есть у меня отдельный механизм работы с ИИшкой, то есть я использую провайдер AI Tunnel, а я там внутри использую различные модельки. Не только Deepseek, я использую ещё там от OpenAI Whisper для транскрипции. Ну, Deepseek 4 Pro у меня выступает для ревью именно проектов Pull Request'ов, которые делают студенты. Там, конечно, большой набор промтов, правил, примеров. Когда я раньше всё вручную проверял, всю эту инфу я собирал, лучшие практики я выписывал, всё это загрузил и реально работает довольно неплохо. И всё это интегрировано ещё с GitHub, чтобы всё было автоматически. Когда человек отправляет Pull Request, сразу начинается проверка.

Отдельный модуль также у меня занимается платёжной системой для оплаты как раз-таки курсов, подписок и всего остального. То есть дальше этот модуль будет расширяться, если понадобится. Ну и для рассылки email сообщений я использую SendGrid. Также на отдельном сервере у меня развёрнут GitLab, где у меня настроено CI/CD, где у меня есть раннеры, там проходят все пайплайны, там у меня весь деплой, то есть у меня полный цикл, так сказать, CI/CD. Э, всё это есть на отдельном сервере GitLab, э, который сам я поднимал и где у меня хранятся там все задачки, репозитории, всё это автоматически деплоится. В общем, такой обзор вот этого всего стека-монстра, который я собрал. И дальше уже пойдём по темам. Если вы что-то сейчас не понимали, это нормально. Сейчас я более базово пройдусь именно по конкретным темам, чтобы вы поняли, э, моменты, которые вы, может, не понимаете там, микросервисной архитектуре, либо как вообще в веб-приложениях происходит вот это всё взаимодействие между всеми этими элементами.

Итак, давайте начнём с базы. Я расскажу сейчас про клиент-серверное взаимодействие, как у нас клиент с сервером взаимодействует и зачем несколько доменов делают разные платформы. Самая основная концепция, что у нас есть запрос. Откуда запрос поступает? Запрос поступает из браузера. Он у нас летит на сервер, и на сервере он уже обрабатывается, и мы получаем ответ, который летит обратно к браузеру, и пользователь его уже видит. Дальше ещё одна концепция — это домен. Домен — это имя, имя, имя вашего сайта, название, допустим, да, куда надо перейти, чтобы там пользователь всё это увидел. Но конкретный адрес, на какой конкретно сервер лететь, то есть айпишник, у нас отдаёт DNS. То есть ниже мы разберём сейчас этот механизм с DNS'ом. Пока что давайте посмотрим, зачем нам несколько доменов. Вот как лично это у меня. У меня есть один домен — это scoflearn.net. Пока что это ещё не в продакшене, но скоро появится. Здесь у меня будет содержаться именно лендинг, вся маркетинговая часть. И также есть именно поддомен, где у меня содержится именно основной фронтенд, который содержит весь функционал, который работает с взаимодействует с бэкендом, то есть отправляет запрос, получает ответ, работает с пользователем. И, в принципе, весь основной функционал платформы находится на этом поддомене. Зачем такое разделение? Ну, во-первых, у нас может быть разный стек у этих проектов. То есть это два отдельных фронтенда, два отдельных сервиса, которые у меня запускаются и которые несут разную задачу. Вот этот лендинг может быть вообще в теории написан на, не знаю, обычном HTML там, не знаю, + CSS, либо какой-нибудь Astro фреймворк, да, последнее время довольно популярный, либо на любом другом фреймворке это может быть написано. У меня в данном случае я не стал распыляться, у меня всё на Next.js. Вообще есть несколько механизмов, как у нас может работать фронтенд. Это довольно большая тема. Значит, у нас есть SSG, у нас есть SSR, у нас есть ещё ISR. И каждый из этих механизмов определяет, как по сути контент страницы будет передаваться клиенту. И в данном случае я использую ISR. То есть у меня по сути передаётся статика раз в какое-то время она летит у меня к клиенту. Зачем это делается? Ну, во-первых, это скорость, это поддержка SEO. Ну, и поэтому мы можем отделять разный функционал наших систем на разные делить поддомены. Допустим, здесь мне никакой ISR или там либо SSG, либо SSR почти не нужен, потому что всё это взаимодействие мне, ну, мне по сути нужно с пользователем работать в рилтайме, чтобы он получал самые свежие данные. Поэтому здесь у меня обычная SPAшка, ну, и обычные клиентские компоненты. Вот кто разбирается во фронтенде, тот знает, тот поймёт. В общем, здесь про рендеринг. А как именно фронтенд, как браузер получает бандлы, а где у нас происходит генерация, как именно даётся HTML, как именно даётся JavaScript. Много достаточно нюансов, это отдельная тема. Но вот для чего у нас происходит разделение на разные поддомены. для того, чтобы либо использовать разные фреймворки, разные типы рендера генерации, чтобы у нас лендинг работал супербыстро, а основное приложение, нам уже такая скорость не нужна, там нужны уже другие моменты, там, ну, нам нужен именно уже JavaScript, весь бандл для того, чтобы хранить состояние и, ну, полноценно работать, да. А также у нас может быть админка вообще, ну, на другом поддомене у нас содержатся именно всё, что касается админа. там нужна какая-то повышенная безопасность, там, допустим, будет отдельная аутентификация. В общем, поэтому мы так и делаем.

Если говорить про обычное, допустим, клиент-серверное взаимодействие, ну, тут совсем просто. Вот у нас есть, допустим, клиент. В роли клиента у нас, на самом деле, может кто угодно выступать. Есть сервер. На самом деле у нас в роли клиента даже сервер может выступать. может выступать браузер, может выступать фронтенд приложение, фронтенд сервер. А здесь сам самый важный — это механизм, то, что клиент отправляет запрос на сервер. Этот запрос может быть, допустим, по HTTP, он может быть по gRPC, да, ну, он может быть через другой какой-то протокол, и он получает ответ. То есть, ну, это и есть клиент-серверное взаимодействие. То есть просто нужно именно сам этот механизм понимать, а какой конкретно там протокол используется, это не особо важно.

Как именно у нас браузер понимает, что отрендерить, на какой поддомен, на какой адрес сходить? На самом деле, здесь у нас помогает нам DNS. Ну, в DNS'е у нас что хранится? У нас хранится комбинация — это имя, то есть название нашего адреса с айпишником. Мы у нас, значит, браузер получает айпишник, куда это всё отправлять. отправляет запрос на наш сервер. То есть запрос у нас ведётся на один айпишник, где находится сервер, Nginx уже решает благодаря хосту, благодаря вот этому названию, куда у нас произвести разделение. Вот. Ну и также вот это приложение, оно через Nginx входит на наш бэкенд. То есть, если у нас есть API в запросе, у нас происходит парсинг, и летят уже запросы на какой-то наш сервер, ну, на бэкенд, по сути, на какой-то микросервис. То есть это вот прямо самая база, как у нас происходит взаимодействие между там браузером, клиентом, фронтендом, а, бэкендом, где что располагается.

Если вы особо не работали с бэкендом, не знаю, писали, допустим, какие-нибудь консольные приложения либо там Unity, другие проекты, вы, наверное, не знаете, из чего, в принципе, бэкенд состоит. Ну, это, во-первых, наше само приложение на любом фреймворке, которым вы пишете, на Go, C#, на Java, это какой-то процесс, который запущен у вас на как на устройстве, условно на сервере. Здесь у нас живёт какая-то логика, то есть обработка запросов. Там, если мы отправили там запрос hello, ну, предположим, у нас endpoint такой `/hello`, то мы должны там получить какой-то ответ, да, там с hello, условно, hello world. И здесь содержится основная логика, то есть здесь мы работаем с данными, здесь мы работаем с бизнес-логикой, что как делать. Здесь у нас различные интеграции с другими там системами, здесь у нас вызов ИИ и так далее. То есть всё у нас на бэкенде. Причём бэкенд может у нас иметь разную структуру. То есть это может быть и монолит, модульный монолит и микросервисы. Об этом поговорим дальше. Вот важно другое, где мы храним данные. То есть важно понимать, что данные мы храним отдельно от бэкенда, отдельно от нашего бэкенд приложения. Вот. Дальше мы разберём механизм stateful и stateless, где как раз-таки этот момент посмотрим. Но важно понимать, что когда мы говорим про backend, мы ещё говорим, что нужно учитывать и уметь работать с базами данных реляционными, там PostgreSQL, допустим, с кэшем. Например, Redis, объектное какое-то хранилище, например, S3, поиск полнотекстовый, например, Elastic Search, либо Typesense, работать с брокерами сообщений — это типа RabbitMQ, Kafka. То есть мы должны со всем этим уметь работать. И по сути это тоже всё бэкенд. Да, конечно, можно сделать просто обычное наше одно приложение, один монолит, ну, и законнектить его там с PostgreSQL. Тогда у нас получится один немасштабируемый особо бэкенд. Но если мы хотим сделать масштабируемую систему, которая будет у нас работать с большой нагрузкой, которая будет выполнять, которая будет расширяемой, вот, и мы делаем реально большой какой-то проект либо работаем над большим проектом, скорее всего, это будет именно микросервисная архитектура, где будет использоваться множество вот этих всех систем.

И отсюда у нас появляется два состояния. Это stateful состояние, то есть можно сказать stateful service либо stateless service. Оно определяет, где у нас живёт состояние и можно ли добавить второй экземпляр. Ну, по сути, если у нас stateful service, второй экземпляр у нас добавить не получится, да? Здесь мы забегаем немножко наперёд на тему масштабирования. Мы про это сейчас поговорим. Это тоже важно, когда мы говорим про бэкенд. Но для этого нам нужно понять, что значит stateful. Stateful — это значит, когда у нас состояние внутри какого-то процесса. Значит, как это работает? Предположим, у нас есть один, там два экземпляра приложения. Зачем нам нужно развёртывать два экземпляра? Ну, чтобы, допустим, большую нагрузку удерживать. То есть у нас один и тот же сервис — это сервис, ну, давайте его назовём Order service, предположим. Вот у нас Order service и Order service, предположим, огромное количество заказов у нас поступает. И если мы будем хранить в памяти этого сервиса, допустим, сессию заказа, что заказ создан, и вдруг у нас сервис там упадёт, ляжет, либо если у нас два экземпляра, то как балансировщик этот запрос распределить сначала в один сервис, потом в другой сервис, то что у нас получится? Мы, допустим, начали заказ. То есть пользователь создал заказ, он там выполнил метод create, у него какая-то сессия создалась, что вот там заказ оформления идёт. И эти данные, предположим, о заказе, вот эти ячейки, там памяти, данные, где у нас там ID заказа хранится и так далее. Это у нас всё хранится прямо в этом микросервисе, прямо в локальной памяти. И вот не дай бог что-то с этим микросервисом произойдёт, там он упадёт. Либо, если у нас опять же есть второй экземпляр и следующий запрос тот же самый юзер отправит уже во второй экземпляр, то этих данных он там не обнаружит, да? Поэтому нам нельзя делать stateful сервисы, либо нам нужно жёстко понимать, что у нас будет только один экземпляр, либо как-то эту ситуацию потом обработать, да? То есть, ну, в целом данные, которые мы хотим хранить независимо от микросервисов, мы храним в базе данных, ну, либо в каких-то внешних хранилищах.

Поэтому у нас есть такое понятие, как stateless сервисы, состояние снаружи. Тут всё хорошо. Один и тот же пользователь отправляет запрос, он через балансировщик распределяется, летит там на какой-то первый микросервис, в первый экземпляр. Состояния здесь никакого нет. И, допустим, вот эти данные о заказе он уже сохраняет в каком-то сторе, в каком-то хранилище, например, в PostgreSQL, в Redis, там, в S3. И если, допустим, тот же юзер отправит снова запрос, связанный с этим заказом, попадёт сюда, то этот экземпляр, второй микросервис, сходит в эту же базу данных и достанет оттуда же эти же данные, которые сохранил первый экземпляр. То есть это очень важная концепция, которую нужно понимать. Конечно, если мы делаем несколько экземпляров, масштабируем нашу систему, там гораздо больше моментов, за которыми надо следить. Там нужно, в принципе, чтобы у нас системы, они троились и выдерживали вот эту распределённую нагрузку, чтобы не было никаких конфликтов. У меня все почти микросервисы, они stateless, они у нас масштабируемые, то есть у меня Education content, то есть все данные о учениках, все данные о образовательном контенте у меня хранится всё в PostgreSQL, то есть одно общее состояние, поэтому мы можем это всё масштабировать. Но у меня есть такой сервис, и у меня он держит SSE соединение. Вот это соединение он держит с браузером, ну, то есть фронтендом по сути. Давайте вот так вот. Фронтенд, он держит соединение постоянное в памяти. И если мы распараллелим этот микросервис, то будут проблемы. Появится несколько соединений и уже балансировки никакой не будет, потому что я использую просто SSE, и там нужно отдельные механизмы для этого писать. Использовать опять же какой-то внешний источник хранения этих соединений, например, тот же Redis.

Итак, если идти глубже именно в архитектуру бэкенда, то здесь можно выделить три модели. Это монолитный, это модульный монолит и микросервисы. То есть три у нас архитектуры самые главные. И первое — монолит. Что значит монолит? Это когда у нас есть одно приложение. Весь код, всё ваше приложение содержится в одном каком-то процессе, в одном месте. Соответственно, у нас, конечно, когда мы делаем какой-то фулстек проект, можно сказать, что это уже не монолит, потому что у нас здесь рядом есть ещё и фронтенд. Но обычно говорят, как вот у нас есть и какая там архитектура, монолитная либо микросервисная. Вот. А фронтенд у нас живёт как бы отдельно, мы его не учитываем. Вот. То есть это имеется в виду здесь бэкенд. Ну и, соответственно, вам нужно уже думать, как в этом бэкенде, а распределять модули вашего большого проекта, как их связывать друг с другом. А как они должны между друг с другом взаимодействовать? Но обычно у нас получается какая-то каша, то, что один модуль взаимодействует с этим, другой модуль взаимодействует с этим, где-то логика перемешивается, всё у нас там одна база данных и, ну, нет никакого разделения. Поэтому мы сейчас пытаемся уходить от монолитной архитектуры. Осо, особенно если у нас какой-то большой проект, то, ну, монолиты часто там превращаются в какую-то несвязанную кашу.

Поэтому на замену обычного монолита сейчас пришёл модульный монолит. Это когда у нас есть тоже одно приложение, то есть у нас тоже одно бэкенд приложение, по сути, один процесс тоже. Но внутри этого приложения мы делим всё на независимые модули. То есть вот эти модули, они все независимы друг от друга. И они не лезут друг к другу. То есть они общаются через контракт, через интерфейс. То есть есть небольшая вот тут вот прослоечка, через которую они взаимодействуют. То есть всё как почти в микросервисной архитектуре. И у нас одна база данных, но в этой базе данных, а, она тоже изолируется не на физическом уровне, а на логическом. То есть модульный монолит, он такой, что мы делаем изоляцию между нашими модулями не на физическом уровне. Физический у нас один процесс, а на логическом уровне. То есть, допустим, у нас есть один модуль orders там, который работает с заказами. Есть второй модуль users, который там работает с юзерами. Есть там третий модуль payments, допустим, который работает с платежами, и они все независимые. То есть они хранят, может, какие-то дубликаты, айдишники, они все там, если нужно в одном модуле получить данные другого модуля, они работают через, ну, прослоечку, через контракт, как я уже сказал. Это почти то же самое. Ну, как бы интерфейс у нас есть, то есть API интерфейс — это в случае, если у нас микросервисная архитектура, а здесь просто контракт внутри приложения, где у нас чётко прописано, хотим мы получить юзеров, должны отправить такую команду, такой запрос и получить юзеров. Вот. Ну и на уровень БД у нас делится тоже на разные схемы. То есть в базе данных мы можем использовать такой механизм, как схема. То есть это некое разделение хранилища. А в каждой схеме у нас могут храниться свои таблички, и они не должны пересекаться. То есть у нас во-первых на каждый модуль своя транзакция. И часто в модульном монолите мы используем какой-нибудь брокер сообщений, допустим, тот тот же самый RabbitMQ для коммуникации между модулями, чтобы события у

нас разлетались между модулями, и они как-то на эти события реагировали. То есть у нас получается такой переходный, переходная архитектура к микросервисам. По сути, у нас все микросервисные шаблоны используются, разные транзакции, разные схемы в базе данных. У нас всё изолировано на логическом уровне, ничего друг от друга не зависит. То есть система она масштабируется, но опять же, мы не можем запустить, допустим, несколько экземпляров одного и того же модульного монолита. Ну, потому что нам нет смысла запускать там, допустим, модуль с Payments несколько раз, потому что он не очень нагруженный.

А если мы хотим вынести какой-то один модуль извне в другой процесс и запустить там несколько раз, у нас получается уже микросервисная архитектура. Это когда мы делим, по сути, модули уже на микросервисы. То есть у нас каждый микросервис — это отдельное end-приложение. У нас есть сервис А, сервис B, сервис C. И каждый из них может работать с одной базой данных. То есть, на самом деле, у нас может быть опять же общая база данных, которая разделена на схемы. А, но или каждый микросервис может иметь свою независимую базу данных и с ней полностью со своим экземпляром работать. То есть здесь у нас есть разные подходы. Ну, точнее, у нас может быть экземпляр именно одна PostgreSQL, но там несколько баз данных, либо у нас может быть одна база данных, но там может быть несколько схем.

В общем, эта архитектура, она довольно сложная. То есть здесь нужно множество знать паттернов, нужно знать, как нам правильно распределить микросервисы. Особенно, если мы это там будем дальше масштабировать с помощью какого-то Kubernetes. У нас здесь будет какой-то ещё балансировщик. Запросы будут распределяться, у нас везде будет несколько инстансов. Система реально усложняется, и нам нужно правильно её проектировать, так чтобы у нас не было потом никаких проблем с масштабированием. Вот.

И как это у меня? На самом деле, я почему выбрал микросервисную архитектуру? Честно, я её выбрал, чтобы ученики могли просто посмотреть примеры микросервисной архитектуры. И я разбираю этот условно проект, показываю, какие у меня есть микросервисы, и специально, так сказать, усложняю свою систему, чтобы просто для обучения показывать более сложные примеры. Но идеальным решением, каким я считаю, когда у нас есть модульный монолит, но какие-то отдельные микросервисы, которые могут повторяться между системами, либо которые имеют прямо большую часть какой-то логики, а отдельной там интеграции, можно выносить в отдельные микросервисы. То есть я всё-таки за комбинацию.

Допустим, у нас может быть несколько микросервисов, и один из этих микросервисов может быть модульным монолитом, где у нас содержится основная бизнес-логика нашего приложения. Например, это работа там с контентом, с учениками, с платежами, там, не знаю, с картами, да, с чем угодно вы работаете. Здесь основная бизнес-логика, и вы не хотите её разносить по нескольким микросервисам. Вы хотите, чтобы оно лежало всё рядышком, и у вас проще работали бизнес-операции. А какие-то отдельные системы по типу, не знаю, файловых систем. Допустим, это File Service, а это может быть Auth Service, который работает с аутентификацией, это может быть какой-то платёжный сервис, Payment Service, это может быть, ну, и так далее. То есть понимаем, да, это какие-то отдельные вот такие сервисы, которые мы можем, во-первых, где-то переиспользовать, которые могут потом сильно нагружаться, ну, и которые, в принципе, независимо в каких они проектах находятся, имеют примерно одну и ту же самую логику. И мы и хотим отделить, потому что они с основным нашим бизнесом, ну, не очень-то и связаны. Вот, в принципе, мне вот такой вот формат больше всего на нравится.

Во-первых, это микросервисная архитектура, я её люблю использовать. Ну, а в-третьих, мы, у нас всё время может быть условно в нашем большом проекте три микросервиса. Один из них огромный модульный монолит и, допустим, File Service и Auth Service. Не буду это писать уже. А, и у нас получается отдельная работа с файлами вынесена в отдельный микросервис. Аутентификация пользователя тоже в отдельной, а вся остальная бизнес-логика в одном модульном монолите.

Окей, сейчас мы переходим ближе уже к коду и как мы код организуем. То есть понятно всё, архитектура, микросервисы, всё это. Как нам код писать-то, по сути? Но для начала, первый шаг — это вы должны выбрать архитектуру. Вы должны с ней хорошо работать. Особенно сегодня, в эпоху, когда у нас большую часть кода пишут нейронки. Нам нужна очень хорошая и качественная организация кода. Поэтому вы используете чистую архитектуру. Она подходит для любого стека. На чём бы вы её писали: Go, JavaScript, Java. А там вы будете использовать концепции из чистой архитектуры, потому что, во-первых, концепции эти все основаны на SOLIDе и OOP. У меня, кстати, на платформе есть открытые уроки, где вы можете посмотреть всю теорию про архитектуру веб-приложений, про архитектуру, чистую архитектуру. И я все эти ссылочки кидал в Telegram-канале. То есть там я иногда пишу всякие прикольчики, которые у меня есть, полезные посты, какие-то бесплатные материалы. Ну, в общем, подпишитесь на Telegram-канал, если вы хотите получать контент, которого здесь нет, но и, в принципе, развиваться как разработчик.

Здесь мы видим стандартное разделение на слои. По сути, что такое чистая архитектура? Это набор правил и принципов, как у нас а должно приложение делиться на слои, как эти слои должны взаимодействовать друг с другом, что в какой папке должно по сути находиться. Итак, вот стандартная модель чистой архитектуры. Здесь мы видим основные слои. Это, можно сказать, от инфраструктуры к ближе идёт к бизнес-реализации. То есть в центре всего у нас содержатся наши бизнес-правила, наши доменные сущности, наш домен. В принципе, как у нас устроена система, описание наших объектов основных. Дальше у нас находятся сценарии, use cases, бизнес-логика. То есть мы отсюда будем через инверсию зависимости работать там с репозиториями, с внешними системами, но опять же через интерфейсы. То есть у нас здесь принцип внеш, принцип инверсии зависимости работает. По сути, мы отсюда не можем напрямую там обратиться к базе данных. Мы не можем, да, у нас потому что принцип только сверху вниз у нас идёт вот такое вот направление. Сверху вниз. То есть у нас endpoint, потом бизнес-логика, там у нас вызывается обращение к базе данных, там используется у нас бизнес-сущности, домены, да, в юзкейсах, например. То есть из юзкейса мы можем обратиться к нашей доменной сущности, но доменная сущность не может знать там, как у нас устроена база данных. То есть у нас вот такое вот распределение есть.

Ну, если нам нужно, допустим, из юзкейса вызвать там логику для работы с базой данных, у нас есть такая штука, как а принципы инверсии зависимости, dependency injection. Здесь у нас содержится какой-то интерфейс, который вызывает уже конкретную реализацию, которая вот здесь вот находится. У нас опять же на моей платформе есть уроки, где я разбираю чистую архитектуру. И ссылку на эти уроки я скину в Telegram-канале.

Но помимо чистой архитектуры есть ещё и другой принцип, который мне доволен достаточно сильно нравится. Это Vertical Slice Design. Это когда у нас одна фича делится как бы на слайс. И всё приложение по сути будет представлять из себя слайсы, да? Слайсы, то есть поделены на фичи. То есть мы всё наше приложение делим на фичи. Я её использую во всех моих проектах. Допустим, когда у меня есть фича там по созданию какого-то юзера или у меня есть фича там по бизнес-операции, где мне нужно там обновить данные у юзера, где мне нужно там его задание проверить, сделать какое-то ревью. Ну, любая вообще фича у меня есть, допустим, и она проходит через все слои. Ну, и она является как бы одним целым. То есть у меня идёт endpoint. Endpoint вызывает фичу. То есть у меня вообще всё это может быть в одном файле, на самом деле, потому что, да, чистая архитектура говорит нам, как делить на слои, как это всё должно выглядеть логически, но как делить это по папкам, вы можете и сами решать. То есть вы вообще можете в одном файле сделать, по сути, чистую архитектуру, что у вас там один класс вызывает другой через интерфейс, у вас всё сохраняются, все принципы. И, в принципе, не обязательно даже всё на разные проекты делить. То есть, ну, всё зависит от проекта. Здесь, ну, достаточно много нюансов.

Если говорить про .NET, как именно это у меня устроено, вот общая общая концепция. У меня есть, значит, проект, где у меня настраивается host DI.NET. У меня есть логика, где то есть обработка запросов, логика, вызов репозиториев через интерфейсы. Всё это у меня содержится здесь. И здесь у меня всё поделено на feature slice. То есть у меня одна фича — это какой-то один большой файл, где у меня есть и бизнес-логика, и валидация, и интерфейс, и доменная моделька, и DTOшка, и всё это здесь, едино. Это очень удобно, легко всё это тестировать и так далее. То есть у меня Core, ну, именно на физическом уровне у меня Core объединяет адаптеры, сценарии, вот так. Нет, точнее, домен у меня отдельно есть, да? А вот эти два слоя, они у меня объединяются в один, в один Core, в ядро, так сказать, ядро наших фич. Как я уже сказал, домен у меня отдельный проект. И под каждую инфраструктуру, то есть под каждую какую-то внешнюю зависимость, у меня тоже есть отдельный проект. Например, под PostgreSQL. Тут у меня может быть дальше Redis. Также у меня может быть TypeSense для полнотекстового поиска. Ну, и так далее. То есть все вот эти внешней зависимости я создаю отдельный проект, где пишу, как именно моей системе с этим отдельным проектом работать. Здесь я уже через интерфейсы это всё вызываю.

Также у меня есть слой с контрактами, где уже другие микросервисы могут взаимодействовать э через эти контракты с моими микросервисами. То есть это слой, где у меня определены DTOшки, где у меня определены реквесты, где у меня определены, ну, в принципе, контракты, модельки, через которые мы взаимодействуем с нашими бэкэнд-сервисами. Ну, и, конечно, отдельная секция — это тесты. У меня есть отдельный проект, где у меня есть и интеграционные тесты, которые в контейнерах, в Docker контейнерах поднимают весь стек, связанный там с PostgreSQL, с Redis. Все внешние зависимости есть у меня, ну, у меня прямо интеграционными тестами очень много покрыто. Каждая фича, все кейсы учтены. И есть также unit-тесты, также есть end-to-end, но это опять же отдельная большая тема. И сейчас очень, ну, также у меня есть, кстати, ещё архитектурные тесты, тесты на контракты и так далее, тесты на типы. Короче, у меня огромное количество тестов, потому что у меня в моём проекте большую часть кода пишет именно ИИшка, но я ей управляю как архитектор. И мне обязательно очень важно покрывать каждый кусок моего кода тестами, чтобы гарантировать, что ИИшка написала правильный код. И бизнес-логика не нарушается.

Кстати, если вы хотите видеть больше всяких практик с ИИшками, практик с разработками, обязательно подпишитесь на Telegram-канал. Например, всю эту доску мне сделала ИИшка. Я наконец-таки победил вот эту вот связку и с Excalidraw интегрировал нормальную. Построил целый свой движок, который теперь может вот такие вот схемки подробные генерировать. Конечно, не без доработок, не без нюансов, но эта система становится всё лучше и лучше.

Похожим образом у меня устроен фронтенд, только там у меня не чистая архитектура, у меня там Feature-Sliced Design, типа feature, но по сути концепция там такая же. Мы всё делим на слои, зависимости идут сверху вниз. В самом низу у нас какая-то основная логика, которую все могут использовать. Там у нас бизнес-сущности, по сути, аналог домена. Здесь у нас аналог коры. Здесь у нас все фичи. Здесь у нас виджеты, которые уже объединяют, могут использовать эти фичи. Ну, аналог фреймворка, ну, точнее, вот это, наверное, аналог фреймворка из бэкэнда. Здесь у нас разные провайдеры, роутинг и настройка всего. То есть у меня на фронте буквально тот же принцип: feature slice design плюс чистая архитектура равно FSD. То есть, если вы не знаете, советую изучить эту архитектуру и делать фронтенд-проекты именно на этой архитектуре.

И теперь давайте вернёмся именно к более абстрактной архитектуре, то есть поговорим про масштабирование. Допустим, мы видим, что уже сервис не вывозит, у нас очень большая нагрузка, у нас в end какой-то экземпляр начинает провисать, проседать, слишком огромная нагрузка идёт, там слишком большая обработка данных. И первый способ, как решить эту проблему — это вертикальное масштабирование. Это когда мы берём и просто повышаем мощности нашего сервера, то есть добавляем просто ядер, добавляем аперативы. Но все мы знаем, что есть лимиты, оперативка дорогая, всё дорогое и потом дорожает всё ещё больше. Вот.

И тут у нас вступает в роли горизонтальное масштабирование, когда у нас есть несколько сервисов, когда у нас есть на каждом из этих сервисов, то есть на каждом сервере у нас может быть, во-первых, масштабирование на уровне серверов, то есть быть несколько серверов. В каждом из этих сервере у нас может быть свой несколько экземпляров одного и того же микросервиса, и нагрузка будет распределяться там с помощью какого-то балансировщика. И, конечно, чтобы строить все такие вот распределённые системы, а сложность очень сильно усложняется, когда мы строим именно распределённые системы, там уже не обойтись одним там Docker Compose, Docker Compose или типа того. Там выступает роль Kubernetes. Это следующий уровень. На него я буду дальше переходить, когда мне уже надоест работать с Docker Compose'ом и вот этими всеми микросервисами. То есть у меня уже довольно их много, поэтому уже пора пора переходить на Kubernetes. Это довольно, ну, не очень лёгкий процесс, но я обязательно перейду и вам об этом расскажу.

Итак, в чём суть горизонтального масштабирования? Это когда мы делаем несколько экземпляров одного и того же stateless сервиса, который не хранит локальные данные. Они делят общее какое-то состояние и какой-то запрос юзеру. Допустим, у нас летит куча-куча запросов, да, от нашего браузера на наш бэкэнд, на наш сервис. И балансировщик распределяет эти запросы между экземплярами. И получается, что здесь у нас там нагрузка 30%, здесь у нас нагрузка 30% и здесь нагрузка 30%. Куда делось 10%, я не знаю, это неважно. Но вот такая у нас, допустим, такое у нас получается распределение. И вместо того, чтобы нагружать там один микросервис [откашливается] там на 110%, у нас вот так вот распределяется нагрузка. С математика у меня, видимо, не очень хорошо, но концепцию, я думаю, вы поняли.

И, конечно, в горизонтальном масштабировании куда больше нюансов, чем здесь нарисовано. А надо, надо, во-первых, следить, чтобы каждый микросервис был готов к тому, чтобы его масштабировали, чтобы он не хранил какое-то общее состояние, чтобы они могли работать там с транзакциями правильно. Мы можем ещё и базу данных, кстати говоря, реплицировать, масштабировать. И, короче, очень это всё сложно, большое количество паттернов. Нужно каждую из этих тем масштабирования разбирать отдельно на каких-то конкретных кейсах. Вот так вот абстрактно. Это максимум, наверное, что я могу рассказать.

Сейчас опять спустимся на уровень пониже. Это коммуникация между микросервисами. И я думаю, что есть всего два типа коммуникации — это синхронная и асинхронная. Зачем вам вообще нужна коммуникация между микросервисами? Ну, вот пример. У меня есть два микросервиса. Первый — это Education Content Service, который хранит данные там о материалах, которые есть на платформе. И есть, предположим, Access Service, который хранит данные о том, доступно ли какому-то юзеру э какой-то курс, доступен ли он ему, может ли он получить эти данные. И когда фронтенд хочет эти данные отобразить, он отправляет запрос в Education Content Service, типа: "Дай мне мои курсы". И он ему отправляет ответ, чтобы вот этот юзер понял, имеет ли он доступ к этим юзерам. Бэкэнд должен сделать запрос в Access Service и сказать: "Слушай, а проверь, вот у этого юзера есть доступ к этому курсу". Этот Access Service уже идёт куда-нибудь там в базу данных, ну, и проверяет эти данные, значит, и возвращает ему ответ. Здесь вот этот микросервис этот ответ мапит в какой-то больший ответ, да? И уже вот эти все данные с информацией, где у меня есть, допустим, user и курс. Вот. То есть мы информацию о курсе добыли здесь, а информацию о юзере добыли здесь. И вот этот ответ, он соединяет этот микросервис и отдаёт фронтенду. А фронтенд уже просто говорит: "Вот у тебя есть доступ к такому курсу, ты можешь ты можешь его получить и можешь узнать о нём всю инфу".

Это у нас синхронная коммуникация, и она возможна через HTTP, она возможна через gRPC, она возможна через там RabbitMQ. Также там можно тоже синхронные запросы отправлять. Есть несколько протоколов. Используем тоже в зависимости от того, что требуется. Но если вдруг нам нужно, а, выполнить, допустим, несколько операций подряд в разные микросервисы, и причём операций может быть очень много, то есть каждый микросервис может выполнять какую-то свою единицу логики. Если у нас вдруг на каком-то этапе прервётся какой-то запрос, а запрос может выполняться долго, не знаю, там несколько секунд, 10 секунд, у нас может быть какая-то тяжёлая операция, то, э, браузеру придётся ждать опять, то есть отправлять заново все запросы и ждать, пока каждый из этих запрос вернёт ответ, каждый из этих запросов обработается. И только когда вся цепочка вот этих синхронных запросов будет успешна, мы получим успех.

Но есть такая штука, как асинхронное взаимодействие. Это когда браузеру не нужно ждать ответа от каждого сервиса. Он может заниматься своими делами, а всё остальное произойдёт в фоне. Вот для этого нам нужны брокеры сообщений. И для этого нам нужно событийно-ориентированный подход. Кстати, всё, о чём я сейчас рассказываю, это все вот эти темы. Вам нужно их знать, да? Сегодня очень большой порог кода для новичка. И, э, поэтому вот внимательно слушайте о том, что я говорю, внимательно слушайте о том, что я рассказываю. Это спрашивают на собеседованиях. Это все темы, которые вам нужно сегодня знать, чтобы работать джуниор разработчиком. К сожалению, тем количество огромное. И даже то, что сейчас ИИшки справляются с написанием какого-то кода, всё равно, а, все эти системы проектировать. Всё это нужно делать вам. И очень много важных, критически важных аспектов вам нужно держать под контролем, чтобы не понести какие-то ущербы. Вам нужно в этом всём разбираться. И если вы .NET разработчик, то вы можете перейти на мою платформу и посмотреть, что я вам предлагаю. У меня есть курс по .NET разработке. Ну, и также скоро я запущу отдельное направление, где я буду как раз-таки обучать не только .NET разработчиков, а мы будем очень подробно говорить и про архитектуру, и про ИИшки, и всё остальное. Но это в будущем. Если хотите следить, то опять же подпишитесь на Telegram-канал.

Итак, какой принцип асинхронности? Это когда у нас какой-то сервис публикует какое-то событие о том, что он что-то сделал. Допустим, Progress Service там засчитал задание, выполненное у какого-то ученика. Мы это событие публикуем в RabbitMQ. Кстати, видео про RabbitMQ у меня есть на канале. И RabbitMQ потом отправляет это событие каким-то получателям, другим микросервисам. Например, он отправляет микросервису, который делает автоматическое ревью. Плюс он отправляет микросервису Notification, которое там оповещает пользователя, что этот, а, юзер там задание выполнил. Ну, админа, например, он может оповестить. Этот микросервис, который проверяет задания с помощью искусственного интеллекта, он делает это в фоне. Это занимает какое-то время. Этот микросервис одновременно уже событие, ну, уже, то есть, уведомление отправил. Ну, и получается, Progress Service не обязан ждать всё это время, пока у нас вот этот весь цикл работы пройдёт. Он сразу оповещает пользователя, допустим, а, всё, задание принято, просто ожидай, типа, и пользователь просто ждёт, своим своими делами занимается, по платформе ходит, лазит, уже следующий урок смотрит, а его задание проверяется в этот момент.

Я использую на платформе и синхронную коммуникацию, и асинхронную коммуникацию. И всё это у меня ещё надёжно, потому что я использую Outbox паттерн для того, чтобы у меня не терялись эти события. Опять же, вот эта тема событий, тема брокеров, она очень большая. Множество паттернов нужно учитывать. Посмотрите мой ролик про RabbitMQ. Там я говорю про это подробней.

Ещё одна важнейшая тема, а, которая сегодня есть везде, которую и спрашивают на собеседованиях, с которой вы столкнётесь, если будете разрабатывать любой свой проект — это как вам работать с юзерами, как сделать аутентификацию, как хранить сессии, где вот эти все токены, JWT токены, где всё хранится, какие identity провайдеры использовать, писать код самому, не самому. Короче, огромное количество тем. У меня даже есть планируется отдельный интенсив, то есть я его почти записал, а потому что тема монструозная, очень много вопросов там по безопасности, по тому, как что хранить, какие протоколы реализовывать. Но как это устроено у меня? Давайте быстренько пройдёмся.

Значит, есть браузер, а есть фронтенд на Next.js. У меня есть отдельный сервис аутентификации и сессий. Этот микросервис у меня, по сути, выступает в роли провайдера. То есть в нём реализован протокол OpenID Connect, в нём реализован протокол OAuth, он генерирует токены, то есть он отвечает за генерацию JWT токенов, то есть это токен для доступа, это токен для OpenID Connect протокола, он отвечает за авторизацию, он хранит там все роли, вот это всё он мапит. О, короче, он за много чего отвечает. И он работает ещё с Redis, чтобы хранить входы. У меня вход так как через почту. Поэтому когда мы входим через почту, генерируется какой-то код. И это мы всё храним в Redis. Он живёт там 5 минуточек. Доступ очень быстрый. То есть вы видите, что мы можем использовать ещё внешнее хранилище, там такое как Redis для, допустим, хранения каких-то временных кодов или ещё что-то.

Значит, у меня, а, так как микросервисная архитектура, поэтому я использую JWT. И у меня JWT токены в каждом с каждым запросом. То есть клиент обращается сначала к сервису, проходит весь цикл протоколов, получает токены. Это JWT токен, access token и ID. И уже с JWT токеном он идёт в другие микросервисы. Они этот токен проверяют, так сказать. А если успех, то мы пользователя пропускаем. И также у меня ещё проверка прав, но это уже относится не к аутентификации, это уже, наверное, больше к resource-based authorization, потому что здесь у меня хранятся уже доступы там, какой юзер к какому курсу имеет доступ.

То есть база, которую вам нужно знать — это, ну, для того, чтобы сделать нормальную аутентификацию и авторизацию. А, во-первых, вам нужно понять, что такое отдельный сервис авторизации, как работает генерация JWT токенов, какие есть протоколы, как безопасно хранить там юзеров, пароли, все эти данные. Ну, либо можете использовать просто отдельный identity провайдер, а, интегрировать его. Например, это может быть Clerk, это может быть популярный Keycloak, это может быть Auth0. Их есть несколько. По сути, я написал свой, но вы можете использовать уже готовый, где всё это уже будет реализовано. Вам лишь нужно интеграцию для этого написать. В общем, тема огромная. Здесь можно достаточно много про неё говорить. Вам нужно ещё понять, как работают сессии.

То есть, если прямо про базу, то вот у нас есть фронт, он хочет получить доступ к какому-то какому-то сервису, и этот сервис должен проверить, что пользователь вошёл в этот фронтенд. То есть у нас есть user, как нам нужно подтвердить вход. Юзер должен ввести email и пароль, правильно? Ну, чаще всего. Либо там по коду пройти. И когда он эти данные вводит, нам нужно где-то сохранить сессию о том, что он вошёл. Эту сессию мы можем сохранить либо в нашем как раз-таки сервере Auth, то есть где-то здесь в базе данных сохранить эту сессию, либо это может быть сессия, ну, неважно, либо это может быть JWT, да. В общем-то говоря, мы выполнили вход. У нас браузер хранит вот эту информацию об этом юзере. Давайте её пометим как вот такой вот замочек. И вот этот замочек дальше он на каждый наш сервис, сервер, неважно, он отправляет вот этот замочек, то есть вот эти там токены либо сессию, он эту информацию отправляет, и сервер уже проверяет, замочек сходится, не сходится. То есть, условно, проверка прошла или не прошла, и может ответить этому юзеру, а, вернуть какие-то данные, к которым он имеет доступ. Вот это прямо мегабаза таким абстрактным языком. Уделите этой теме особое внимание, потому что, во-первых, она спрашивается на собеседованиях. Во-вторых, если вы делаете свои проекты, вам нужно сделать это всё достаточно безопасно. Если вы работаете в продакшене, скорее всего, там это всё уже будет реализовано, но вам нужно в этом всём всё равно разбираться.

Итак, файловое хранилище. Как нам работать с файлами? Здесь возникает вопрос, где эти файлы хранить, а что использовать для работы с файлами? Что, если файлы слишком большие? Что если у нас файлы файлы разного формата? Как их доставлять на фронтенд? как их отображать, да, это файлы встречаются во всех проектах. Это и превью, и видео, ну, и всё, что с этим вообще связано. Итак, сразу скажу, что

В Позра SQL, в нашей реалиционной базе данных, мы файлы не храним. То есть здесь файлов у нас ни в коем случае не будет, и их там не должно быть. Здесь у нас файлов нет.

Вместо этого мы позросле будем хранить метаданные. Что это значит? Это, возможно, размер файла, где он находится, когда он, не знаю, загружен, название файла, какое расширение, какой формат. То есть здесь мы храним всю информацию о файле, но сам файл мы будем хранить в S3 хранилище.

Это объектное хранилище, которое специально сделано под такой формат, где мы можем хранить большое количество разных объектов разного размера, и оно специально под это спроектировано. Это протокол, который сделал Amazon. И если вы в своём бэкэнд проекте реализуете работу с этим протоколом, вы дальше можете использовать любое совместимое S3 хранилище. Это может быть Яндекс Cloud, это может быть какой-то там Select Cloud, это может быть ваше локальное S3 хранилище, которое вы развернёте на сервере и так далее. Соответственно, мы файлы храним именно там специально. Вот это запомните: S3 хранилище. Если вы не знаете, вам стоит изучить с ним работу.

Что касается насчёт видео, конечно, мы можем хранить видео также в нашем облачном хранилище, но видео — это не только их хранение, это ещё обработка видео, это ещё хранение в разном формате. Это ещё специальный протокол, который позволяет нам из S3 получать большое видео чанками, маленькими кусочками, смотреть видео в разных форматах, в разных разрешениях, с разной скоростью. И поэтому, на самом деле, когда видео загружается, ещё происходит процесс обработки этого видео. И всё это реализовывать вручную довольно муторно. Поэтому есть сервисы, которые уже это всё реализовали. И один из них — это Kinescope. В нём есть встроенный CDN. Это такая система, которая позволит доставлять контент, доставлять видео более быстро, да, потому что она развёрнута на разных серверах и выбирается самый быстрый, самый, ну, который боле рядом находится и позволяет там хранить как бы копии этого контента. Ну, в общем, это отдельный протокол, который позволит вам быстрее получать доступ к данным. У Кинескопа есть свой плеер, вы его можете как угодно кастомизировать и настраивать. Это внешний сервис, который тоже интегрируется, но по сути протоколы там работают те же самые.

В общем, как работает именно механизм? Зачем нам вообще вот нужен отдельный микросервис по работе с файлами, если по сути за хранение файлов будет отвечать за хранение видео Kinoscope, а за хранение всех остальных файлов будет отвечать наше стрих хранилище? Ну, во-первых, потому что нам нужен отдельный микросервис для того, чтобы, во-первых, хранить метаданные. То есть у нас всё равно в базе данных там будет условно какой-то объект, какая-то табличка, там files либо media, где мы будем хранить связи, какой файл принадлежит к какому объекту. То есть у нас есть, допустим, урок lesson, и урока должен быть файл, какого он должен быть формата, видео, не видео, соответственно. Поэтому нам нужно это тоже решать. Вся эта логика будет храниться здесь. И здесь же будут интегрированы, а интеграции с внешними провайдерами, с Кинесскопом, с S3.

И как нам, ну, как вообще клиент, браузер должен эти файлы все отображать? Потому что, если мы будем эти файлы отображать, гнать их все через файлсервис, условно, вот у нас, допустим, в S3 хранится файл, а у нас, допустим, браузер идёт сначала файл севис говорит: "Ну вот, дай мне какой-то файлик". Он фай севис идёт там, допустим, в S3, типа, забирает этот файл, все эти байты, всё это, весь большой размер этого файла отправляется назад, через этот сервис перегоняется и отправляется обратно нашему клиенту. Это будет, э, нерационально. Слишком большая нагрузка и нет всяких оптимизирующих процессов. Поэтому мы так ни в коем случае не делаем.

Как мы делаем иначе? У S3 есть такой механизм, который называется presigned URL. Это ссылки, которые генерируют S3 хранилище на то, чтобы получить файл. То есть, условно, вы идёте в S3, говорите: "Сгенерируй мне ссылку, чтобы достать какой-то конкретный файл, который здесь внутри лежит". Ну, какой-то конкретный объект. Он эту ссылку генерирует. Эту ссылку у нас получает наш файлсервис. И эту ссылку мы отдаём браузеру. И дальше по этой ссылке мы пойдём в наш через engin? Ну, мы таким образом прячем нашестрихнилище за engin и всё с ним взаимодействие делаем через engin. Во-первых, это повышает безопасность, стандартный формат. Здесь мы можем настроить какое-то кэширование, и мы не даём прямого да доступа к нашего клиента. То есть прямой доступ мы не даём к S3, мы его прячем за Enin, то есть у нас даже клиент не будет знать точного адреса, как до этого S3 дойти, условно. И вот идёт с этой ссылкой. То есть это какая-то там ссылка большая, HTTP, потом какой-то там адрес, куча там файл, какие-то там потом айдишники, какие-то параметры. Вот это, короче, большая ссылка. Мы по ней летим в S3 и напрямую получаем доступ к файлу. То есть мы его не загружаем, а не не перегоняем там байты, мы напрямую просто получаем к нему доступ. То есть у нас есть компоненты на фронтде, мы туда вставляем src и у нас сразу файлик. Просто мы его запрашиваем напрямую из S3.

Что касается загрузки, то мы опять же большие файлы не загружаем напрямую целиком. Мы разбиваем эту загрузку на чанке. И для этого у нас есть разные механизмы, которые позволяют нам эстроить мультипарт загрузку чанковую. Есть специальные протоколы, которые позволяют вам файлик загружать чанками, потом продолжать загрузку, да, если нужно. То есть я вам советую это тоже изучить. По сути, это просто мультипарт загрузка.

Ну и в целом, на самом деле, работа с файлами во многих проектах она примерно одинаковая. Поэтому мы можем взять наш файлсервис и, допустим, сделать его универсальным, а потом просто дорабатывать для каждого проекта. То есть у нас получится такой универсальный сервис, который мы можем подключить к любому проекту на любом стеке, опять же, да, мы можем всё это мы можем реализовать на любом стеке. Неважно, Go, Java, C#P, все механизмы одинаковые, которые я перечисляю.

Итак, ни одно нормальное бэкэнд приложение производительное, оно не будет обходиться без кэширования. Причём кэширование оно есть на уровне не только бэкэнда. У вас кэширование начинается с уровня фронтда, потому что фронтEN у вас кэширует запросы. В браузере тоже есть определённое кэширование. И получается, что у нас кэширование существует на разных слоях. То есть это и в браузере, и в фронтде, и Джинксе, и бэкэнди, и в бэкэнде, и фреймворке, и базе данных, и отдельные слои кэша, и локальный кэш в ваших бэкэнд-сервисах. Короче, везде. Опять же, на переписочку .net не обращайте внимания, заменяется всё на любой стек.

В общем, самая популярная концепция кэширования — это кэш и сайт. Для чего кэширование вообще может использоваться? Что такое кэш? Это какое-то временное хранилище, где вы храните а данные, к которым хотите получить быстрый доступ. И кэширование подразумевает, что у вас есть оригинальный источник данных. Допустим, есть Погрс SQL. Это первый источник, где у вас хранятся такие вот прямо надёжные данные, которые вы там храните, которые, если вы сходите позгры в Scale, вы получите всегда актуальные данные. А кэш — это копия этих данных. Для чего эта копия делается? Во-первых, чтобы снизить нагрузку с базы данных, потому что к базе данных постоянно много кто идёт, выполняет там запросы сложные и так далее. То есть здесь выполняются SQL запросы, которые нагружают нашу базу данных. И мы хотим это всё делоровать, чтобы каждый раз этот запрос не выполнять, чтобы можно было его обойти и сразу получить доступ к данным.

Как это работает? Вот у нас есть ээнд. У нас, допустим, поступает запрос: "Получи мне список юзеров". Сначала мы идём в кэш. В роли кэша у нас может выступать, предположим, RISС. Это очень популярная база данных для кэширования, потому что там есть формат ключ-значение. Он очень быстрый, а он работает с оперативной памятью. Он также дублирует данные на жёсткий диск. Также можно использовать локальный кэш, то есть ваш бэкэнд может хранить локальный кэш, но это не распределённый кэш. И получается, каждый экземпляр будет хранить именно у себя в памяти. Если у вас что-то случится с тотсервисом, допустим, он помрёт, то у вас этот кэш пропадёт. То есть это нужно всегда учитывать. После перезапуска микросервиса тоже кэш пропадёт. Если вы пользуетесь микросервисной архитектурой и у вас общий кэш между разными экземплярами, то обязательно используем именно rс.

Итак, принцип кэша сайта какой? Мы, допустим, делаем запрос на получение списка юзера. Мы сначала идём проверять кэш. Если в закашированы у нас эти данные, то есть если в кэше у нас содержится по такому-то ключу, допустим, там по ключу users и какой-то там запрос, допустим, там limit 100, там оset какой-нибудь, ну какие-то фильтры указаны. И если по этим фильтрам у нас содержится список наших юзеров, мы его возвращаем сразу же. То есть сразу происходит возврат, и пользователь быстро получает ответ. Если у нас кэше пусто по этому ключу, у нас получается промах. Мы идём в базу данных. Через базу данных мы эти данные получаем и записываем эти данные в кэш. То есть мы их в кэше фиксируем. Теперь в кэше у нас содержится usюer лимит и другой юзер, который там у нас 1.000 юзеров, и получается, второй юзер уже получит данные быстро из кэша. Если вы используете гибридный кэш, а я использую гибридный кэш, то я ещё и сохраняю эти данные в локальном кэше, чтобы мы сначала первым шагом пошли в локальный кэш. Если там этих данных нет, то мы потом уже проверили Ries. Вот. Потому что локальный, конечно, ещё быстрее, чем дис. Ну ладно, предположим, вы используете один уровень, конечно. Всё, вы данные сохранили, значит, попадание, и вы возвращаете эти данные, а [фыркает] в ваш ээнд и дальше уже возвращаете эти данные, ну, браузеру, кто сделал исходный запрос. Это именно концепция cash assй. Есть ещё множество других концепций, когда вы ходите там только в кэш, когда вы ходите там другим способом. То есть у вас просто меняется именно формат, кто когда, в какой момент ходит в кэш. Но по сути концепция она примерно всегда такая.

Кэш. Он не ограничивается тем, что мы фиксируем какие-то данные для того, чтобы быстро к ним получить доступ, именно только для запросов. Во-первых, мы можем Редис использовать как стриминг, то есть мы можем как брокер его использовать, чтобы использовать его Pubs механизм, но чаще всего всё-таки мы его используем, чтобы быстро получить доступ к данным. Например, я использую дис, чтобы хранить там проверку доступов. То есть у меня в рейдисе хранятся для каждого юзера хранится доступы, которые у него есть, чтобы каждый раз не ходить базу данных, не выполнять SQL, какие-то скрипты сложные. У меня всё хранится там. И я сначала иду в readyс. И чаще всего у меня там эти данные будут. Они изменяются довольно редко. Также я редис использую для рейдлимитинга. Это ограничитель, чтобы не спамили на какие-то определённые поинты. Так как система распределённая и может быть несколько экземпляров, сервис может падать, то мне нужно, чтобы я использовал какое-то внешнее хранилище для того, чтобы я условно считал, сколько запросов на какой point прилетает. Коды входа, как я уже говорил, для аутентификации, для отправки сообщений по почте я там использую. Ну потому что удобно, что а кэш у нас хранится не всегда, какое-то время. Я могу точно выставить, что вот там 5 минут хранить эти данные и быстро доступ к этим кодам получать. Ну и понятно, отдельные ответы других сервисов. То есть я, когда у меня HTTP коммуникация между микросервисами происходит, я тоже это всё кэширую.

Но очень важно понимать, что вам нужно, если вы используете кэш, создать механизм инвалидации, то есть обновлять этот кэш. Потому что в кэше данные устаревают. Если вы не будете обновлять кэш, как вы обновляете базу данных, то у вас кэш устареет, и вы будете получать данные неактуальные. Предположим, вы сначала сделаете сделали запрос на получение там usер инфоы. Какой-то челик потом взял этого юзера, обновил, и юзер теперь его зовут там не Максим, а его зовут условно Кирилл, да? И потом другой пользователь сделал запрос, а он видит старый никнейм Кирилл, потому что мы не обновили данные в КШ. Вот как данные можно в кше обновлять? Либо через события, либо ставить по времени, то есть истече истекать запись будет по времени, либо по событию. Допустим, если юзер изменился, то в систему прилетает какое-то событие в RabitQ, и кэш на это реагирует и обновляет эту запись конкретную, либо просто сбрасывает кэш.

Также ещё нужно понимать, что мы можем потерять кэш целиком. Ничего страшного не будет, если у нас кэш вообще удалится полностью, данные потеряются, потому что, да, база данных нагрузится сильнее, и у нас будет нагружаться база данных, но кэш со временем заполнится. Ещё есть такой механизм, как прогрев кэша — это когда у нас может полететь в систему такое огромное количество запросов, что база данных не выдержит. И нам нужно, чтобы к этому времени кэш был уже наполнен, да? Нам нужно поэтому своевременно побеспокоиться, как нам наполнить этот кэш. То есть ещё множество может быть проблем с кэшем. Это и конкуренция кэшу, это и затирание кэша, это и бесконечные циклы, петли, которые мы можем попадать из-за того, что не успевает кэш обновиться. Короче, тоже, да. Вот тема огромная по кашированию, надо её тоже отдельно изучать.

Итак, наблюдаемость. Зачем она нам нужна? Ну, давайте представим ситуацию, что у вас легли все микросервисы. Как вы об этом узнаете? Ну, вам, наверное, начнут спамить юзеры, что что-то не так, сайт не откроется. Либо какая-то другая ситуация, например, случился какой-то баг. А у вас есть определённая жалоба, что-то не работает, либо вы это сами заподозрили, и вы тоже хотите разобраться, что не так. Либо вы понимаете, что у вас-то баги есть, что-то, ну, не так работает, что-то медленно загружается, но вам негде это посмотреть. Вам негде посмотреть логи, трассировки, метрики, сколько занимает времени определённый запрос, где у вас чаще всего случаются баги. Ну, для этого всего вам и нужна наблюдаемость. То есть мониторинг вам нужно обязательно настроить, особенно если это продакшн проект. Да, да, да. И даже во время разработки вам нужно просто понимать, следить за логами, следить за трассировками, чтобы понимать, что куда летит. Вот поэтому мы сейчас я вам расскажу, в принципе, из чего это всё состоит и зачем всё это нужно настраивать. Поэтому мы внедряем обязательно мониторинг в наш продакшн. Ну и, в принципе, в наш весь этот стек. Всё это вместе запускаем.

Из чего он состоит? Вот у нас есть определённое приложение, а оно должно писать куда-то логи. То есть мы вот эти сигналы, логи, метрики, трассировки, вот эти все три составляющие мониторинга, obserсербиility, чтобы настроить наблюдаемость полноценную, мы направляем в какую-то точку. Ну это может быть несколько разных точек, это могут быть какие-то энпоинты, всё это на разных, скорее всего, протоколах работает, либо объединяется одним, ниже расскажу каким. А летит это всё. И есть некие сборщики, то есть какие-то агенты, не и агенты, а как раз-таки сборщики. Которые просто, ну, сервисы определённые, они ходят и собирают все эти данные. Потом эти данные, они распределяются, то есть где-то фиксируются, у вас сохраняются метрики в вашей системе, где-то фиксируются, сохраняются логи, где-то трейсы. То есть метрики. Что это такое? Это что, когда случилось, сколько времени, какой запрос занимает, чтобы вы могли отслеживать какие-то временные промежутки и производительность вашей системы. Логи — это просто сообщение, которое вы сами пишите во время работы вашего приложения о том, какой запрос базу данных полетел, а какое действие ваша система сделала, какие данные там у вас обработались. То есть вы просто логируете всё подряд. Ошибки вы обязательно все логируете. То есть когда вы что-то разрабатываете, у вас ошибка в консоли, ой, там такой-то такой-то эпtion, что-то пошло не так, всё это тоже вы обязательно логируете. Ну и трассировки — это откуда, что куда полетело. Допустим, из фронт-энда в ээкэнд, потом там вс, потом снова в ээкэнд, потом в реbb, потом туда. Сколько времени какой запрос занимал. Ну, это с метриками синхронизируется. В общем, трассировки — это они нужны для того, чтобы посмотреть весь путь запросов, что, куда и когда полетело. Ну и дэшборды — это система, где вы можете всё это анализировать.

И вот ниже конкретный стек, как это работает на примере. Я использую DNET микросервиса, опять же, на C#P, но неважно какой. Используйте Staк, Python, Go, Java, везде это примерно всё одинаковое. Везде вы будете использовать Prometeus для хранения, обработки метрик. Везде вы будете использовать. И, скорее всего, будете использовать там либо tempo, либо Jer. Собственно, — это хранилище для логов, — это хранилище для трассировок, Промете, хранилище для метрик. И вы подключаете графану. Это место, где есть интерфейс, где вы можете подключить все эти хранилища и удобно сделать себе дэшборды, таблички, всё это, ну, удобно просматривать. Плюс настроить систему алёртинга и систему, а, чтобы у вас создавались инциденты и приходили оповещения, например, там, в Telegram, Select, Confluence, на почту, ну, вообще, везде, да, где только можно. Ну, и также с этой системой вы можете интегрировать агентов. Во-первых, у графана есть MCP, которая интегрируется с агентом, и вы можете, ну, попросить агента, допустим, изучить какой-то сценарий. Вы можете попросить агента изучить, в принципе, инцидент, что произошло не так. Он обязательно все данные соберёт, потому что они все хранятся в стандартизированном виде. Он всё это может заскрапить, изучить, что не так и сказать вам, что произошло не так. Либо вы можете настроить автоматизацию, условно настроить пайплайн, что если прилетает бак, либо случается инцидент, либо у вас агентом триггерится по какому-то, по какой-то ситуации, он идёт разбираться, что случилось не так. Он заводит, допустим, отдельный баг, отдельный инцидент, там тикет он условно заводит. Можно сделать так, чтобы он дальше другому агенту отправлял это в работу. Он эту работу выполнял, делал фикс этого инцидента. Фикс у нас проводился. И потом мы это всё ревьюили уже проверяли и оценивали, стоит ли это вливать. Ну и стоило ли это вообще тех усилий. Ну либо можно на каком-то шагу, естественно, человека подключать. То есть всё это легко благодаря наблюдаемости, obserсербиility, а тестам и прогонам, мы можем ещё и сделать какую-то автоматизацию на продакшене с помощью агентов. То есть я это тоже, наверное, буду в будущем разбирать уже в своих материалах. Поэтому, чтобы не пропустить, подпишитесь на Telegram.

Как конкретно, допустим, какой-то микросервис там на Goша подправляет все эти данные. Куда он это делает? А он делает это с помощью протокола от LTP, то есть Open Temметри есть специальный стандарт, по которым он а на определённые попоинты, в определённые места все эти логи складывает. И есть определённые коллекторы, там коллектор, LO, которые сканируют все, то есть крапят, забирают все эти данные и уже потом по определённым сервисам своим их разносят. То есть куда, ну, для каждого вот этого типа метрики, либо логи, либо трейса, есть своё хранилище, потому что формат работы с ними у всех немножко разный.

Итак, вот мы, в принципе, рассмотрели все, наверное, какие-то важные э моменты, да, мы ещё не рассмотрели куча всего. Это интеграцию с платёжкой, это как там, не знаю, работает полнотекстовый поиск, как делается интеграция с иишкой. Ну, в общем, очень много деталей есть в разработке софта и который я не могу уместить в один ролик, поэтому это чисто архитектурный видосик, который вы посмотрите, чтобы увидеть именно объём, формат, как это всё устроено, объём вот этих продуктов, которые развёрнуты на продакшене. Ещё, конечно, отдельного внимания требует сам продакшн, потому что там просто всё очень сильно отличается от локальной разработки. Всё может немножко по-разному настраиваться. Поэтому именно вот этим опытом мне хочется делиться. Сейчас это чисто архитектура, но дальше это практические видео. Ну и чтобы не пропустить материалы, которые будут платные, но которые будут прямо очень полезные для вас, как для разработчиков, тоже на телегу подпишитесь. Посмотрим, что будет дальше.

Итак, свои конкретные идеи для а фичфункционала пишите в комментарии на Ютубе либо в Телеграме. И спасибо всем за просмотр этого видео. Поставьте лайк. Всем спасибо. Всем пока.