От современного C++ к современным веб-приложениям

(Эта история — репост из моего блога)

ЗАЯВЛЕНИЕ ОТ ОТВЕТСТВЕННОСТИ. Эта статья содержит грубые выражения и слишком много раз встречается слово "фреймворк".

Прошли годы с тех пор, как я не занимался программированием для Интернета.

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

Очевидно, мне нужно было освежить свои знания по этому вопросу.

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

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

Но сначала краткое напоминание о том, как устроены наши десктопные и мобильные приложения.

Типичное приложение MVC C++ для рабочего стола или мобильного устройства.

Неважно, делаете ли вы игру, фоторедактор или приложение для хранения порно, когда есть графический интерфейс и состояние приложения, которые нужно каким-то образом сохранить, вы обнаружите, что это хорошая привычка делить свои программа в организации Model-View-Controller. Или одно из его производных: MVP, MVVM, IMMVP,… Хорошо, я только что придумал последнее, извините.

Позвольте мне напомнить вам, как это обычно работает.

Святая Модель

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

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

«Будь красивой и заткнись»: The View

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

То, как представление берет данные из модели, зависит от типа используемого шаблона. Например, в такой среде, как Cocoa от Apple, вы должны разработать представление с помощью удобного интерфейса перетаскивания и связать его поля со слотами в вашем объекте ViewController. Таким образом, представление достаточно мелкое, чтобы его можно было сериализовать в XML-файле, и оно предназначено только для определения макета. Это просто «Presenter», отсюда и название MVP.

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

Настоящий ум: Контроллер

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

В MVP именно контроллер будет наблюдать за моделью на предмет любых изменений, которые необходимо отразить в представлении. В типичном MVC представление будет отвечать за наблюдение за моделью. Поскольку это может сбить людей с толку относительно того, кто что делает, мне больше нравится подход MVP. Иногда я говорю MVC, но в уме я на самом деле имею в виду MVP. Но не волнуйся, ты умный, разберешься. А так как вы разработчик C++, вы все равно уже все это знаете.

Действие-реакция туда и обратно

Давайте резюмируем, что происходит, когда я нажимаю «удалить запись» в представлении моей коллекции. Надеюсь, вам понравятся мои художественные навыки в стиле ретро ASCII!

Что можно резюмировать как:

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

Ознакомьтесь с документацией Apple по MVC, чтобы узнать об этом еще раз.

Отзывчивость графического интерфейса

При всем уважении к старшим поколениям, мне не нужен их пользовательский интерфейс. Вы знаете, те уродливые кнопки, которые застряли, пока какое-то действие выполнялось в фоновом режиме?

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

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

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

Клиент-серверные приложения

Довольно часто корпоративное программное обеспечение также сильно зависит от централизованной модели, живущей на сервере.

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

Завершение

Итак, вот состояние десктопных (и мобильных) приложений, какими я их знаю:

  • У нас есть хорошо зарекомендовавшие себя фреймворки, такие как Qt, JUCE или Apple Cocoa SDK, которые действительно ускоряют разработку настольных приложений.
  • Для изучения лучших практик и дизайна требуется некоторое время, но как только вы их освоите, вы поймете, что они не изменятся радикально.
  • Единственная система, в которую мы должны вписаться, чтобы люди могли использовать наше приложение, — это операционная система. Ничто не заставляет вас использовать определенный протокол для связи с вашими серверами.

и это то, что я хочу иметь в виду, прыгая в веб-торнадо.

Переход в Интернет

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

Но давайте сначала сделаем резервную копию и воспроизведем историю веб-приложений, не так ли?

MVC на стороне сервера

В основе всех веб-приложений лежит необходимость формировать страницы HTML/CSS, которые являются основными объектами, которые может отображать браузер.

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

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

Запрошенный вами URL говорит мне, что вы хотите видеть последние транзакции по вашему банковскому счету? Очень хорошо, позвольте мне получить их из нашей БД и создать страницу, содержащую список, из шаблона, который я подготовил специально для этого случая!

Помню, в то время мне постоянно предлагали поработать над проектами PHP и J2EE. Такие модные словечки, как LAMP (Linux-Apache-MySQL-PHP), были повсюду. Аааааа. Ностальгия!

В настоящее время такие фреймворки, как Ruby On Rails или Phoenix (мой любимый!) позволяют действительно легко разрабатывать такие серверные части.

Скриптовый клиент

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

