Что такое серверная часть приложения

Клиент-серверная архитектура в картинках

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Знакомая картинка? А вы ведь постоянно сталкиваетесь с этой архитектурой — когда покупаете билет в кино онлайн, бронируете путевку на море или записываетесь к врачу.

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

Об этом я и расскажу в статье. Объясню на пальцах, с примерами и забавными картинками =) Если вы больше любите видео-формат, можно посмотреть мой ролик на youtube на ту же тему.

Содержание

Что это и как работает

Вот есть у нас некий Вася, который решил купить машину. Такую, как в рекламе — быструю, мощную, красивую! Только стоит она как хвост самолета, у Васи таких денег нет.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Конечно, Вася может подкопить несколько лет, а потом уже покупать машину. Но ведь хочется здесь и сейчас! Да и средство передвижения нужно…

А еще Вася не умеет копить — получил зарплату, закупился основным, оплатил жилье, всё! Остальное можно потратить. Для таких людей есть банки, куда можно прийти и взять деньги в кредит.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Конечно, потом вы будете переплачивать, возвращая их назад. Проценты-то конские. Но зато уже сейчас можете позволить купить себе что-то дорогое.

Вася подумал, прикинул и сказал:

— Да, хочу именно так! 100 рублей с зарплаты платить в банк могу, а откладывать — нет. Потрачу.

Поэтому Вася идет в банк и говорит:

— Я Василий Иванов, хочу автокредит на 1000р.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

У Кати есть специальная программа для проверки данных по клиентам. Эта программа может быть как web, так и desktop:

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Катя вбивает в программу «Василий Иванов» и получает информацию по клиенту — есть ли он в черных списках? Была ли кредитная история раньше? И так далее. Но что происходит в потрохах приложения?

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Катя ввела данные на клиенте. Но когда она нажала «проверить», клиент отправил запрос на сервер:

— Дай мне информацию по Васе Иванову!

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Сервер отправил запрос в БД, базу данных:

— Select * from clients where fio = ‘Василий Иванов’. (Дай мне всю информацию по ФИО ‘Василий Иванов’)

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

— Вот тебе все, что нашла.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Сервер вернул эту информацию клиенту:

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

А клиент уже отрисовал ее для Кати:

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

— Ага, кредитная история хорошая.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

И делает предложение Васе:

— Пожалуйста, если хотите взять кредит, то мы готовы выделить 1000р на 12 лет под 80% годовых. Устроит?

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

— Да, меня всё устраивает, давайте скорее деньги, и я побежал за машиной!
Все счастливы, все довольны.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Катя даже не догадывается, какой путь проделали данные в программе, когда она вбила туда ФИО своего клиента. Но мы с вами должны узнать, что же это за путь такой? И к чему все эти сложности? Почему именно такая структура? Почему есть клиент, почему есть сервер?

Зачем нужен клиент

Тут все просто — с клиентом работает пользователь. Он нужен, чтобы превратить байтики программного кода в красивую и понятную картинку. Пользователь — не программист, он не понимает язык программирования или sql. Он понимает формочки и кнопочки. Их в клиенте и рисуем.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Зачем нужен сервер

Клиентов может быть много. В примере с банком у нас может быть по 10 отделений в 10 городах России, а в каждом отделении по 10 операционисток. Тысяча Катек, и у каждой отдельный компьютер.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

А мы ведь хотим, чтобы приложение работало быстро. Чтобы оно не тупило и не зависало, нервируя операциониста и заставляя клиента ждать. Значит, машина нужна мощная. Но если делать мощным каждый компьютер операциониста, денег придется вложить очень много!

Поэтому мы выносим всю основную логику на сервер. И вот его уже делаем мощным! А клиентские машины могут быть дешевыми, потому что на них остается лишь логика в стиле «запросить информацию и красиво отрисовать».

Нет дублирования кода

Если бы у нас были только клиентские машины, на каждой из них хранился бы одинаковый код по обработке логики, лежала вся база данных, все справочники террористов и прочая. Но так как сервер и БД вынесены в отдельные звенья, с клиентской машины освобождается куча места… И кода.

Не надо дублировать код, ведь вся основная логика вынесена на более мощный сервер.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

На сервере и в базе хранится информация, недоступная простому операционисту. Это:

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

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Зачем нужна база

При чем же тут БД? Вот у нас есть наш сервер, пусть он и хранит всю информацию. Бывает и так, иногда база просто не нужна и у нас остается двузвенная архитектура клиент-сервер.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

В таком случае все данных сервер хранит в памяти. Вот только если сервер упадет, или просто перезагрузится — вся информация будет потеряна. Все, что было в памяти, стирается при выключении системы.

БД (база данных) — отдельный программный продукт, который позволяет:

