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

Структура веб-приложения

Чаще всего веб-приложения состоят как минимум из трёх основных компонентов:

Это, пожалуй, основные компоненты большинства веб-приложиний. Графически схему их взаимодействия можно представить так:

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

Язык программирования для серверной части

Для программирования серверной части веб приложения может использоваться язык PHP, как в описанном примере, но не обязательно он. Для разработки веб-приложений можно использовать практически любые современные языки программирования:

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

В дальнейшем в ходе освоения веб-технологий будем опираться на язык PHP, поскольку он является самым распространённым и самым простым среди всех перечисленных. Конфигурация сервера, включающая себя операционную систему Linux, язык программирования PHP, веб-сервер Apache и СУБД MySQL считается стандартом де-факто для среднестатистического сервера и даже носит название LAMP (от сокращения Linux, Apache, MySQL, PHP).

Язык программирования клиентской части

Для «оживления» графического интерфейса используются дополнительные технологии: скрипты JavaScript, а также встроенные в веб-страницу компоненты, созданные на Flash, Java или Silverlight. Все эти элементы веб-страницы могут взаимодействовать между собой: программа, написанная на JavaScript и выполняющаяся на веб-странице, может управлять встроенными в страницу компонентами, тем самым реализуя пользовательский интерфейс с богатыми возможностями.

Источник

Введение в серверную часть

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

Перед стартом:Базовая компьютерная грамотность. Базовое понимание, что такое веб-сервер.
Цель:Ознакомиться с тем, что такое программирование серверной части, на что оно способно и чем отличается от программирования клиентской части.

Большинство крупных веб-сайтов используют программирование серверной части чтобы динамично отображать различные данные при необходимости, в основном взятые из базы данных, располагающейся на сервере и отправляемые клиенту для отображения через некоторый код (например, HTML и JavaScript).

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

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

В современном мире веб-разработки настоятельно рекомендуется узнать о разработке на стороне сервера.

Что такое программирование серверной части сайта?

Веб-браузеры взаимодействуют с веб-серверами при помощи гипертекстового транспортного протокола (HTTP). Когда вы нажимаете на ссылку на веб-странице, заполняете форму или запускаете поиск, HTTP-запрос отправляется из вашего браузера на целевой сервер.

Запрос включает в себя URL, определяющий затронутый ресурс, метод, определяющий требуемое действие (например, получить, удалить или опубликовать ресурс) и может включать дополнительную информацию, закодированную в параметрах URL (пары поле-значение, оправленные как строка запроса), как POST запрос (данные, отправленные методом HTTP POST) или в куки-файлах.

Веб-серверы ожидают сообщений с клиентскими запросами, обрабатывают их по прибытию и отвечают веб-браузеру при помощи ответного HTTP сообщения (HTTP-ответ). Ответ содержит строку состояния, показывающую, был ли запрос успешным или нет (например, «HTTP/1.1 200 OK» в случае успеха).

Тело успешного ответа на запрос может содержать запрашиваемые данные (например, новую HTML-страницу или изображение, и т. п.), который может отображаться через веб-браузер.

Статические сайты

Схема ниже показывает базовую архитектуру веб-сервера для статического сайта (статический сайт — это тот, который возвращает одно и то же жёстко закодированное содержимое с сервера всякий раз, когда запрашивается конкретный ресурс). Когда пользователь хочет перейти на страницу, браузер отправляет HTTP-запрос «GET» с указанием его URL.

Сервер извлекает запрошенный документ из своей файловой системы и возвращает HTTP-ответ, содержащий документ и успешный статус (обычно 200 OK). Если файл не может быть извлечён по каким-либо причинам, возвращается статус ошибки (смотри ошибки клиента и ошибки сервера).

серверная часть приложения это. Basic%20Static%20App%20Server. серверная часть приложения это фото. серверная часть приложения это-Basic%20Static%20App%20Server. картинка серверная часть приложения это. картинка Basic%20Static%20App%20Server.

Одинаково ли программирование серверной части и клиентской?

Динамические сайты

Динамический веб-сайт — это тот, где часть содержимого ответа генерируется динамически только при необходимости. На динамическом веб-сайте HTML-страницы обычно создаются путём вставки данных из базы данных в заполнители в HTML-шаблонах (это гораздо более эффективный способ хранения большого количества контента, чем использование статических сайтов).

Динамический сайт может возвращать разные данные для URL-адреса на основе информации, предоставленной пользователем или сохранёнными настройками, и может выполнять другие операции, как часть возврата ответа (например, отправку уведомлений).

Большая часть кода для поддержки динамического веб-сайта должна выполняться на сервере. Создание этого кода известно, как «программирование серверной части» (или иногда «программирование бэкенда»).

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

Запросы статических ресурсов обрабатываются так же, как и для статических сайтов (статические ресурсы — это любые файлы, которые не меняются, обычно это: CSS, JavaScript, изображения, предварительно созданные PDF-файлы и прочее).

серверная часть приложения это. Web%20Application%20with%20HTML%20and%20Steps. серверная часть приложения это фото. серверная часть приложения это-Web%20Application%20with%20HTML%20and%20Steps. картинка серверная часть приложения это. картинка Web%20Application%20with%20HTML%20and%20Steps.

Запросы динамических данных отправляются (2) в код серверной части (показано на диаграмме как Веб-приложение). Для «динамических запросов» сервер интерпретирует запрос, читает необходимую информацию из базы данных (3), комбинирует извлечённые данные с шаблонами HTML и возвращает ответ, содержащий сгенерированный HTML (5, 6).

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

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