К счастью, в браузере уже была поддержка языка сценариев под названием JavaScript (позже стандартизированного как ECMAScript), и разработчики поняли, что могут использовать его функциональные возможности для асинхронного запроса данных с сервера и изменения узлов, составляющих HTML-страницу (страницы HTML). DOM, объектная модель документа) с этими данными. Это называется AJAX, и самая популярная библиотека, используемая для этого, — jQuery.

В начале этой статьи показан типичный вызов AJAX, грязный, но функциональный:

$(document).ready -›

photoHTML = (фото) =›

“‹li› ‹a id='photo_#{photo.id}' href='#{photo.url}'› ‹img src='#{photo.url}' alt='#{photo. alt}' /› ‹/a› ‹/li›”

$.ajax

ссылка: ‘/photos’

введите: "ПОЛУЧИТЬ"

contentType: ‘application/json’

при успехе: (ответ) =›

для фото в response.photos

узел = $(photoHTML(photo)).appendTo($("#photos-list"))

node.on(‘click’, (e) =›

e.preventDefault()

node.find(‘img’).prop(‘src’, photo.url + ‘.grayscaled.jpg’)) )

при отказе: =›

$("#photo-list").append("‹li› Не удалось загрузить фотографии. ‹/li›")

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

MVC на стороне клиента

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

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

Такие фреймворки, как AngularJS, Backbone.js или Ember.js (и многие другие), предназначены для структурирования вашего SPA (одностраничного приложения). У них есть повторно используемые компоненты представления, архитектура тестирования, адаптеры для преобразования ваших внутренних данных в классы моделей, активные сообщества и множество подключаемых модулей.

«Язык ассемблера Интернета»

Если вы похожи на меня, то сейчас вы, вероятно, думаете: «Поскольку все работает с JavaScript, то я могу просто скомпилировать любой язык, который мне нравится, в JavaScript с помощью цепочки инструментов LLVM и вперед!».

Что ж, Google годами компилировал Java в JavaScript, а с C/C++ это именно то, что делает комбинация Emscripten + ASM.js. Это все еще довольно ранняя стадия, но с многообещающими результатами.

Так что если вы разработчик игр и хотите перенести проект в браузер, это хорошая новость!

«Внешние серверы» и изоморфизм

Но если вы строите новый бизнес-убийцу Facebook, как половина команд, которые соревнуются в конкурсе стартапов, вы не станете знаменитым и не снимете фильм после своей жизни, если никто не сможет найти вас в поисковик…

Поисковая оптимизация (SEO) — это то, что помогает клиентам найти вас и естественным образом прийти к вам. Но с полным интерфейсом JavaScript поисковые роботы не могут прочитать вашу страницу. Потому что они не интерпретируют это.

Чтобы справиться с этим ограничением, веб-разработчики все больше и больше полагаются на среду выполнения JavaScript на стороне сервера. Вы, наверное, слышали о Node.js. Это среда выполнения сервера, которая выполняет JavaScript. Следуя этой логике, вы сможете написать JS-код, который будет использоваться как на сервере, так и в браузере. Это то, что называется изоморфизмом. Вам не нужно полностью отделять код клиента от кода сервера. Как только NodeJS сгенерирует первый рендеринг вашей страницы, он будет передан в браузер, который продолжит выполнение остальной части вашего интерактивного JS-приложения.

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

Умники из AirBnb очень хорошо описали это в этой статье.

Применяя это

Подводя итог, если мы, разработчики C++, хотим разработать веб-приложение, мы можем:

  1. Перенесите существующее приложение C++ на JavaScript, используя Emscripten и ASM.js, с риском быть заблокированным поисковыми системами, потому что наша страница недоступна для поиска.
  2. Создайте внешний интерфейс в два этапа с предварительным рендерингом на стороне сервера, что позволит использовать SEO.

Теперь, если вы похожи на меня, вы можете подумать: «Чем меньше мне приходится трогать JavaScript, тем я счастливее». И я почти уверен, что тысячи разработчиков могут понять это. Буквально вчера наткнулся на этот твит:

Причина, по которой javascript отстой…

И, в некотором смысле, это может быть не проблемой, потому что мы можем просто перевести любой язык, поддерживаемый emscripten, в JS при создании модуля NodeJS нашего приложения. Он просто выдал бы некоторый JavaScript, который мог бы работать ваш интерфейсный сервер и браузер.

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

Так что я пока стисну зубы и попробую поиграться с Node и React, начав с этого руководства по Node и React. Он использует библиотеку под названием React.js, представленную Facebook в последние месяцы. Это помогает вам создавать многоразовые представления, которые вы предварительно визуализируете в Node.

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

А пока вернемся к C++!

(Следующую статью из этой серии смотрите здесь!)