Да, базы может не быть. Но когда она есть, мы уверены в сохранности данных и легко можем по ним поискать.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Плюсы архитектуры

Резюмируем плюсы архитектуры:

Минусы архитектуры

Упало одно звено — все отдыхают

Если упал сервер или отвалилась база, то есть испортилось 1 звено — всё, все в ступоре, все отдыхают. Сотни, тысячи, да хоть миллионы клиентов если есть — никто не может работать. Все операционистки грустно смотрят на окно «Простите, что-то пошло не так» и разводят руками перед клиентом.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Именно поэтому в бизнес-критичном ПО архитектуру усложняют и даже дублируют. Банк с тысячами операционистов не может позволить себе простой. Поэтому они используют кластер серверов — один упал, остальные работают.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Как в таком случае клиент понимает, куда ему отправлять запрос?

Перед серверами ставят балансировщик, и клиент шлет запрос туда. Сколько бы серверов не поставили в кластер, клиенту это не интересно. У него есть один URL — адрес балансировщика.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

И вот с клиента поступает запрос:

— Дай мне всю информацию по Васе Иванову.

— Ребята, новый запрос! Кто меньше загружен?

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

— У меня 5 запросов в очереди стоит.

Балансировщик отправляет запрос второму серверу.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Такая схема используется для высоконагруженного приложения — когда запросов поступает так много, что один сервер с ними просто не справляется.

Facebook, amazon, google — туда заходят миллионы пользователей. Один сервер с ними не справится. Поэтому ставят кластер, а балансировщик делит между ними нагрузку. И в таком случае в кластере может быть не 2 сервера, а 10, 15, сколько нужно, столько и ставим.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

При этом мы можем точно также балансировать базу данных. У нас может быть несколько копий баз данных на самых разных машинах, и балансировщик отправляет запросы то к одной, то ко второй.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Такая схема называется горячий резерв — когда у нас есть несколько серверов, работающих в параллель, и балансировщик распределяет нагрузку между ними.

При этом может быть и схема холодного резерва — когда у нас второй сервер является резервной копией «на всякий случай». Все запросы идут на первый сервер, второй отдыхает.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Но если с первым сервером что-то случится и он помрет, балансировщик перенаправит нагрузку на второй сервер:

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

В это время у администраторов будет время разобраться с проблемой на сервере 1.

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

Простой может быть не только потому, что случилось что-то плохое. Есть еще штатное обновление приложения. Обе схемы резервирования позволяют обновляться безболезненно. Если в кластере два сервера, обновление будет выглядеть так:

Таким образом, схемы резервирования помогают нам устранить проблему «упало 1 звено — все отдыхают». Клиент никогда не узнает, что один или несколько серверов в кластере сдохли, у него всё как работало, так и работает.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Высокая стоимость оборудования

Сервера стоят дорого. Туда нельзя поставить обычный SSD как для домашнего компьютера. Почему? Потому что к железу для серверов совсем другие требования по надежности + есть поддержка специфичных функций:

— у HDD это специальная микропрограмма контроллера, которая оптимизирована для работы диска в RAID, дома это не нужно.

— у SSD это наличие группы конденсаторов, которые хранят энергию на случай отключения питания, чтобы хватило времени скинуть из DDR кэша данные в энергонезависимую память и данные не побились.

SSD — быстро работающий диск, HDD — обычный. RAID — когда мы N дисков вместе соединили, а DDR кэш — это оперативная память

Плюс у серверных решений гарантия обычно гораздо дольше: 5 лет, а не год.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

По цене отличаются в 2 раза. Например, SSD:

Вроде не сильно отличается, да? Но смысл в том, что для дома 1 тб хватает за глаза — и фоточки все влезут, и кино, и куча приложений… А для базы данных иногда и 10 тб будет мало. А если делать кластер, то умножаем стоимость на 2, если не больше. Поэтому и разница в цене кажется огромная, но при пересчете на гигабайт небольшая выходит.

Не забывайте, что дома вам просто надо свои фоточки держать, да и те обычно в облаке. А на сервере бизнес-критичный функционал, который жрет дофига ресурсов и который надо дублировать на случай «вдруг первый сдохнет».

Нужно нанять сисадмина

Нам нужно нанять сисадмина, который будет следить за всеми нашеми серверами приложения и БД. Добавляем его зарплату к стоимости оборудования!

Что тестировать

Чтобы понимать, что тестировать, надо понимать, с чем имеет дело человек.

Пользователь работает с клиентом. Это может быть web или desktop приложение, не суть. Операционистке Кате дали рабочее место, показали какую программу запускать и как с ней работать. Она знать не знает о наличии серверов и БД, она работает только с клиентом.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Поэтому тестировщик в первую очередь проверяет клиент! Потому что сервер может работать идеально, вы можете даже написать тесты на уровне API и они все будут зелененькие, и кажется, что все зашибись! А пользователь загрузит отчет и увидит ошибку. Ой.

Сервер работает, на клиенте ошибка. И плевать на сотни «зеленых» автотестов. У пользователя все равно ошибка. И наша задача — посмотреть с его точки зрения.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Однако, если у вас есть доступ к серверу приложения и его базе данных — стоит проверять и их тоже! Так мы можем увидеть «будущий баг». Например:

— Ну, наверное, их и не заполняли.

А их заполняли! Просто сохранение криво сработало. Поэтому, если у нас только черный ящик, то нужно проверять, «а реально ли сохранились данные?». Сохранили? Откройте карточку в новом окне или вызовете информацию через API-метод.

Если доступ к базе есть — просто проверьте по ней, что все хорошо. Если есть доступ к серверным логам — проверьте их на наличие ошибок.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Помимо простых пользователей бывают злые люди, которые пытаются встрять в наше приложение и своровать деньги / данные. Они используют не клиент или сервер — туда у них доступа нет. Они пытаются перехватить данные в пути от клиента к серверу, или от сервера к БД.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Ну а раз нехорошие люди могут это сделать, то тестировщик тоже должен это уметь! Потому что тестировщик предоставляет информацию о нашем продукте.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Тестировщик изучает уязвимости и потом рассказывает команде:

— Ребята, вот я проверил, у нас есть такие-то и такие-то потенциальные дыры. Давайте подумаем, надо нам их как-то закрывать или нет.

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

Но какие-то базовые проверки типа sql-иньекций или XSS-атак стоит изучить и проверить на своем приложении. Хотя бы чтобы понять их критичность. Ведь если атака сломает клиент — ну и пусть, сам себе буратино. А если атака положит сервер, это уже не очень хорошо. И надо хотя бы знать, от чего это бывает.

Итого

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Клиент — та программа, с которой работает пользователь. Он знать не знает, это у него на компьютере программа целиком, или где-то за ней прячутся сервер с базой, а то и целый RAID. Он работает в браузере или с desktop-приложением. И всё, что ему нужно знать — это «куда тут тыкать».

Клиенту не нужно много памяти, места на диске и других ресурсов. Поэтому рабочие места относительно дешево стоят. А это именно то, что нам нужно, особенно если нужно закупить оборудование для тысяч операционисток банка.

Сервер — компьютер, на котором хранится само приложение. Весь код, вся логика, все дополнительные материалы и справочники. Например, справочник адресов ФИАС или справочник юр лиц ЕГРЮЛ — они тоже занимают место, как сами по себе, так и в памяти приложения.

Иногда говорят «сервер приложения» и «сервер БД». Это нормально, ведь фактически сервер — это просто машина, компьютер. А базу и сервер приложения обычно хранят на разных машинах, ради безопасности. В таком случае, если говорят «сервер приложения» — речь о втором звене нашей схемы.

Приложения бывают самые разные. Есть ресурсоемкие, им нужно много памяти и места на диске. Есть «легкие», которые можно развернуть даже на домашнем компьютере.

БД (база данных) — хранилище данных. Тут вы можете легко поискать информацию + уверены в том, что она сохранится, даже если в приложении что-то сломается. Подробнее о ней — в статье «Что такое База Данных (БД)»

Сколько места нужно под базу, зависит от количества данных. Есть огромные базы в банках, где и 1тб будет мало. А есть совсем небольшие, которые вы можете установить на своей машине. Например, XAMPP можно поставить. И врядли вы напихаете туда столько данных, что у вас не останется под них место.

Отдельной базы может не быть, тогда структура станет двузвенной: клиент-сервер. И все!

Схема условная, в реальной жизни у нас как минимум будет больше клиентов. А если приложение высоконагруженное, то будет несколько серверов и несколько баз данных:

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

PS — больше полезных статей ищите в моем блоге по метке «полезное». А полезные видео — на моем youtube-канале

Источник

Как устроен наш код. Серверная архитектура одного проекта

