- Типовой шаблон (каркас) windows-приложения (тшп)
- Изучение свойств типового шаблона
- Изучить листинг типового шаблона
- Простой каркас Android приложения
- Сервис на языке Dart: каркас серверного приложения
- Подготовка
- Установка Aqueduct
- Генерация приложения
- Конфигурация
- Инъекция зависимостей
- Cетевой слой
- Первый запуск
- Упаковка в контейнер
- Подключение базы данных
- Публикация
Типовой шаблон (каркас) windows-приложения (тшп)
Изучение свойств типового шаблона
Изучить листинг типового шаблона
Ниже приведено описание структуры Windows-приложения с типовым минимальным набором средств, включая средства графического интерфейса (окно, системные кнопки и системное меню).
Текст, похожий на приведенный здесь, можно получить автоматически, используя мастер Win32 Application (Hello). Однако он будет отличаться от приведенного здесь (об отличиях см. ШаблонHello).
#include //указания на более чем 30 отдельных хедеров с описаниями > 2000
//функций, 3000 констант и др. описания Windows
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
char szWindowClass [] = «myWindowClass»;
int WINAPI WinMain (HINSTANCE hInst, //идентификатор текущего экземпляра приложения
HINSTANCE hPreInst, //всегда NULL
LPSTR lpszCmdLine, //указатель на командную строку приложения
int nCmdShow) //способ изображения окна при первом выводе
// TODO: Place code here:
HWND hWnd; //дескриптор, хендл, идентификатор окна
MSG lpMsg; //структура для хранения параметров сообщений
// Initialize global strings
WNDCLASS wcApp; //структура для хранения параметров класса (стиля) окна
wcApp.lpszClassName = szWindowClass; //имя класса окна
wcApp.hInstance = hInst; //дескриптор этого приложения., регистрирующий класс окна
wcApp.lpfnWndProc = WndProc; //указатель на функцию-обработчик сообщений окна
wcApp.hCursor = LoadCursor(NULL, IDC_ARROW); //тип курсора окна (стрелка)
wcApp.hIcon = 0; //вид пиктограммы при выводе окна в свернутом виде (здесь без)
wcApp.lpszMenuName = 0; //строка – имя ресурса меню (здесь без меню)
wcApp.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); // цвет для фона
//wcApp.hbrBackground = GetStockObject(WHITE_BRUSH); // кисть по умолчанию
// см. [У. Мюррей, с.90]. CS_DBCLKS, CS_KEYCVTWINDOW !
wcApp.style = CS_HREDRAW|CS_VREDRAW; //перерисовывать окно при resize
wcApp.cbClsExtra = 0; //число доп. байт, выделяемых структуре WNDCLASS
wcApp.cbWndExtra = 0; //число доп. байт для всех доп. структур
if ( ! RegisterClass (&wcApp)) //регистрация окна, описанного в структуре по адресу &wcApp
// Perform application initialization: создает окно и возвращает его идентификатор hWnd
hWnd = CreateWindow (szWindowClass, //имя зарегистрированного класса окна
«A Template», //заголовок (название) окна
WS_OVERLAPPEDWINDOW,//стиль окна (перекрыв.&сист.меню&кнопки maxmin)
CW_USEDEFAULT, //х – начальное положение окна по оси Х
CW_USEDEFAULT, //y – начальное положение окна по оси Y
CW_USEDEFAULT, //ширина окна в ед. устройства
CW_USEDEFAULT, //высота окна в ед. устройства
(HWND)NULL, //указатель на родит. окно (у перекрывающегося окна его нет)
(HMENU) NULL, //зависит от стиля окна – указывает его меню
hInst, //определяет копию модуля, связанного с окном (кто создал)
NULL); //адрес доп. информации, нужной для создания окна
ShowWindow (hWnd, nCmdShow); //вывод окна
UpdateWindow (hWnd); //перерисовка окна, т.к. генерируется сообщение WM_PAINT
// Main message loop: копирует очередное сообщение в структуру по адресу &lpMsg
//т.е. передает его в основной блок программы без фильтрации – 0,0
while (GetMessage (&lpMsg, NULL, 0, 0))
TranslateMessage (&lpMsg); //если нужно — преобразует виртуальн. клавиши в символы
DispatchMessage (&lpMsg); //передача сообщения соответствующей функции окна
// ФУНКЦИЯ ГЛАВНОГО ОКНА (функция окна = оконная функция = функция обратного вызова).
// Имя функции (здесь WndProc) выбирает пользователь.
// PURPOSE: Processes messages for the main window:
// WM_COMMAND — process the application menu,
// WM_PAINT — paint the main window,
// WM_DESTROY — post a quit message and return.
LRESULT CALLBACK WndProc (HWND hWnd, //дескриптор окна, получившего сообщение
UINT messg, //сообщение для обработки
HDC hdc; //дескриптор контекста изображения (устройства для этого окна)
PAINTSTRUCT ps; //структура, хранящая информацию о клиентской области окна
case WM_PAINT: //первый раз это сообщение вызывается функцией UpdateWindow()
//подготовка окна hWnd к выводу информации, получение дескриптора устройства hdc
hdc = BeginPaint (hWnd, &ps);
//——Начало фрагмента пользователя
//——Конец фрагмента пользователя
ValidateRect (hWnd,NULL); //перерисовка не нужна
EndPaint (hWnd, &ps); //завершение отрисовки в окне и освобождение hdc
case WM_DESTROY: //если команда ГМ-Выход
default: //очистка очереди от необработанных сообщений
return ( DefWindowProc (hWnd, messg, wParam, lParam));
Простой каркас Android приложения
Подготовка
- Скачиваем и импортируем в рабочее пространство проекты actionbar-sherlock и sliding-menu.
- Для проекта sliding-menu устанавливаем зависимость от проекта actionbar-sherlock.
При установке зависимостей можно выскочить ошибка, что jar файлы имеют разные SHA. В таком случае удалите файлы android-support-v4.jar из папки libs проекта sliding-menu - Создаем новый проект для своего приложения, как обычно. При установке выбираем Minimum-Required-SDK как API8 (android-2.2) и Target SDK какой требуется (API14 и выше). Устанавливаем нашему проекту зависимости от проектов actionbar-sherlock и sliding-menu.
Возможно, что будет опять конфликт jar файлов, как в пункте выше. Тогда просто удалите из проекта своего приложения файл android-support-v4.jar в папке libs.
Теперь почти все готово для создания нашего приложения, но если Вы запустите проект на android-2.2 и android-4 то увидите, что приложения выглядят абсолютно по-разному. Чтобы это исправить провернем несколько несложных манипуляций:
- В папке res создадим папку drawable
- Из папки drawable-mdpi перенесем файл иконки приложения (по-умолчанию eclipse обзывает его ic_launcher.png) в папку drawable
- Удалим папки drawable-XXX и папки values-XXX (мое мнение, что данные папки должны создаваться по мере необходимости и/или предрелизной подготовки приложения)
- В файл res/values/styles.xml укажем, что стиль AppBaseTheme будет наследоваться от стиля style/Theme.Sherlock.Light
Сама соль
У нас все готово для создания приложения – у нас одинаковый внешний вид приложения в стиле 4-го android и это все одинаково прекрасно смотрится и на android-2.2.
На данный момент у нас не хватает только бокового меню. Для его реализации создадим файл разметки в res/layout и назовем его, к примеру, sidemenu.xml. Пока трогать его не будем.
Перейдем к главной activity (если не создано, создайте). Наша activity будет наследоваться не от стандартного класса activity, а от класса SherlockFragmentActivity.
В методе onCreate опишем реализацию нашего бокового меню:
После этих действий у нас готова болванка для создания приложения, которое имеет боковое меню (не забудьте внести свою разметку в файл res/layout/sidemenu.xml), которое открывается/закрывается по «потягиванию» вправо/влево, а так же наше приложение имеет одинаковый внешний вид для всех версий android. В качестве приятного бонуса мы получили еще и полностью кастомизируемый actionbar (о нем я постараюсь рассказать в следующем посте).
Приложу дополнительно исходник такого болванистого проекта: Скачать
В итоге болванка будет выглядеть примерно так:
Всем спасибо за внимание и приятной разработки!
Сервис на языке Dart: каркас серверного приложения
- 1. Введение
- 2. Backend
- 2.1. Инфраструктура.
- 2.2. Доменное имя. SSL
- 2.3. Каркас серверного приложения (мы находимся здесь)
- .
- 3. Web
- 3.1. Flutter web-страница
- .
- 4. Mobile
- .
Подготовка
В прошлый раз мы закончили на том, что разместили статическую веб страницу-заглушку, разработанную с использованием Flutter для web. Страница отображает прогресс разработки нашего сервиса, однако данные о датах начала разработки и релиза пришлось захардкодить в приложении. Таким образом мы лишились возможности изменить сведения на странице. Пришло время разработать приложение — сервер данных. Схема всех приложений сервиса — в статье «Сервис на языке Dart: введение, инфраструктура бэкэнд».
В этой статье мы напишем приложение с использованием фреймворка Aqueduct, оценим его производительность и потребление ресурсов в разных режимах, напишем инструментарий для компиляции в нативное приложение для Windows и Linux, разберемся с миграциями схемы базы данных для доменных классов приложения и даже опубликуем наш инструментальный docker образ в публичный регистр DockerHub.
- все скриншоты с кодом «кликабельные»
- полный код здесь
- консольные команды собраны в README.md проекта
- комментарии, вопросы, советы — приветствуются
- пообщаться с автором можно в Телеграмм канале
Установка Aqueduct
Начнем с установки dart-sdk — набора средств разработки на языке Dart. Установить его можно с использованием пакетного менеджера вашей операционной системы как предложено здесь. Однако, в случае Windows никакого пакетного менеджера в вашей системе по умолчанию не установлено. Поэтому просто:
- Скачаем архив и распакуем его на диск C:
- Теперь, чтобы наша операционная система знала, где искать исполняемые файлы, добавим необходимые пути. Откроем переменные ОС. Для этого начнем вводить «изменение переменных среды текущего пользователя» в строке поиска
Теоретически можно установить локально также сервер баз данных PostgreSQL. Однако Docker позволит нам избежать этой необходимости и сделает среду разработки подобной среде выполнения на сервере.
Генерация приложения
Итак, откроем папку нашего сервера в VsCode
Для тех, кто не видел первую и вторую статьи, исходный код можно склонировать из guthub репозитория:
Создадим шаблон приложения:
Ознакомимся с содержимым шаблона проекта:
- README.md — заметка с описанием, как работать с aqueduct проектом, запускать тесты, генерировать API документацию и пр. Вспомогательный файл.
- pubspec.yaml — спецификция для пакетного менеджера pub. Здесь находятся сведения об используемых пакетах, названии, описании, версии проекта и пр.
lib/channel.dart — Фактически ApplicationChannel — это и есть экземпляр нашего приложения. Aqueduct умеет запускать несколько таких экземпляров для более эффективной утилизации ресурсов CPU и RAM. Такие экземпляры работают в изолированных потоках (в Dart их называют isolate) и никак (почти) не могут взаимодействовать друг с другом.
lib/data_app.dart — файл инкапсуляции импортов зависимостей. Позволяет объединить необходимые пакеты в условную (library) библиотеку dart_app
Конфигурация
Первая задача, которую предстоит решить, — настройка приложения при запуске. Aqueduct имеет встроенный механизм извлечения параметров из конфигурационных файлов, но этот механизм не очень удобен при запуске в Docker контейнере. Мы поступим иначе:
- Передадим список переменных в операционную систему контейнера.
- При запуске приложения внутри контейнера прочитаем переменные окружения операционной системы и используем их для первоначальной настройки.
- Создадим маршрут для просмотра по сети всех переменных окружения запущенного приложения (это будет полезно при просмотре состояния приложения из панели администратора).
В папке /lib создадим несколько папок и первый репозиторий для доступа к переменным окружения:
EnvironmentRepository в конструкторе считывает переменные окружения из операционной системы в виде словаря Map и сохраняет в приватной переменной _env. Добавим метод для получения всех параметров в виде словаря:
lib/service/EnvironmentService — логический компонент доступа к данным EnvironmentRepository:
Инъекция зависимостей
Здесь необходимо остановиться и разобраться с зависимостями компонентов:
- сетевому контроллеру потребуется экземпляр сервиса переменных,
- сервис должен быть единственным для всего приложения,
- для создания сервиса необходимо предварительно создать экземпляр репозитория переменных.
Эти задачи решим с помощью библиотеки GetIt. Подключим необходимый пакет в pubspec.yaml:
Создадим экземпляр контейнера инжектора lib/di/di_container.dart и напишем метод с регистрацией репозитория и сервиса:
Метод инициализации контейнера DI вызовем в методе подготовки приложения:
Cетевой слой
lib/controller/ActuatorController — сетевой http компонент. Он содержит методы доступа к служебным данным приложения:
Задекларируем обработчики маршрутов для контроллеров в lib/controller/Routes:
Первый запуск
Для запуска необходимо:
- приложение упаковать в Docker образ,
- добавить контейнер в сценарий docker-compose,
- настроить NGINX для проксирования запросов.
В папке приложения создадим Dockerfile. Это скрипт сборки и запуска образа для Docker:
Добавим контейнер приложения в сценарий docker-compose.yaml:
Создадим файл data_app.env с переменными конфигурации для приложения:
Добавим новый location в отладочный конфиг NGINX conf.dev.d/default.conf:
Запускаем отладочный сценарий с флагом предварительной сборки образов:
Сценарий успешно запустился, но настораживают несколько моментов:
- официальный образ со средой dart от google занимает 290MБ в виде архива. В распакованном виде он займет кратно больше места — 754МБ. Посмотреть список образов и их размер:
В нагрузочном тесте (только сетевые запросы GET /api/actuator/) потребление памяти находится в диапазоне 350—390 МБ для приложения, запущенного в одном изоляте
Предположительно ресурсов нашего бюджетного VPS не хватит для работы такого ресурсоемкого приложения. Давайте проверим:
- Создадим на сервере папку для новой версии приложения и скопируем содержимое проекта
Выполним docker-compose сценарий в папке /opt/srv_2/
Так выглядит сборка приложения перед запуском:
А так — в работе:
Из доступного 1ГБ оперативной памяти наше приложение потребляет 1,5ГБ «заняв» недостающее в файле подкачки. Да, приложение запустилось, но ни о какой нагрузочной способности речь не идет.
Остановим сценарий:
Нам предстоит решить три задачи:
- снизить потребление оперативной памяти dart приложением,
- уменьшить время запуска,
- снизить размер docker-контейнера приложения.
Решением станет отказ от dart в рантайме. Начиная с версии 2.6, dart-приложения поддерживают компиляцию в нативный исполняемый код. Aqueduct поддерживает компиляцию начиная с версии 4.0.0-b1.
Начнем с локального удаления aqueduct CLI:
Установим новую версию:
Поднимем зависимости в pubspec.yaml:
Соберем нативное приложение:
Результатом будет однофайловая сборка data_app.aot размером около 6 МБ. Можно сразу запустить это приложение с параметрами, например:
Потребление памяти сразу после запуска — менее 10 МБ.
Посмотрим под нагрузкой. Параметры теста: сетевые запросы GET /actuator, 100 потоков с максимальной доступной скоростью, 10 минут. Результат:
Итого: средняя скорость — 13к запросов в сек для тела ответа JSON 1,4кВ, среднее время ответа — 7 мсек, потребление оперативной памяти (на два инстанса) 42 MB. Ошибок нет.
При повторном тесте с шестью инстансами приложения средняя скорость, конечно, повышается до 19к/сек, но и утилизация процессора достигает 45% при потреблении памяти 64 МБ.
Это превосходный результат.
Упаковка в контейнер
Здесь мы столкнемся еще с одной сложностью: скомпилировать dart-приложение в натив мы можем только под текущую ОС. В моем случае это Windows10 x64. В docker-контейнере я, конечно, предпочел бы один из дистрибутивов Linux — например, Ubuntu 20.10.
Решением здесь станет промежуточный docker-стенд, используемый только для сборки нативных приложений под Ubuntu. Напишем его /dart2native/Dockerfile:
Теперь соберем его в docker-образ с именем aqueduct_builder:4.0.0-b1, перезаписав, если есть, старые версии:
Напишем сценарий сборки нативного приложения docker-compose.dev.build.yaml:
Запустим сценарий сборки:
Файл скомпилированного под Ubuntu приложения data_app.aot занимает уже 9 МБ. При запуске утилизирует 19 МБ оперативной памяти (для двух инстансов). Проведем локальное нагрузочное тестирование с теми же условиями, но в контейнере с проксированием NGINX (GET, 100 потоков):
В среднем 5,3к запросов в секунду. При этом потребление оперативной памяти не превысило 55 МБ. Размер образа уменьшился по сравнению с установленным dart и aqueduct c 840 МБ до 74 МБ на диске.
Напишем новый сценарий docker-compose.aot.yaml запуска приложения. Для этого заменим блок описания data_app, установив базовым образ “пустой” Ubuntu:20.10. Смонтируем файл сборки и изменим команду запуска:
Решим еще одну сервисную задачу: фактически сборочный docker-образ c установленными dart и aqueduct — вполне себе переиспользуемый инструмент. Имеет смысл выгрузить его в общедоступный регистр и подключать как готовый скачиваемый образ. Для этого необходимо:
- зарегистрироваться в публичном регистре, например, DockerHub,
- авторизоваться локально с тем же логином
переименовать выгружаемый образ по схеме login/title:tag
выгрузить образ в регистр
https://hub.docker.com/repository/docker/andx2/aqueduct/general
Теперь можно изменить наш сценарий сборки для использования публичного образа
Подключение базы данных
В aqueduct уже встроен ORM для работы с базой данных PostgreSQL. Для его использования необходимо:
- Создать доменные объекты, описывающие записи в базе данных.
- На основе доменных объектов и связей между ними сгенерировать файл миграции. Заметка: для записи в базу данных необходимо, чтобы в БД были подготовлены таблицы, чьи схемы подходят для хранения доменных объектов. Aqueduct предоставляет инструмент миграции, который фактически обходит все классы проекта, являющиеся расширением ManagedObject (управляемые объекты), прочитывает типы их полей и связей с другими управляемыми объектами и создает специальный файл, в котором описано изменение схемы таблиц и связей в базе данных по сравнению со схемой предыдущего файла миграции. Файлы миграции добавляются при каждой перегенерации схемы.
- Применить файлы миграции к базе данных. Применение происходит последовательно, начиная с версии, которая записана в БД текущей.
- В файлы миграции, сгенерированные aqueduct, можно вносить свои изменения, например определить реализацию метода seed() — для добавления в БД каких-либо начальных данных.
- Генерация и применение миграций производится aqueduct CLI.
Начнем с подключения нового docker-контейнера с БД PostgreSQL в сценарии docker-compose.aot.yaml. Готовый образ на основе Linux Alpine («компактная» версия Linux для встраиваемых применений):
Здесь необходимо обратить внимание на файл переменных окружения data_db.env. Дело в том, что образ заранее настроен на использование этих переменных в качестве имени пользователя, хоста, порта и пароля доступа. Добавим эти переменные в файл:
Значения приведены условно.
Также смонтируем папку хоста ./data_db/ в контейнер для хранения данных БД.
Далее в приложении data_app добавим класс /service/DbHelper для подключения к базе данных, используя переменные окружения:
Создадим доменный объект, управляемый ORM, для получения настроек клиентского приложения:
Добавим репозиторий и сервис для добавления настроек и получения актуальной версии:
Зарегистрируем новые компоненты в DI контейнере:
Добавим новый контроллер и эндпойнт в маршрутизатор:
Теперь сгенерируем файл миграции базы данных. Выполним:
Результатом будет создание файлов миграции в папке проекта:
Теперь нужно решить сервисную задачу: миграции нужно применять из системы с установленным aqueduct (и dart) к базе данных, работающей в контейнере, и это нужно выполнять как при локальной разработке, так и на сервере. Используем для этого кейса ранее собранный и опубликованный образ для AOT-сборки. Напишем соответствующий docker-compose сценарий миграции БД:
Интересная деталь — строка подключения к БД. При запуске сценария можно передать в качестве аргумента файл с переменными окружения, а затем использовать эти переменные для подстановки в сценарии:
Также обратим внимание на флаги запуска:
- —compatibility — совместимость с версиями docker-compose сценариев 2.х. Это позволит использовать параметры deploy для ограничения использования ресурсов контейнером, которые игнорируются в версиях 3.х. Мы ограничили потребление оперативной памяти до 200МБ и использование процессорного времени до 50%
- —abort-on-container-exit — этот флаг устанавливает режим выполнения сценария таким образом, что при остановке одного из контейнеров сценария будут завершены все остальные. Поэтому, когда выполнится команда миграции схемы базы данных и контейнер с aqueduct остановится, docker-compose завершит также и работу контейнера базы данных
Публикация
Для подготовки к публикации приложения необходимо:
- Изменить переменные окружения в data_app.env и data_db.env. Напомню, что сейчас у нас POSTGRES_PASSWORD=postgres_password
- Переименовать сценарий запуска нативного приложения docker-compose.aot.yaml в docker-compose.yaml. Команда запуска приложения на сервере не должна иметь аргументов
- Временно заблокировать маршрут просмотра переменных окружения запущенного приложения /api/actuator. В следующей статье мы реализуем механизм авторизации по ролям и откроем доступ к этому маршруту только для администратора.
Скопируем на сервер папку приложения ./data_app/. Важным моментом здесь будет ключ -p (копировать с сохранением атрибутов файлов) в команде копирования. Напомню, что при сборке нативного приложения мы установили права на исполнение файлу data_app.aot:
- Измененную конфигурацию NGINX ./conf.d/default.conf
- Сценарии запуска и миграции docker-compose.yaml, docker-compose.migrations.yaml
- Файлы с переменными окружения data_app.env и data_db.env
Добавим на сервере папку /opt/srv_1/data_db. Это том файловой системы хоста для монтирования в контейнер базы данных. Здесь будут сохраняться все данные PostgreSQL.
Выполним сценарий миграции схемы базы данных: