Архитектура server-side приложения
Мир кодерам.
По долгу службы я бОльшую часть времени имею дело с client-side приложениями, поэтому возник такой вопрос. Я делаю собственный проект, без использования фреймворков по идеологическим причинам. Архитектуру серверной части сайта продумываю сам и хотелось бы получить несколько советов от людей, разбирающихся в этом предмете, до того как начать её создавать. Наблюдая за другими проектами, мне понравился способ, при котором страница, на которую пришел запрос, сама вытягивает нужные ей данные. Причем, на разных проектах это выглядит по-разному: 1. Шаблон напрямую обращается к источникам данных (базе, например). 2. Шаблон дергает некую ручку (приложение), которое обращается к данным, формирует их в нужном формате и возвращает. 3. Приложение (а не шаблон), получает нужные данные из источников и передаёт их одной пачкой в шаблон. Это самые частые механизмы генерации страниц, которые мне попадались. Есть один важный момент, который мне нужно учесть: данные могут быть запрошены как для шаблона, так и в чистом виде. Например: на странице есть блок - ТОП10 мудаков сайта. Список мудаков нужно отдать в шаблон, при запросе содержащей её страницы. Затем, каждые 10 минут необходимо обновлять блок AJAX-ом, т.е. получить по-сути те же данные, которые отдаются в шаблон перед рендерингом. Исходя из этого требования, становится понятно, что первый и третий варианты не подходят - придется дублировать агрегацию данных для шаблонов и AJAX-запросов. В первом варианте шаблон сам агрегирует нужные ему данные. Во втором этим занимается отдельное приложение. А кто будет это делать для AJAX? Не круто. Второй вариант подходит. Но. Что, если шаблон не умеет дергать ручки? Например, dust, TSN. Да, они могут вызывать функции, но результат при этом должен вернуться синхронно, а в NodeJS (мой проект на ноде) этого практически никогда не бывает. Поэтому я ничего лучше не придумал, как объединить второй и третий вариант. Алгоритм: 1. Приходит GET-запрос на роутер (в моем случае это nginx). Роутер определяет сервис (раздел или подраздел сайта, к примеру), который обрабатывает этот запрос и проксирует ему. 2. Сервис, получив запрос, определяет какой шаблон с какими данными необходимо вернуть. 3. Дергает нужные ручки, в надежде получить от них данные в JSON. 4. После получения всех данных рендерит шаблон и возвращает результат. Для получения AJAX-ответа достаточно будет создать сервис AJAX, который будет дергать нужную ручку и возвращать полученные от неё данные. Можно считать, что ручка - это модель, сервис - контроллер, шаблон - представление. Контроллер запрашивает только нужные данные, которые могут не нуждаться в представлении (как в сервисе AJAX). Кто-нибудь имел дело с такой архитектурой? Представьте, что вы устроились на работу в проект, где всё так устроено. Хотелось бы вам работать в таком окружении? |
UPD: Буду рад услышать ваши варианты, но с аргументами.
|
Цитата:
У меня не nginx решает, а php куда дальше передавать запрос или исполнение задачи. Запрос разбивается на части и определяется раздел и прочие данные. В самом простейшем случае подходит банальный switch, в сложном то, что описал ты. С более сложным не сталкивался. |
Немного поясню про nginx. Там, собственно, никакой магии нет:
Код:
location /serviceName/ { Код:
Request -> Nginx -> Memcached -> Nginx -> Response. |
Цитата:
|
Больше никому нечего добавить? Хотя это не удивительно - здесь большинство клиентских кодеров. Но ведь должны же быть ещё знающие люди.
|
![]() very complexity |
Vantedur, на картинке ничего не видно.
|
там личные имена, а вообще это мануал ядра для совсем профи, всю систему писал сперва чисто для профи а уже потом построил поверх конструктор. сейчас виду работу над изготовлением всевозможных готовых комплектов, ну чтоб чайники могли сразу юзать.
|
И как это относится к теме поста?
|
Цитата:
|
Maxmaxmахimus, не умничай, сначала пойми о чем речь - потом комментируй.
|
Цитата:
|
Спасибо тебе, за старания и потраченное время.
А теперь прочитай название темы ещё разок: "Архитектура server-side приложения". Зачем ты сюда клиента прикрутил - для меня загадка. На будущее: я в курсе как реализовать общение клиента с сервером, как реализовать API, в каком формате гонять данные, какие события вызывать. Не стоит мне расписывать очевидные вещи. И не пиши больше в этой теме - ты мне точно ничем здесь не поможешь. Спасибо за понимание. |
B~Vladi,
всё, что могу посоветовать - напишите лучше на форумах по perl, php, etc. у нас целый отдел делает серверную часть, пишут на перле. это действительно сложный процесс =) |
Цитата:
Цитата:
|
В том что вы описали можно узреть аналогию с популярными патернами
================================================== ============ 1) Шаблон напрямую обращается к источникам данных (базе, например). Я думал так уже не пишут, с этого начилось программирование для web, хотя нет пишут, DLE-engine так работает)) 2) Шаблон дергает некую ручку (приложение), которое обращается к данным, формирует их в нужном формате и возвращает Такое поведение характерно для паттерна MVC, так работает джумла. кстатии привет от Smarty, он тоже очень любит дёргать за всякие ручки. 3) приложение (а не шаблон), получает нужные данные из источников и передаёт их одной пачкой в шаблон. Подобное поведение характерно для паттерна MVP, так работает YII. Конечно эти сравнения с натяжкой но всё же похоже. |
DjDiablo, спасибо, с DLE и YII не работал, но в курсе.
Сейчас я остановился на третьем варианте, с учетом что мой шаблонизатор подразумевает на вход уже сформированные данные. |
Я всё таки решил закончить мысль.
Я пока отлажу в сторону архитектуру, и сосредоточусь на ином вопросе. Для начала, кто сказал что надо рендирить список мудаков и для клиента и для сервера. Помоему данное дублирование неразумно, это двойная работа при планировании, при разработке , при отладке, при доработке в следующих версиях. Предлагаю рассмотреть два случая. Я пытаюсь найти способ рендерить информацию в хтмл только в одном месте, а не в двух. 1й) Рендерим json для клиента. Это значит что на клиенте будет скрипт знающий какую ручку дёрнуть, скрипт будет регулярно запрашивать данные с сервера, соответственно на сервере будет контролёр который будет уметь эти данные отдавать. Больше нечего не надо ! Всё что нужно это засунуть скрипт-виджет в шаблон. Первую порцию данных скрипт запросит сам,а значит делать дублирующий рендер этой же информации на сервере не нужно. Вся работа сервера сведётся к возвращению данных. Всем хорош этот способ, вот только данные для клиентских плагинов, поисковики учитывать не будут Воплатить это с одинаковым успехом можно совершенно любым архитектурным паттерном. 2й) Рендерим html и для сервера, и для клиента. в этом случае подразукмевается что данные на сервере превращаются в html а не JSON или XML. Такой рендер удобен тем что он одинаков как для клиентской, так и для серверной части. То есть, есть код который генерирует хтмл, который вы потом используете по усмотрению передавая его на клиент по ajax, либо вставляя шаблон. Данный подход удобно реализуется паттерном HMVC. Реализуем архитектурным паттерном HMVC. Когда контролёр "A" вдруг обнаруживает что ему нужен результат работы контролёра "B", он просит контролёр "B" вернуть результат. Когда скрипт обновляет данные регулярно,то он обращается к контролёру "B" напрямую, get запросом. И вставляет HTML в нужное место. Реализуем научив mvc вкладывать один view внутрь другого view. Когда view "A" вдруг обнаруживает что ему внутри нужен отрендеренный "B", он рендерит B и вставляет его в себя. Когда клиентский скрипт нуждается в обновлении данных он запрашивает view "B" напрямую. Если вы рендерете хтмл на стороне сервера то при таком подходе решается проблема с поисковиками, однако теряется гибкость клиентских скриптов. |
Цитата:
Вот почти весь перечень Yandex,95.108.,77.88.,93.158. Google,66.249. Mail,94.100.,217.69.134.,217.69.136. Rambler,81.19. Yahoo!,67.195.,72.30.,74.6.,202.160. Bing,207.46.,65.52.,65.55.,157.55. Baidu,119.63.,123.125.,220.181. Ask,66.235. Ezooms,208.115.111. Aport,194.67. Ну дык вот - посколь как то делали многоаяксовый сайт, хотелось индексирование сохранить Дык для поисковиков Селектировали по IP и выделяли для них отдельный серв, цепляемый , как клиент/render к общему, который рендил и проксировал уже отренденное взятое от пользовательского серва для данных IP |
У меня напрашивается определённые мысли по решению проблемы.
Спишу поделится. Заранее извеняюсь за оффтоп. Разжовываю подробно и со всех сторон. Который раз пишу про архитектуру, и всегда получается очень много . Словами большой кусок кода описать непросто :) Итак напомню проблемы. 1)При возвращении данных в формате ajax у нас возникает проблема с поисковиками, 2)возвращая данные в html, мы обрекаем скрипт клиента работать с хтмл, что ему не всегда удобно первую проблему можно решать делая рендер для сервера и клиента отдельно , что весьма трудозатратно. Однако я сразу необратил внимание на то, что трудозатратно это только в том случае если на сервере и на клиенте разные языки, к примеру PHP + JS. Но ведь связки NODEJS+JS это не касается, тут то один язык ! ================================================== = Это значит что мы можем написать универсальный код который будет работать и на клиенте и на сервере. Звучит неплохо, можно порассуждать как сделать. ================================================== = Для начала назовём этот самый динамический фрагмент страницы виджетом, чтобы обзавестись терминологией. Первое что приходит на ум, сделать виджет большим цельным модулем, который будет подключатся к клиенту и серверу. Однако в этом случае на сервере будут подключаться обработчики событий браузера, которые там нафик не нужны, так как серверной стороне пофиг на события браузера. Да и вообще виджет может быть очень очень большим, и пихать всё в один модуль не айс. Попробуем разбить виджет на части, чтобы подключать в разных ситуациях только нужные части. За принципам разбивания далеко ходить не будем, возьмём проверенный MVC. Тогда получается следующая картина. Каждый виджет состоит из трёх частей. Модель - хранит данные и умеет с ними оперировать. VIEW - умеет рендерить данные в хтмл. Контролёр - поведение виджета (реакция на события). К серверу подключать достаточно VIEW и MODEL К клиенту VIEW,MODEL и контролёр. То есть на сервере при рендеренге страницы,мы используем VIEW которая использует модель. На клиенте используется тот же view с той же моделью, и контролёр в придачу (чтобы он мог реагировать на события). То есть в виджите есть две части(модель и view ), существующие одновременно на двух физических разделённых слоях системы. ================================================== = Как добится их применимости на клиенте и на сервере одновременно ? ================================================== = Очевидно что нужно абстракироваться от платформы. Модель может отдалится от источника данных при помощи специального класс (назовём его PROXY). Иными словами модель не будет иметь доступа к источнику данных напрямую. Она будет работать при помощи посредника, который скрывает от неё истинный источник данных. Если прокси на сервере, будет посредником между бд и моделью,то на клиенте прокси будет посредником между моделью и сервером. последний случай интересен тем, что в реальности прокси между моделью и сервером, это прокси между моделью и сервисом сервера по предоставлению данных. В этом случае на сервис придётся возложить ответственность по проверке прав доступа к данным, для каждой операции. Представление (view) может отдалится при помощи шаблонизатора и какихнибудь хелперов для рендеринга. Тоесть опять же особенности платформ мы пытаемся вынести в сторонние библиотеки, оставляя код виджета чистым от тонкостей платформ. ================================================== = как это приблизительно работает ? ================================================== = Сервер 1) Генерируется страница, на странице обнаружен виджет 2) Подгружается модель и view виджета, в VIEW запускается метод rendeIndex, который рендерит виджет 3) VIEW в процессе рендеринга использует в качествке источника данных модель, модель для загрузки данных в себя, обращается за помощью к прокси, который умеет работать с бд 4) Полностью отрендеренная страница отправляется клиенту. клиент. 1) На страницу подгружаются все востребованные клиентом скрипты.(впрочем это может происходить паралельно с шагом 2) 2) Начинается процесс инициализации, сначало ищется тег в котором находится виджет, затем инициализируется соответственный контроллёр, которому в качестве параметра передаётся ссылка на DOM элемент с виджетом. 3) контролёр, при инициализации может захотеть заполнить модель, в этом случает он даёт модели команду load, модель обращается к прокси, который обращается к сервису на сервере, на сервере происходит проверка прав на доступ, и если всё нормально то данные возвращаются прокси, и он заполняет данными модель. 4) дальше контролёр подписывается на все интерисующие его события. 5) Контролёр ждёт событий. Первый раз рендерить виджет не надо, так как он уже отрендерен на сервере. Когда произойдёт события контролёр просто вызовет соответствующий метод у view для того чтобы он отобразил данные из модели. ================================================== = Какой профит ? ================================================== = 1) Решили проблему с поисковиками. - Виджеты рендерятся на сервере, когда генерируется страница. 2) Сокращён срок разработки - Мы не дублируем код на сервере и на клиенте. 3) Сохранена функциональность клиентских скриптов, нет обязаловки в передачи клиенту HTML, клиент получает данные в формате JSON, которые попадают в модель, клиент этими данными распоряжается по своему усмотрению. 4) Мы получили возможность создавать очень сложные виджеты, за счёт разделение кода виджета на логические части. ================================================== = Можно ли для построения сайта использовать только плагины ? ================================================== = Кто работал с MODX тот в курсе что страницы там по сути статичный HTML, а весь динамически сгенерированный контент создают снипеты. Подобный подход можно применить и здесь, возвращать только статичный html с сервера, а весь динамически создаваемый контент в нём, могут создавать плагины. То есть страница будет просто рамкой в которую встроены плагины. Конечно всё вешать на плагины не обязательно, но идея на мой взгляд забавная. Ниже я ещё допишу пару топиков с поянениями, и примерами кода. Также жду ваших соображений по выше написанному. |
Вариант реализации
Доброго дня, предложу вариант, с которым обычно работаю:
1. Запрос получает фронт-контроллер, единая входная точка веб-приложения. 2. Он определяет, какая модель данных затребована (на шаблоны ему пока побоку), в данном случае - СписокМудаков. 3. Запрашивает у Model-слоя необходимую модель. По сути - набор объектов, в родном для системы формате (в вашем случае - JavaScript-массив Мудаки[] + отчет об ошибках, если нужен: Ошибки[]). Никаких потерь на сериализацию на данном шаге! 4. Определяется шаблон. Варианты: - HTML, JSON, XML...; - редирект (этому пользователю нельзя видеть список мудаков); - сообщение об ошибке (вместо запрошенных данных) в нужном формате; - ...etc; Фишка определения шаблона именно на этом шаге (а не на первом-втором) в том, что мы адекватно можем отреагировать на ошибки формирования модели, ограничения прав доступа и т. д. 5. Модель отдается соответствующему шаблонизатору. Фишка, опять же, в том, что видов шаблонизаторов здесь можно подключить сколь угодно много (HTML, XML, JSON, ...). 6. Шаблонизатор сериализует модель должным образом и отдает клиенту. Тип подключаемого шаблонизатора может определяться параметром запроса или урлом. Например, по умолчанию генерим HTML, при наличии в запросе &format=json - генерим легковесный JSON. NGINX на входе становится не обязательным (и даже не желательным в контексте работы фронт-контроллера), но вполне может использоваться для выдачи статики. В общем, ничего ничему не мешает. Успехов в серверном JS! |
Цитата:
Цитата:
Цитата:
Цитата:
lsync, по поводу второго и третьего пунктов. При ajax-запросе всё просто, нужны одна модель - массив мудаков. При обычном запросе, помимо этого массива, нужен уже набор этих моделей. Так же и при ajax-запросах возможно понадобятся несколько моделей в одной пачке. Если определение этих наборов переложить на контроллер - он будет сильно разрастаться. Поэтому я и разделил на несколько контроллеров и назвал их сервисы. Собственно, получается то, о чем я писал в первом посте. |
Цитата:
Цитата:
2. Разделить на несколько контроллеров и назвать их сервисами - это супер, но то, что вы написали в первом посте, требует наличия внешнего роутера. Он ни к чему в данной цепочке - лучше сделать его внутренним. Причины:
Хотя есть и серьезное преимущество - внешний роутер это плюс к легкости масштабирования. Так что надо взвешивать конкретную ситуацию. Цитата:
|
Так, стоп.
Цитата:
Цитата:
Цитата:
Цитата:
|
Цитата:
Цитата:
Обычно для формирования одной страницы сайта вам придется обратиться к нескольким своим сервисам (блочный дизайн, так?), отсюда и вызов нескольких сервисов на один HTTP-запрос. И отсюда же - необходимость логики для формирования всей страницы из кирпичиков независимых сервисов: Пришло: http://domain.com/forum/page=3 Вызвали свои сервисы: - getMenu; - getUserData; - getPosts; - getAd; - get... Положили все это в дерево ответа. Отдали шаблонизатору, отрендерили, вернули результат в виде HTML страницы. Пришло: http://domain.com/api?getAd=8 Вызвали: - getAd; Положили в дерево, отрендерили в JSON, вернули. В примере выше "getAd" - ваш сервис, он независим от остального и всегда выдаёт одинаковую модель, хотя и вызывается из совершенно различных контекстов. А фронт-контроллер определяет, какой набор сервисов вызвать согласно запрошенному URL, и в каком шаблоне отрендерить результат. Для простоты понимания я бы предложил ввести еще один термин - Flow (Поток). Тогда верны утверждения: - URL определяет, какой Flow будет отработан; - Flow содержит нужный набор сервисов; - Фронт-контроллер выполняет Flow, вызывая нужные сервисы и сохраняя ответы в результирующую модель; - Фронт-контроллер определяет, корректно ли выполнен Flow, и принимает решение о выборе шаблона; - Шаблонизатор рендерит модель в указанный контроллером шаблон. |
В догонку добавлю, что несмотря на кучу терминологии и длинные объяснения принципов работы, на практике подобная система реализуется на любом языке за 15 минут (при условии наличия шблонизатора), без каких-либо фреймворков, и самое главное - весьма эффективно растет и масштабируется, без появления каши в системе.
|
lsync, я понял, мы под разными терминами подразумеваем разные вещи. Я говорил о:
Цитата:
Цитата:
Цитата:
Поэтому я и написал, что одновременно не могут быть вызваны несколько сервисов. Перед сервисами - роутер (nginx), разруливает, на какой сервис пришел запрос. Цитата:
Цитата:
Цитата:
Если такая терминология подходит, предлагаю переходить на неё. Если нет - объясни какие моменты считаешь нелогичными/непонятными. |
Ну, раз взаимопонимание достигнуто - всё супер. По сути, я хотел бы только выделить из написанного мной выше, что именно предлагаю откорректировать в исходном алгоритме:
Цитата:
Цитата:
Цитата:
Цитата:
|
Цитата:
Цитата:
Цитата:
|
Цитата:
Цитата:
Цитата:
|
Цитата:
Цитата:
Цитата:
Цитата:
|
B~Vladi,
раз уж тут такая пьянка, попробую рассказать о своей системе реализованной на собственном движке. Реализация у меня такова, кратко: 1. Шаблон, выступает в качестве client-side/virtual-client-side приложения, никак не связанного c View и ни с чем другим. Собственно он дергает ручки контролера. 2. Контролер, занимается перенаправлением требований от client-side и не более того, то-есть client-side дергает ручку контрола, контрол дергает нужную модель, на основе запроса. 3. модель, собственно занимается формированием данных, обработкой запросов и т.д. Формирует данные и отдает их уже выходному view. 4. View, собственно отвечает за то в каком виде отдать клиентской части данные, в формате JSON ( тоесть по запросу AJAX ) либо уже сформированное view представление. Вот собственно модель работы моего движка. Конечно нюансы есть где-то, но принцип примерно такой. |
Цитата:
ЗЫ: немо 3 поста (!) написал и даже нахуй никого не послал :blink: Походу настроение хорошее было... |
Цитата:
|
devote, понятно, но принципиальных отличий не заметил от того, к чему мы пришли выше.
|
ой, не MVCM а получается MVVC
|
Цитата:
|
Цитата:
|
Цитата:
Цитата:
|
Часовой пояс GMT +3, время: 07:53. |