Что такое серверная часть приложения. b1c20ffbc51d4d1e9b3f7ffad19f9f80. Что такое серверная часть приложения фото. Что такое серверная часть приложения-b1c20ffbc51d4d1e9b3f7ffad19f9f80. картинка Что такое серверная часть приложения. картинка b1c20ffbc51d4d1e9b3f7ffad19f9f80.Так сложилось, что к тридцати годам я менял работу лишь единожды и не имел возможности на собственном опыте изучить, как в различных компаниях устроены веб-проекты, расчитанные на высокую скорость отклика и большое количество пользователей. Так что, дорогой хабраюзер, попавший в поле моего зрения в оффлайне, увидев меня, лучше беги, пока я не начал докучать тебе вопросами на тему обработки ошибок, логирования и процесса обновления на рабочих серверах&lt/irony&gt. Мне интересен не столько набор используемых технологий, сколько принципы, на которых построена кодовая база. Как код разбит на классы, как классы распределены по слоям, как бизнес-логика взаимодействует с инфраструктурой, каковы критерии по которым оценивается качество кода и как организован процесс разработки нового функционала. К сожалению, подобную информацию найти непросто, в лучшем случае всё ограничивается перечислением технологий и кратким описанием разработанных велосипедов, а хочется, конечно, более детализированной картинки. В этом топике я попытаюсь как можно более подробно описать, как устроен код в компании, где работаю я. Этот подход — мой суммарный опыт полученный за 10 лет разработки в разных компаниях.

Заранее извиняюсь, если мой тон кому-то покажется наставническим — я не имею амбиций обучать кого-либо, максимум на что претендует этот пост — это рассказ об архитектуре серверной части одного реального проекта. В моем не самом развитом с точки зрения разработки ПО городе я не один и не два раза встречал разработчиков, которым был очень интересен мой опыт построения серверной части веб-приложений, так что, ребята, я пишу этот пост во многом из-за вас, и я искренне надеюсь, что у меня получится удовлетворить ваш интерес.

В статье я не буду демонстрировать листинги рабочего проекта, но рассказывать о том как устроена кодовая база без листингов будет все-таки тоже неправильно. Поэтому я решил “придумать” абстрактный продукт и на его примере пройти весь процесс разработки серверной части: от получения программистами ТЗ до реализации сервисов хранения, используя при этом принятые в нашей команде практики и проводя параллели с тем, как устроен наш реальный проект.

MoneyFlow. Постановка задачи

Что такое серверная часть приложения. 480224244e00490181ffccbd30479256. Что такое серверная часть приложения фото. Что такое серверная часть приложения-480224244e00490181ffccbd30479256. картинка Что такое серверная часть приложения. картинка 480224244e00490181ffccbd30479256.

Что такое серверная часть приложения. 438be63f690a47baab1339c30364e29c. Что такое серверная часть приложения фото. Что такое серверная часть приложения-438be63f690a47baab1339c30364e29c. картинка Что такое серверная часть приложения. картинка 438be63f690a47baab1339c30364e29c.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Контракт взаимодействия между клиентской и серверной частями приложения

В нашей команде разработка нового функционала серверной и клиентской частей сервиса ведется параллельно. После ознакомления с ТЗ мы первым делом определяем интерфейс, по которому строится взаимодействие фронтенда с бэкендом. Так сложилось, что наше API построено как RPC, а не REST, и при его (API) определении мы в первую очередь руководствуемся принципом необходимости и достаточности передаваемых данных. Попробуем по макетам прикинуть, какая информация может потребоваться клиентскому приложению от серверной части.

Что такое серверная часть приложения. 78290fd43fcf4bc58ef62aa5849712da. Что такое серверная часть приложения фото. Что такое серверная часть приложения-78290fd43fcf4bc58ef62aa5849712da. картинка Что такое серверная часть приложения. картинка 78290fd43fcf4bc58ef62aa5849712da.

В макете списка у нас всего три столбца — сумма, описание и день, когда была совершена операция. Кроме этого клиентскому приложению для отображения списка потребуются категория, к которой относится расходная операция, и Id, чтобы можно было кликом из списка перейти к экрану редактирования.

Пример результата вызова серверного метода для страницы со списком совершенных расходных операций.

Теперь посмотрим на макет страницы редактирования.

Что такое серверная часть приложения. a00dd0e1021b492eab34323eabf116d9. Что такое серверная часть приложения фото. Что такое серверная часть приложения-a00dd0e1021b492eab34323eabf116d9. картинка Что такое серверная часть приложения. картинка a00dd0e1021b492eab34323eabf116d9.

Для отображения страницы редактирования клиентскому приложению потребуется метод, возвращающий следующую JSON строку.

По сравнению с моделью операции, запрашиваемой в списке, на странице редактирования в модель добавилась информация об истории создания/изменения записи. Как я уже писал, мы руководствуемся принципом необходимости и достаточности и не создаем единую общую модель данных для каждой сущности, которая могла бы использоваться в API в каждом связанном с ней методе. Да, в приложении MoneyFlow разница в моделях списка и страницы редактирования всего в одно поле, но в реальности такие простые модели бывают только в книгах, наподобие “Как за 7 дней научиться программировать на C++ на уровне эксперта”, с недобро улыбающимся упитанным мужиком в свитере на обложке. Настоящие проекты устроены намного сложнее, разница в модели для экрана редактирования по сравнению с запрашиваемой в списке моделью в реальном проекте у нас может достигать двузначного количества полей и, если мы будем пытаться везде пользоваться одной и тоже моделью, мы неоправданно увеличим время генерации списка и впустую будем нагружать наши сервера.

JSON объекты, отдаваемые сервером, это конечно же сериализованные DTO объекты, поэтому в серверном коде у нас будут классы, определяющие модели для каждого метода из API. В соответствии с принципом DRY эти классы, если это возможно, построены в иерархию.

Классы, определяющие контракт API MoneyFlow для экранов списка, создания и редактирования операций.

После того как контракт определен, занимающиеся фронтендом разработчики создают моки (mocks) на еще несуществующие серверные методы и идут творить Angular JS магию. Да, у нас очень толстый и очень замечательный клиент, руку к которому приложили уважаемые хабраюзеры iKbaht, Houston и еще несколько не менее замечательных хабра-анонимов. И было бы совсем некрасиво со стороны серверных разработчиков, если бы наше красивое, мощное JS приложение работало бы на медленном API. Поэтому на бэкенде мы стараемся разрабатывать наше API быстрым настолько, насколько это вообще возможно, выполняя большую часть работы асинхронно и строя модель хранения данных максимально удобной для чтения.

Разбиваем систему на модули

Если рассматривать приложение MoneyFlow с точки зрения функционала, можно выделить в нем два различных модуля — это модуль по работе с расходами и модуль отчетности. Если бы мы были уверены, что число пользователей у нашего приложения будет невелико, мы бы строили модуль отчетности прямо поверх модуля внесения расходов. В этом случае у нас была бы, к примеру, хранимая процедура, которая бы строила для пользователя отчет за год непосредственно по базе внесенных расходов. Такая схема очень удобна с точки зрения консистентности данных, но, к сожалению, у нее есть и недостаток. При достаточно большом количестве пользователей таблица с данными расходов станет слишком большой, чтобы наша хранимая процедура отрабатывала быстро, рассчитывая “на лету”, к примеру, сколько средств было потрачено пользователем на транспорт за год. Поэтому, чтобы пользователю не приходилось ждать отчета долго, нам придется генерировать отчеты заранее, обновляя их содержимое по мере появления в системе учета расходов новых данных.
Разработка ПО — процесс итеративный. Если считать схему с единой моделью данных для расходов и отчетов первой итерацией, то вынесение отчетов в отдельную денормализованную БД уже итерация номер два. Если теоретизировать дальше, то можно вполне нащупать направление и для следующих улучшений. К примеру, как часто нам придется обновлять отчеты за прошлый месяц или год? Наверное, в первых числах января пользователи будут вносить в систему информацию о покупках, совершенных в конце декабря, но большая часть приходящих в систему данных будет относиться к текущему календарному месяцу. То же справедливо и для отчетности, пользователей гораздо чаще будут интересовать отчеты за текущий месяц. Поэтому в рамках третьей итерации, если эффект от использования отдельного хранилища для отчетов будет нивелирован увеличением количества пользователей, можно оптимизировать систему хранения перенесом данных по текущему периоду в более быстрое хранилище, расположенное, к примеру, на отдельном сервере. Или использованием хранилища вроде Redis, хранящего свои данные в оперативной памяти. Если проводить аналогию с нашим реальным проектом, то мы находимся на итерации 2.5. Cтолкнувшись с падением производительности, мы оптимизировали нашу систему хранения, перенеся данные каждого модуля в независимые базы данных, а также мы перенесли часть часто используемых данных в Redis.

По легенде проект MoneyFlow только готовится к запуску, поэтому мы оставим его на итерации номер два, оставив себе простор для последующих улучшений.

Синхронный и асинхронный стеки выполнения

Кроме увеличения производительности за счет хранения данных в максимально удобном для чтения виде мы стараемся большую часть работы производить незаметно для пользователя в асинхронном стеке выполнения. Каждый раз, когда со стороны клиентского приложения к нам приходит запрос, мы смотрим, какую часть работы необходимо выполнить до того, как ответ будет возвращен клиенту. Очень часто мы возвращаем ответ почти сразу, выполнив лишь необходимую часть вроде проверки валидности пришедших данных, переложив большую часть работы на асинхронную обработку. Работает это следующим образом.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Теперь сам код. Для каждого пришедшего с клиентской части запроса мы создаем объект, ответственный за его корректную обработку. Объект, обрабатывающий запрос на создание новой расходной операции, выглядел бы у нас так.

Объект ChargeOpsCreator проверил корректность входных данных и добавил совершенную операцию в модуль учета расходов, после чего клиентскому приложению возвратился Id созданной записи. Процесс обновления отчетов у нас производится в фоновом процессе, для этого мы отправили на сервер очередей сообщение ChargeOpCreated, обработчик которого и обновит отчет для пользователя. Сообщения, отправляемые в сервисную шину, это простые DTO объекты. Вот так выглядит класс ChargeOpCreated, который мы только что отправили в сервисную шину.