Код клиентской части написан с использованием HTML, CSS и JavaScript — он запускается в веб-браузере и практически не имеет доступа к базовой операционной системе (включая ограниченный доступ к файловой системе).

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

Код серверной части может быть написан на любом количестве языков программирования — примеры популярных языков серверной части включают в себя PHP, Python, Ruby, C# и NodeJS (JavaScript). Код серверной части имеет полный доступ к операционной системе сервера, и разработчик может выбрать какой язык программирования (и какую версию) он хотел бы использовать.

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

И снова, поскольку и клиентская и серверная части используют фреймворки, области очень разные и, следовательно, фреймворки тоже разные. Фреймворки клиентской части упрощают вёрстку и представление данных, тогда как фреймворки серверной части обеспечивают много «обычной» функциональности веб-сервера, которую вы, возможно, в противном случае, должны были осуществлять самостоятельно (например, поддержка сессий, поддержка пользователей и аутентификация, простой доступ к базе данных, шаблонам библиотек и т. д.).

На заметку: Фреймворки клиентской части часто используются для ускорения написания кода клиентской части, но вы также можете решить писать весь код руками; на самом деле, написание кода руками может быть более быстрым и эффективным, если вам нужен небольшой простой веб-сайт UI.

И, наоборот, вы практически никогда не посмотрите в сторону написания кода серверной части веб-приложения без фреймворка: осуществление жизненно важной функции, такой как HTTP сервер действительно сложно сделать с нуля, скажем, на Python, но веб-фреймворки для Python, такие как Django, обеспечивают это из коробки наряду с другими полезными инструментами.

Что можно сделать в серверной части?

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

Компании, такие как Amazon, используют программирование серверной части для построения исследовательских результатов для товаров, формирования целевого предложения, основанного на предпочтениях клиента и предыдущих покупках, упрощения заказов и т. д. Банки используют программирование серверной части, чтобы хранить учётную информацию и позволять только авторизованным пользователям просматривать и совершать транзакции. Другие сервисы, такие как Facebook, Twitter, Instagram и Wikipedia используют бэкенд, чтобы выделять, распространять и контролировать доступ к интересному контенту.

Некоторые типичные применения и выгоды бэкенда перечислены ниже. Вы заметите, что есть некоторое пересечение!

Эффективное хранение и доставка информации

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

Программирование серверной части позволяет вместо этого хранить информацию в базе данных и динамически создавать и возвращать HTML и другие типы файлов (например, PDF, изображения, и т. д.). Также есть возможность просто вернуть данные (JSON, XML, и т. д.) для отображения, используя подходящий фреймворк клиентской части (это уменьшает загрузку процессора на сервере и количество передаваемых данных).

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

Из-за того, что информация находится в базе данных, её также можно легко передать и обновить через другие бизнес системы (например, отслеживание).

На заметку: вам не нужно сильно напрягать своё воображение, чтобы увидеть достоинства кода серверной части для эффективного хранения и передачи информации:

Для обычного поиска (например, «рыба») вы можете увидеть буквально миллионы найденных значений. Использование базы данных позволяет им храниться и передаваться эффективно, и это позволяет контролировать представление информации всего в одном месте.

Настраиваемый пользовательский опыт взаимодействия

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

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

На заметку: Google Maps сохраняет вашу историю поиска и посещений. Часто посещаемые или часто вводимые в поиск локации выделяются больше, чем остальные.

Результаты поиска Google оптимизируются на основе предыдущего поиска.

Стечение обстоятельств? Нет!

Контролируемый доступ к контенту

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

На заметку: Рассмотрим другие реальные примеры, где доступ к контенту контролируется. Например, что вы можете увидеть, если зайдёте на сайт вашего банка? Авторизуйтесь через вашу учётную запись, и какую дополнительную информацию вы можете просматривать и редактировать? Что за информацию вы можете увидеть, которую может редактировать только банк?

Хранение информации о сессии/состоянии

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

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

На заметку: Посетите новостной сайт, у которого есть подписка и откройте ветку тегов (например, The Age). Продолжайте посещать сайт в течение нескольких часов/дней. В итоге вас начнёт перенаправлять на страницы, объясняющие, как оформить платную подписку, а сами статьи станут вам недоступны. Эта информация является примером сессии, сохранённой в куки-файлах.

Уведомления и средства связи

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

Вот несколько примеров:

На заметку: Самый распространённый вид уведомлений – это «подтверждение регистрации». Возьмите почти любой интересующий вас крупный сайт (Google, Amazon, Instagram и т. п.) и создайте новую учётную запись, используя ваш адрес электронной почты. Вскоре вы получите письмо, подтверждающее факт вашей регистрации или содержащее информацию о необходимости активировать вашу учётную запись.

Анализ данных

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

Например, и Amazon, и Google рекламируют товары на основании предыдущих поисков (и покупок).

На заметку: Если вы пользуетесь Facebook, зайдите на вашу стену и посмотрите на ряд постов. Заметьте, что некоторые посты не идут по порядку: в частности, посты с большим количеством «лайков» часто находятся выше по списку, чем остальные. Также взгляните на рекламу, которую вам показывают, вы вероятно увидите рекламу товаров, которые искали на других сайтах. Алгоритм Facebook для выделения контента и рекламы может казаться мистикой, но очевидно, что он зависит от ваших лайков и запросов поиска!

Подведение итогов

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

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

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

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

Источник

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

серверная часть приложения это. 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 не будет опубликован. Обязательные поля помечены *