Мы решили создать небольшую платформу для бессерверных веб-приложений на AWS. Может, правильнее было бы назвать это не фреймворком, а шаблоном — не знаю.
Но суть в том, чтобы создать основу для быстрой разработки бессерверных приложений на AWS. Код опубликован на GitHub и открыт для любых улучшений, которых будет много.
В статье пойдет речь о том, как разрабатывать и тестировать бессерверные приложения локально, о маршрутизации на фронтенде и бэкенде, о сервисах Amazon и подобных вещах.
Если кому интересно, добро пожаловать в разрез!
Что-то вроде предисловия
До недавнего времени разработка бессерверных приложений сильно осложнялась отсутствием средств полноценного локального тестирования лямбда-функций и API. При создании приложений приходилось либо все время работать онлайн, редактируя код в браузере, либо постоянно архивировать и загружать исходный код лямбда-функций в облако.Летом 2017 года произошел прорыв.
AWS создала новый упрощенный стандарт шаблонов CloudFormation, который они называют Модель бессерверного приложения (SAM) и одновременно запустили проект Сэм-локальный .
Перво-наперво.
Amazon CloudФормирование — это сервис, который позволяет вам описать всю инфраструктуру AWS, необходимую для вашего приложения, с помощью файла шаблона в формате JSON или YAML. Это очень и очень удобная вещь.
Потому что без него вам придется вручную создавать многие необходимые ресурсы через веб-консоль или командный интерфейс: лямбда-функции, базу данных, API, роли и политики.
С помощью CloudFormation инфраструктуру можно нарисовать как в специальном конструкторе, так и написать вручную по шаблону.
В любом случае конечным результатом является файл-шаблон, с помощью которого потом можно будет в пару кликов или одной командой подобрать все необходимое для приложения.
А затем при необходимости внести изменения в этот шаблон и применить их снова одной командой.
Это значительно упрощает обслуживание инфраструктуры приложений.
Оказывается, инфраструктура похожа на код. CloudFormation великолепен, его шаблоны позволять описывают почти 100% ресурсов AWS. Но из-за своей универсальности это довольно многословный формат — шаблоны могут быстро разрастаться до приличных размеров.
Признавая это и стремясь упростить создание бессерверных приложений, AWS создала новый формат. СЭМ .
Грубо говоря, можно предположить, что обычные шаблоны CloudFormation написаны на низкоуровневом языке.
А шаблоны SAM написаны на языке высокого уровня, что позволяет описывать инфраструктуру бессерверных приложений с использованием упрощенного синтаксиса.
Шаблоны SAM преобразуются CloudFront в обычные шаблоны во время развертывания.
Что это такое Сэм-локальный ? Это инструмент командной строки, который позволяет локально работать с бессерверными приложениями, описанными шаблонами SAM. Sam-local позволяет тестировать лямбда-функции, генерировать события от различных сервисов AWS, запускать API Gateway, проверять шаблоны SAM — и все это локально! Sam-local использует контейнер Docker для эмуляции API Gateway и Lambda. Принцип работы следующий.
При запуске sam-local ищет файл шаблона SAM в папке проекта.
Он анализирует файл шаблона и запускает выделенные в шаблоне ресурсы в Docker-контейнере: открывает API и подключает к ним лямбда-функции.
Причём поддержка очень близка к работе реальных лямбда-функций (показаны лимиты, объём используемой памяти и продолжительность выполнения).
Это выглядит примерно так
Далее обращение к локальному API и вызов соответствующих лямбда-функций отображается в консоли, в общем, так же, как лямбда-функции выводят информацию в логи CloudWatch:Georgiy@Baltimore MINGW64 /h/dropbox/projects/aberp/lambda (master) $ sam local start-api --docker-volume-basedir /h/Dropbox/Projects/aberp/lambda "aberp" ←[34mINFO←[0m[0000] Unable to use system certificate pool: crypto/x509: system root pool is not available on Windows 2018/04/04 22:33:49 Connected to Docker 1.35 ←[34mINFO←[0m[0001] Unable to use system certificate pool: crypto/x509: system root pool is not available on Windows 2018/04/04 22:33:50 Fetching lambci/ lambda:nodejs6.10 image for nodejs6.10 runtime. nodejs6.10: Pulling from lambci/lambda ←[1B06c3813f: Already exists ←[1B967675e1: Already exists ←[1Bdaa0d714: Pulling fs layer ←[1BDigest: sha256:56205b1ec69e0fa6c32e9658d94ef6f3f5ec08b2d60876deefcbbd72fc8cb12f52kB/2.052kBB Status: Downloaded newer image for lambci/ lambda:nodejs6.10 ←[32;1mMounting index.handler (nodejs6.10) at http://127.0.0.1:3000/{proxy+ } [OPTIONS GET HEAD POST PUT DELETE PATCH]←[0 m You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template.
2018/04/04 22:36:06 Invoking index.handler (nodejs6.10)
2018/04/04 22:36:06 Mounting /h/Dropbox/Projects/aberp/lambda as /var/task:ro inside runtime container
←[32mSTART RequestId: 9fee783c-285c-127d-b5b5-491bff5d4df5 Version: $LATEST←[0m
←[32mEND RequestId: 9fee783c-285c-127d-b5b5-491bff5d4df5←[0m
←[32mREPORT RequestId: 9fee783c-285c-127d-b5b5-491bff5d4df5 Duration: 476.26 ms Billed Duration: 500 ms Memory S
ize: 128 MB Max Memory Used: 37 MB ←[0m
Sam-local все еще находится в публичной бета-версии, но я считаю, что она довольно стабильна.
Все это в целом позволяет работать над созданием бессерверного приложения на локальном компьютере и это не сложнее, чем создание традиционных веб-приложений.
Я не могу не упомянуть об этом.
у sam-local есть аналог - это Бессерверная структура .
Бессерверный фреймворк довольно популярен, во многом из-за того, что ранее не было альтернатив.
У меня нет большого опыта его использования, но, насколько я знаю, он не предоставляет полноценной локальной среды, такой как sam-local. Sam-local разрабатывается в самой AWS, а бессерверный фреймворк разрабатывается отдельной командой энтузиастов.
Однако преимущество бессерверной инфраструктуры заключается в том, что она позволяет сделать приложения менее привязанными к конкретному поставщику.
О структуре
Как я уже писал, он нужен для обеспечения быстрого старта при создании новых бессерверных приложений.В настоящее время он реализует авторизацию только с использованием веб-токенов.
Далее мы планируем добавить обработку ошибок, работу с формами и отображением табличных данных, а также настроить механизм развертывания.
В общем, чтобы в дальнейшем можно было клонировать репозиторий AB-ERP и быстро приступить к работе над приложениями.
Мы создаем ERP-системы, поэтому и назвали это АБ-ERP по аналогии с названиями других наших товаров: AB-ЗАДАЧИ И AB-DOC .
При этом AB-ERP не обязателен для создания ERP-систем; на его основе можно построить любые бессерверные веб-приложения.
Приложение имеет внешний и внутренний код. Соответственно в корне проекта есть 2 папки: лямбда (бэкэнд) и общественный (передний): +---lambda
| +---api
| +---core
\---public
+---css
| \---core
+---img
+---js
| \---core
\---views
AB-ERP работает по принципу одностраничного веб-приложения (SPA).
При развертывании приложения внешний код должен быть размещен в AWS S3, а перед ним настроен CloudFront. Это было описано в моем предыдущая статья о AB-DOC в разделе «Разработка и внедрение».
Бэкэнд-код будет загружен в сервис AWS Lambda при развертывании.
AB-ERP использует MariaDB в качестве базы данных.
MariaDB развернут на AWS RDS. При желании AB-ERP можно перенастроить, например, для работы с AWS DynamoDB.
Пользовательские файлы будут храниться в AWS S3.
Вот как выглядит архитектура приложения:
Бэкэнд
На данный момент все очень и очень просто.Всего один ресурс API-шлюза и всего одна лямбда-функция.
Вот как выглядит шаблон SAM: AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: An example RESTful service
Resources:
ABLambdaRouter:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs6.10
Handler: index.handler
Events:
ABAPI:
Type: Api
Properties:
Path: /{proxy+}
Method: any
В шаблоне SAM мы видим один из наших ресурсов ABLambdaRouter, который представляет собой лямбда-функцию.
ABLambdaRouter вызывается только одним событием ABAPI, которое поступает из API. Наш ресурс API Gateway принимает запросы любыми методами ( ЛЮБОЙ ) к любым путям в URL-адресе: /{прокси+} .
То есть, другими словами, он действует как обычный двусторонний прокси.
Функция Lambda, соответственно, должна взять на себя роль маршрутизатора, который будет выполнять разный код в зависимости от запросов.
Код функции Lambda (маршрутизатор) 'use strict';
const jwt = require('jsonwebtoken');
//process.env.PROD and other env.vars are set in production only
if(process.env.PROD === undefined){
process.env.PROD = 0;
process.env.SECRET = 'SOME_SECRET_CODE_672967256';
process.env.DB_HOST = '192.168.1.5';
process.env.DB_NAME = 'ab-erp';
process.env.DB_USER = 'ab-erp';
process.env.DB_PASSWORD = 'ab-erp';
}
//core modules
const HTTP = require('core/http');
const DB = require('core/db');
//main handler
exports.handler = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
let api;
const [resource, action] = event.pathParameters['proxy'].
split('/'); //OPTIONS requests are proccessed by API GateWay using mock //sam-local can't do it, so for local development we need this if(event.httpMethod === 'OPTIONS'){ return callback(null, HTTP.response()); } //require resource module try { api = require('api/' + resource)(HTTP, DB); } catch(e) { if (e.code === 'MODULE_NOT_FOUND') { return callback(null, HTTP.response(404, {error: 'Resource not found.'})); } return callback(null, HTTP.response(500)); } //call resource action if(api.hasOwnProperty(action)) { if(api[action].
protected === 0){
api[action](event, context, callback);
} else if (event.headers['X-Access-Token'] !== undefined) {
let token = event.headers['X-Access-Token'];
try {
event.userData = jwt.verify(token, process.env.SECRET);
api[action](event, context, callback);
} catch(error) {
return callback(null, HTTP.response(403, {error: 'Failed to verify token.'}));
}
} else {
return callback(null, HTTP.response(403, {error: 'No token provided.'}));
}
} else {
return callback(null, HTTP.response(404, {error: 'Action not found.'}));
}
}
API имеет двухуровневую иерархию: первый уровень — модуль, второй уровень — действие.
URL-адреса выглядят следующим образом api.app.com/module/action .
Функция маршрутизатора анализирует Параметры пути получил запрос, пытается подключить нужный модуль из папки лямбда/API а затем передать запрос нужной функции в этом модуле.
По умолчанию функции в модулях требуют авторизации, поэтому перед вызовом функции из модуля наш маршрутизатор проверит наличие действующего токена в X-Access-токен заголовок запроса.
Если токен действителен, будет вызвана функция из модуля; в противном случае будет возвращена ошибка 403. Почему мы выбрали этот подход вместо создания множества отдельных ресурсов API-шлюза и множества лямбда-функций? Во-первых, и это самое главное, с такой архитектурой легко настроить, развернуть и собственно работать.
Во-вторых, такой подход сводит к минимуму холодные запуски функции.
Дело в том, что если функция долгое время не вызывалась, AWS удаляет ее контейнер, а затем при новом вызове обработка запроса занимает больше времени.
У этого подхода есть и недостатки.
Мы не сможем выполнить какие-либо специальные настройки на уровне API-шлюза для разных ресурсов API. Может у кого-то возник вопрос: зачем вообще нужен API-шлюз? Почему бы не получить доступ к лямбде напрямую из браузера? API Gateway предоставляет множество преимуществ.
Может работать как CDN, в режиме Edge Optimized, есть кэширование ответов, может сам отвечать на OPTION-запросы без обращения к бэкенду (интеграция MOCK) — всё это существенно ускоряет работу приложения.
Также имеется защита от DDOS и возможность регулировать трафик с помощью ограничений.
Ну, а также позволяет открыть API приложения для сторонних разработчиков.
Внешний интерфейс
Для внешнего интерфейса мы решили не использовать «большие» фреймворки, такие как React, Vue.js или Angular.js, поэтому написали небольшой маршрутизатор для нашего SPA-приложения.Роутер хранит описание каждой страницы: какой html шаблон и какие css, js файлы ему нужны.
При запросе страницы роутер загружает все необходимые файлы в текстовом виде, объединяет их и вставляет в div-контейнер интерфейса приложения.
При вставке в контейнер выполняется JavaScript открываемой страницы.
Код маршрутизатора "use strict";
//ROUTER object
const ROUTER = {
pages: {
"index": ["css/index.css", "views/index.html", "js/index.js"],
"login": ["css/login.css", "views/login.html", "js/login.js"]
},
open: function(page){
let self = this;
$container.html(big_preloader_html);
if(self.pages.hasOwnProperty(page)){
const parts = self.pages[page];
let getters = [];
let wrappers = [];
for (let i = 0; i < parts.length; i++) {
if( /^.
*\.
css$/i.test(parts[i]) ){ wrappers.push('style'); } else if ( /^.
*\.
js$/i.test(parts[i]) ){ wrappers.push('script'); } else { wrappers.push(''); } getters.push( $.
get(parts[i], null, null, 'text').
promise() ); } Promise.all(getters).
then(function(results) {
let html = '';
for (let i = 0; i < results.length; i++) {
if(wrappers[i] === ''){
html += results[i];
} else {
html += `<${wrappers[i]}>${results[i]}</${wrappers[i]}>`;
}
}
self.updatePath(page);
$container.html(html);
});
} else {
//TODO
console.log('404');
}
},
updatePath: function(newPath){
if(newPath !== window.location.pathname) {
history.pushState({}, null, newPath);
}
}
}
Настройка среды
Я постарался шаг за шагом изложить все, что вам нужно для запуска проекта на вашем компьютере, в README на github проект. Если что-то не получится, пишите в комментариях – постараемся помочь.
Соответственно, мы обновим README.
Для локального тестирования я написал небольшой HTTP-сервер на Node.js: const express = require('express');
const app = express();
app.use(express.static('public'));
app.use(function(req, res, next) {
req.url = 'app.html';
next();
});
app.use(express.static('public'));
app.listen(80, () => console.log('Listening.'))
Перед началом работы необходимо запустить ее командой узел abserver.js .
Когда приходит запрос, он ищет файл в папке общественный и отдает обратно, если найдет. Если файл не найден, выдает основной файл приложения общедоступный\app.html .
Этого вполне достаточно для работы SPA-приложения.
В производственной среде ту же проблему решает Amazon CloudFront.
Заключение
AB-ERP все еще очень сырой.Будем рады любым предложениям и комментариям, а тем более, коммитам.
На данный момент в AB-ERP более-менее реализована только авторизация — об этом планирую рассказать в одной из следующих статей.
Какие варианты авторизации есть при работе с API Gateway и почему мы не реализовали собственный авторизатор или интеграцию с Cognito. Некоторые планы по дальнейшему развитию проекта.
Ключевыми компонентами любого приложения данных являются формы для ввода данных и таблицы для вывода.
Поэтому в первую очередь будет добавлен функционал по работе с формами и таблицами.
Есть идея стандартизировать работу с формами (построение форм на странице, валидация на бэкенде и фронтенде, сохранение в базе данных) за счет использования YAML-шаблонов.
То есть дать возможность описывать формы в YAML-шаблонах, а затем всю остальную работу по фронтенду и бекенду делать с помощью кода AB-ERP. Для таблиц мы будем использовать библиотеку Таблицы данных , который мы использовали в нашем трекере задач AB-TASKS. При написании этой статьи мне помогли следующие инструменты:
- Онлайн-сервис рисования диаграмм draw.io
- Команда дерево Командная строка Windows для рисования дерева каталогов
-
Циглер, Карл
19 Oct, 24 -
После 15 Марта Мы Начнем Тестировать Lte
19 Oct, 24 -
Сигнализация Своими Руками
19 Oct, 24 -
Каждый День В Outlook У Меня Просто Мука...
19 Oct, 24 -
«Индиэкатор» Отменил Систему Приглашений
19 Oct, 24 -
Cogear — Система Управления Сайтом
19 Oct, 24 -
Рекомендательные Системы: Советы От Машины
19 Oct, 24