Разбиение приложения на слои

Оба стека выполнения (синхронный и асинхронный) на уровне сборок у нас разбиты на три слоя — слой приложения (контекст исполнения), слой бизнес-логики и сервисы хранения данных. У каждого слоя строго определена зона его ответственности.

Синхронный стек. Слой приложения

В синхронном стеке контекстом исполнения является ASP.NET приложение. Его зона отвественности, кроме обычных для любого веб-сервера действий вроде приема запросов и сериализации/десериализации данных, у нас невелика. Это:

Слой приложения в нашей системе очень прост и легковесен, мы за день можем поменять контекст выполнения нашей системы с ASP.NET MVC (так у нас исторически сложилось) на ASP.NET WebAPI или Katana.

Синхронный стек. Слой бизнес-логики

Слой бизнес-логики в синхронном стеке у нас состоит из множества маленьких объектов, обрабатывающих входящие запросы пользователей. Класс ChargeOpsCreator, листинг которого я приводил выше, это как раз пример такого объекта. Создание подобных классов хорошо состыкуется с принципом единственной ответственности и каждый подобный класс может быть полноценно протестирован модульными тестами благодаря тому, что все его зависимости инжектируются в конструктор.

В реальном проекте объекты слоя бизнес-логики у нас отделены от контроллеров, где они инстанцируются, интерфейсами. Но особой практической пользы это отделение нам не принесло, а только усложнило секцию IoC контейнера в конфигурационном файле.

Асинхронный стек

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

Пример конфига службы (псевдокод).

Служба при старте считывает конфигурационный файл, проверяет, что на сервере сообщений существует очередь с названием ReportBuilder (если нет — то создает ее), проверяет существование роутинга, отправляющего сообщения типа ChargeOpCreated в эту очередь (если нет — настроит роутинг сама), и начинает обрабатывать сообщения, попадающие в очередь ReportBuilder, запуская для них соответствующие обработчики. В нашем случае — это единственный обработчик типа ChargeOpCreatedHandler (об объектах обработчиках чуть ниже). Также служба поймет из конфигурационного файла, что на разбор сообщений очереди “ReportBuilder” она может выделить до 10 потоков, что в случае возникновения ошибки в работе объекта ChargeOpCreatedHandler сообщение должно вернуться в очередь с таймаутом в 200мс, а при повторном падении обработчика сообщение должно попасть в лог с пометкой “CriticalError” и еще несколько подобных параметров. Это дает нам замечательную возможность ”на лету”, не внося изменений в код, масштабировать разборщики очередей, запуская на резервных серверах в случае накопления сообщений в какой-нибудь очереди дополнительную службу, указав ей в конфиге, какую именно очередь она должна разбирать, что очень, очень удобно.

Сервис разбора очередей — это обертка над библиотекой MassTransit (сам проект, статья на хабре), реализующий паттерн DataBus над сервером очередей RabbitMQ. Но программист, пишущий в слое бизнес-логики, ничего об этом знать не должен, вся инфраструктура, которой он касается (сервера очередей, key/value хранилища, СУБД и т.д.), скрыта от него слоем абстракции. Наверное, многие видели пост “Как два программиста хлеб пекли” о Борисе и Маркусе, использующих диаметрально противоположные подходы к написанию кода. Нам бы подошли оба: Маркус разрабатывал бы бизнес-логику и слой, работающий с данными, а Борис бы занимался у нас работой над инфраструктурой, разработку которой мы стараемся вести на высоком уровне абстракции (иногда мне кажется, что даже Борис бы наш код одобрил). При разработке же бизнес-логики, мы не пытаемся выстроить объекты в длинную иерархию, создавая большое количество интерфейсов, мы скорее стараемся соответствовать принципу KISS, оставляя наш код максимально простым. Вот так, например, в MoneyFlow будет выглядеть обработчик сообщения ChargeOpCreated, который мы уже заботливо прописали в конфиг занимающейся разбором очереди ReportBuilder службы.

Все объекты-обработчики являются наследниками абстрактного класса MessageHandler, где T — тип разбираемого сообщения, с единственным абстрактным методом HandleMessage, перегруженном в наследниках.

После получения сообщения с сервера очередей, служба создает нужный объект-обработчик с помощью IoC контейнера и вызывает метод HandleMessage, передавая в качестве параметра полученное сообщение. Чтобы оставить возможность тестировать поведение обработчика в отрыве от его зависимостей, все внешние зависимости, а у ChargeOpCreatedHandler это только сервис хранения отчетов, инжектируются в конструктор.

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

Обработка ошибок в асинхронном стеке выполнения

Модуль отчетов у нас больше подходит под определение согласованности в конечном счете (eventual consistency), чем под определение сильной согласованности (strong consistency), но это все равно гарантирует конечную согласованность данных в модуле отчетов при любых возможных сбоях системы. Представим, что сервер с базой данных, хранящей отчеты, упал. Понятно, что в случае бинарной кластеризации, когда каждый инстанс базы данных продублирован на отдельном сервере, подобная ситуация практически исключена, но все-таки представим, что это произошло. Клиенты продолжают вносить свои расходы, сообщения о них появляются в сервере очередей, но разборщик, ответственный за обновление отчетов, не может получить доступ к серверу БД и падает с ошибкой. Согласно конфигу службы, приведенному выше, после падения на сообщении ChargeOpCreated это же сообщение вернется обратно на сервер очередей через 200мс, после второй попытки (тоже неудачной) сообщение будет сериализовано и занесено в специальное хранилище упавших сообщений, которое в нашем проекте объединено с логами. После того, как сервер БД поднимется, мы можем взять все упавшие в процессе обработки сообщения из логов и отправить их на сервер очередей обратно (у нас это делается вручную), приведя тем самым данные модуля отчетов в согласованное состояние. Но все это накладывает на программистов обязательство писать код в объектах-обработчиках сообщений очереди по принципу атомарности. Обработчик должен либо полностью сработать, либо сразу упасть. Как вариант, он также может быть “идемпотентным”, то есть выполнив часть работы и упав, он должен при повторной обработке сообщения понять, какую работу он уже выполнил, и не пытаться сделать ее повторно.

Слой хранения данных

Слой хранения данных у нас общий для асинронного и синхронного стеков выполнения. Для разработчика бизнес-логики сервис хранения — это просто интерфейс с методами для получения и изменения данных. Под интерфейсом скрывается сервис, полностью инкапсулирующий в себе доступ к данным определенного модуля. При проектировании интерфейсов сервисов мы пытаемся, если это возможно, следовать концепции CQRS — каждый метод у нас является либо командой, выполняющей какое-то действие, либо запросом, возвращающим данные в виде DTO объектов, но не одновременно. Делаем мы это не для разбиения системы хранения на две независимые структуры для чтения и для записи, а скорее порядка ради.

Как бы мы не снижали время отклика, выполняя большую часть работы асинхронно, неудачно спроектированная система хранения может перечеркнуть всю проделанную работу. Описание того, как устроен слой хранения в нашем проекте, я не случайно оставил в самом конце. Мы взяли за правило разрабатывать сервисы-хранилища только после того, как завершена реализация объектов слоя бизнес-логики, чтобы при проектировании таблиц БД точно понимать, как данные этих таблиц будут использованы. Если при разработке бизнес-логики нам нужно получить какую-то информацию из слоя хранения, мы добавляем в интерфейс, скрывающий реализацию сервиса, новый метод, отдающий данные в удобном для бизнес-логики виде. У нас именно бизнес-логика определяет интерфейс хранилища, но никак не наоборот.

Вот пример интерфейса сервиса хранения отчетов, который был определен при разработке бизнес-логики приложения MoneyFlow.

Для хранения данных мы используем реляционную базу данных Postgresql. Но это конечно же не означает, что данные мы храним в реляционном виде. Мы закладываем возможность масштабирования шардингом и проектируем таблицы и запросы к ним по специфичным для шардинга канонам: не используем join-ы, строим запросы по первичным ключам и т.д. При построении хранилища отчетов MoneyFlow мы тоже оставим возможность перенести часть отчетов на другой сервер, если вдруг это потребуется впоследствии, не перестраивая при этом структуру таблиц. Как мы будем делать шардинг — с помощью встроенного механизма физического разделения таблиц (partitioning) или добавлением в сервис хранения отчетов менеджера шард — мы будем решать тогда, когда в шардинге появится необходимость. Пока же нам стоит сконцентрироваться на проектировании структуры таблицы, которая бы впоследствии не препятствовала шардингу.

В Postgresql есть замечательные NoSQL типы данных, такие как json и менее известный, но не менее замечательный hstore. Отчет, который нужен клиентскому приложению, должен представлять собой json строку. Поэтому нам было бы логично использовать для хранения отчетов встроенный тип json и отдавать его на клиент как есть, не тратя ресуры на цепочку сериализаций DB Tables->DTO->json. Но, чтобы еще раз попиарить hstore, я буду делать то же самое с одной лишь разницей, что внутри БД отчет будет лежать в виде ассоциативного массива, для хранения которых и предназначен тип hstore.

Для хранения отчетов нам будет достаточно одной таблицы с четырьмя полями:

ПолеЧто означает
idидентификатор пользователя
yearотчетный год
monthотчетный месяц
reportхеш-таблица с данными отчета

Первичный ключ таблицы у нас будет составным по полям id year month. В качестве ключей ассоциативного массива report мы будем использовать категории расходов, а в качестве значений — сумму, потраченную на соответствующую категорию.

Пример отчета в базе данных.

По этой строке ясно, что пользователь с id «d717b8e4-1f0f-4094-bceb-d8a8bbd6a673» потратил в январе 2015 года 500р на транспорт и 2500р на развлечения.

Если реализация метода GetMonthReport() не вызывает вопросов, сформировать json строку отчета из ассоциативного массива несложно встроенными средствами postgresql, то для корректной релизации обновляющего месячный отчет метода UpdateMonthReport() придется повозиться чуть побольше. Во-первых, нам надо убедиться, что отчет за этот месяц уже существует в БД и создать его, если это не так. Во-вторых, нам надо исключить состояние гонки (race condition) — попытки создания/обновления этого же отчета паралельным потоком. Пример получился довольно большим, но связано это не со сложностью типа hstore, а с необходимостью производить операцию UpSert, состоящую из двух запросов и следующую из этого необходимость исключения состояния гонки. 99% методов в сервисах хранения у нас устроены гораздо проще, я и сам не ожидал, что придется писать так много кода. Но нет худа без добра, этот пример отлично демонстрирует, почему именно слой бизнес-логики у нас определяет интерфейс сервиса хранения, а не наоборот. Если бы мы начали работу над проектом с создания хранилища отчетов, мы наверняка сделали бы классический репозиторий с методами AddReport(), GetReport(), UpdateReport() и невольно переложили бы тем самым необходимость обеспечения потокобезопасного доступа на клиентов этого репозитория. То есть на слой бизнес-логики. Именно поэтому, отношения объектов слоя бизнес-логики с сервисами хранения мы строим, руководствуясь принципами большого начальника, которые гласят следующее: ни один крупный руководитель не будет пытаться выполнять работу, с которой его подчиненный в состоянии справиться самостоятельно, и тем более он не будет подстраиваться под своего подчиненного.

Код сервиса-хранилища отчетов.

В конструкторе сервиса ReportStorage у нас две зависимости — это IDbMapper и IDistributedLockFactory. IDbMapper — это фасад над легковесным ORM фреймворком BLToolkit.

Для генерации запросов было бы вполне допустимо использовать NHibernate или еще какой-либо ORM, но мы решили писать запросы руками и только маппинг результатов выполнения в DTO объекты переложить на фреймворк, с чем BLToolkit справляется просто прекрасно.

IDistributedLockFactory в свою очередь — это фасад над механизмом распределенных блокировок, наподобие встроенного в ServiceStack механизма RedisLocks.
Я уже писал, что мы используем подход: абстрактная инфраструктура — “чисто конкретная” бизнес-логика, именно поэтому мы стараемся оборачивать сторонние библиотеки врапперами и фасадами, чтобы всегда иметь возможность заменять элементы инфраструктуры, не переписывая бизнес-логику проекта.

“Смешанная” концепция разбиения на модули

Есть определенная доля лукавства в словах, что мы выделили систему отчетности приложения MoneyFlow в отдельный, независимый модуль. Да, мы храним данные отчетов отдельно от данных системы учета, но в нашем приложении бизнес-логика и части инфраструктуры, такие как сервисная шина или например веб-сервер, являются общими ресурсами для всех модулей приложения. Для нашей небольшой компании, в которой для пересчета программистов вполне достаточно пальцев одной руки фрезеровщика, подобный подход более чем оправдан. В крупных же компаниях, где над разными модулями работа может вестись разными командами, принято заботиться о минимизации использования общих ресурсов и об отстутствии единых точек отказа. Так, если бы приложение MoneyFlow разрабатывалось бы в крупной компании, его архитектура бы представляла собой классическое SOA.

Что такое серверная часть приложения. image loader. Что такое серверная часть приложения фото. Что такое серверная часть приложения-image loader. картинка Что такое серверная часть приложения. картинка image loader.

Отказ от идеи сделать систему на основе полностью независимых модулей, общающихся друг с другом на основе одного простого протокола, дался нам непросто. Изначально при проектировании мы планировали делать настоящее SOA решение, но в последний момент, взвесив все за и против в рамках нашей компактной (не в смысле замкнутой и ограниченной, а просто очень небольшой) команды решили использовать “смешанную” концепцию разбиения на модули: общая инфраструктура и бизнес-логика — независимые сервисы хранения. Сейчас я понимаю, что это решение было верным. Время и силы, не потраченные на полное дублирование инфраструктуры модулей, мы смогли направить на улучшение других аспектов приложения.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *