Введение

В этом руководстве мы собираемся создать веб-приложение с помощью AdonisJS и интегрировать его с Materialize, чтобы создать панель мониторинга в реальном времени на основе потоковых данных с использованием стандартного SQL.

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

Предпосылки

Перед началом работы вам необходимо установить следующие вещи:

Что такое материализовать

Materialize – это потоковая база данных, которая берет данные из таких источников, как Kafka, PostgreSQL, корзины S3 и т. д., и позволяет эффективно преобразовывать их в режиме реального времени с помощью SQL.

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

В контексте веб-разработки Materialize можно использовать в качестве серверной части для приложений реального времени (как мы увидим в этой демонстрации)!

Запуск демонстрации Materialise

Ради этого урока мы собираемся запустить следующую демонстрацию Materialize:

Materialize — Демонстрация парсинга логов

Настройка демо-версии следующая:

Мы не будем вдаваться здесь в подробности, но если вы еще не проходили эту демонстрацию, обязательно прочитайте ее!

Чтобы запустить демонстрацию, выполните следующие действия:

Прежде всего, прежде чем вы сможете запустить демо, вам нужно клонировать репозиторий:

cd mz-http-logs
docker-compose up -d

При этом у вас будет запущенный и работающий экземпляр Materialize. Далее мы подготовим нашу установку AdonisJS и используем AdonisJS для создания источников и представлений Materialize!

Что такое AdonisJS

AdonisJS — это веб-фреймворк для Node.js. Он включает в себя все, что вам нужно для создания полнофункционального веб-приложения или API.

AdonisJS был вдохновлен Laravel и имеет собственную ORM, поддержку аутентификации и инструмент командной строки под названием Ace, который очень похож на Artisan.

В конце мы расширим демо-версию Materialize для разбора журнала и получим следующую настройку:

Установите AdonisJS

Начнем с установки AdonisJS. Для этого вам нужно будет выполнить следующую команду:

После запуска вам будет предложено выбрать структуру проекта. Вы сможете выбирать между API, веб-приложением и минимально возможным приложением AdonisJS:

CUSTOMIZE PROJECT ❯ Select the project structure ... Press to select
api (Tailored for creating a REST API server)
❯ web (Traditional web application with server-rendered templates) 
slim (A smallest possible AdonisJS application)

Для этого урока давайте воспользуемся приложением web! С помощью клавиш со стрелками выберите web и нажмите Enter.

После этого вам будет предложено выбрать имя для проекта, я оставлю его как hello-materialize, но вы можете выбрать другое имя.

Затем я нажму Enter и скажу «да» остальным настройкам:

❯ Enter the project name · hello-materialize ❯ Setup eslint? (y/N) · y ❯ Configure webpack encore for compiling frontend assets? (y/N) › y

Это создаст экземпляр проекта и может занять до минуты:

Когда все будет готово, вы можете cd перейти в новый каталог проекта:

cd hello-materialize

Затем запустите веб-сервер:

node ace serve --watch

Если вы пришли из мира Laravel, это будет похоже на запуск php artisan serve. Инструмент ace CLI точно такой же, как artisan, и обладает многими теми же функциями.

Чтобы проверить все команды ace, вы можете запустить: node ace.

Установка Люсид

Lucid — это ORM AdonisJS. Он очень похож на Laravel Eloquent.

Lucid поставляется с Active Record ORM, Query Builder, миграциями, семенами и фабриками.

Давайте продолжим и установим его! Для этого просто выполните следующую команду:

После этого вам нужно будет выполнить быструю настройку.

Настройка Lucid

Чтобы настроить Lucid, вам необходимо выполнить следующую команду ace:

Вам будет предложено выбрать драйвер базы данных, который вы хотите использовать. Поскольку Materialize совместим с PostgreSQL по проводам, вы можете подключиться к нему с помощью любого драйвера pg; здесь обязательно выберите PostgreSQL!

https://user-images.githubusercontent.com/21223421/142431728-ac88085b-34cb-4ebb-83c7-b0cae9fb455d.png

Далее вам будет предложено выбрать, где вы хотите отобразить инструкции по настройке. Я выбрал In the terminal, который выводит необходимые переменные среды, которые вы должны добавить в свой файл .env.

Настройте переменные среды Materialize.

Чтобы наше приложение AdonisJS могло подключаться к Materialize, нам нужно изменить детали PG_* в файле .env.

В вашем любимом текстовом редакторе откройте файл .env и измените переменные среды PG_ на:

DB_CONNECTION=pg 
PG_HOST=localhost 
PG_PORT=6875 
PG_USER=materialize 
PG_PASSWORD= 
PG_DB_NAME=materialize

Это позволит AdonisJS подключаться к Materialise так же, как при подключении к PostgreSQL.

Следует иметь в виду, что Materialize еще не поддерживает полный системный каталог PostgreSQL (мы работаем над этим!), а это означает, что ORM, такие как Lucid, Prisma, Sequelize или TypeORM, могут дать сбой при некоторых попытках взаимодействия. с Материализовать. По мере расширения охвата pg_catalog интеграция с этими инструментами будет постепенно улучшаться!

Создание контроллера

Давайте создадим контроллер, куда мы добавим функционал, который позволит нам подключаться к Materialize!

Поскольку демонстрация Materialize имитирует журнал приложения с большим количеством посетителей, давайте назовем наш контроллер AdonisJS VisitorsController:

Это создаст файл контроллера по адресу:

app/Controllers/Http/VisitorsController.ts

Далее, давайте создадим маршруты, которые нам понадобятся!

Создание маршрутов AdonisJS

Ваш файл маршрутов хранится по адресу start/routes.ts. Там мы можем указать URL-адреса нашего приложения и сопоставить их с различными контроллерами и методами!

У нас еще нет готовых методов, но мы знаем, что нам понадобятся следующие маршруты:

  • /source: При посещении этот маршрут создаст источник Materialise.
  • /view: При посещении этот маршрут создавал материализованное представление.
  • /visitors: Этот маршрут вернет поток событий со всеми последними изменениями в нашем материализованном представлении.
  • /: это будет целевая страница, на которой мы будем отображать потоковые данные, которые мы получаем от конечной точки /visitors и Materialize.

Откройте файл маршрутов по адресу start/routes.ts и обновите его, чтобы он имел следующее содержимое:

import Route from '@ioc:Adonis/Core/Route' 
Route.get('/', 'VisitorsController.index') 
Route.get('/visitors', 'VisitorsController.visitors') Route.get('/source', 'VisitorsController.source') 
Route.get('/view', 'VisitorsController.view')

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

Создание источника Materialise из журналов

Если бы вы обращались к Materialise напрямую через клиент SQL (например, psql), чтобы получить доступ к данным из постоянно создаваемого файла журнала, вы должны выполнить следующую инструкцию:

Давайте посмотрим, как мы можем сделать это с помощью AdonisJS!

Сначала откройте файл app/Controllers/Http/VisitorsController.ts в своем любимом текстовом редакторе.

Первоначально файл будет иметь следующее содержимое:

Есть несколько вещей, которые мы хотели бы сделать:

// import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Database from '@ioc:Adonis/Lucid/Database'
export default class VisitorsController {
public async source({request, response}) {
//Using Ludic to connect to Materialize, we are executing a CREATE SOURCE statement
const res = await Database.rawQuery(
`CREATE SOURCE requests
FROM FILE '/log/requests' WITH (tail = true)
FORMAT REGEX '(\\?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - - \[(\\?P<ts>[^]]+)\] "(\\?P<path>(\\?:GET /search/\\\?kw=(\\?P<search_kw>[^ ]*) HTTP/\d\.\d)|(\\?:GET /detail/(\\?P<product_detail_id>[a-zA-Z0-9]+) HTTP/\d\.\d)|(\\?:[^"]+))" (\\?P<code>\d{3}) -';`
);
return res;
}
}

Теперь, если вы зайдете по URL-адресу /source через браузер ( http://127.0.0.1:3333/source), он создаст источник Materialize:

Создание материализованного представления

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

Давайте сделаем то же самое, что и раньше, но создадим материализованное представление на основе нашего исходного файла! Для этого создадим метод view со следующим содержимым:

Добавьте это сразу после окончания метода source

public async view({request, response}) {
//Using Ludic to connect to Materialize, we are executing a CREATE VIEW statement
const res = await Database.rawQuery(
`CREATE OR REPLACE MATERIALIZED VIEW unique_visitors AS
SELECT count(DISTINCT ip) FROM requests;`
);
return res;
}

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

Чтобы создать представление, перейдите по URL-адресу /view через браузер (например, http://127.0.0.1:3333/view).

После этого наше представление будет создано, и мы сможем перейти к следующему шагу!

Создание потока событий

Вы можете запросить новое материализованное представление, которое мы только что создали, как обычно, с помощью стандартного оператора SELECT:

SELECT * FROM unique_visitors;

Однако для того, чтобы в полной мере воспользоваться постепенно обновляемым материализованным представлением прямо из нашего приложения AdonisJS, вместо того, чтобы запрашивать Materialize со стандартным SELECT для получения состояния представления в определенный момент времени, мы будем использовать оператор TAIL для запроса поток обновлений по мере изменения представления.

public async visitors({request, response}) {
// First we set a header to identify that this would be an event stream
response.response.setHeader('Content-Type',  'text/event-stream');
// Then we declare a TAIL cursor
await Database.rawQuery('BEGIN');
await Database.rawQuery('DECLARE visitors_c CURSOR FOR TAIL unique_visitors');
// Finally we use FETCH in a loop to retrieve each batch of results as soon as it is ready
while (true) {
const res = await Database.rawQuery('FETCH ALL visitors_c');
response.response.write(`data: ${JSON.stringify(res.rows)}\n\n`)
}
}

Для получения дополнительной информации о TAIL обязательно ознакомьтесь с официальной документацией здесь:

Материализировать TAIL заявление.

Если бы вы сейчас посетили URL-адрес /visitors через браузер, вы бы увидели следующий вывод:

Далее давайте создадим представление, в котором мы будем использовать конечную точку /visitors в качестве источника событий и постоянно обновлять нашу веб-страницу.

Отображение количества уникальных посетителей на фронтенде

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

node ace configure encore

Затем создайте новый файл по адресу:

resources/views/visitors.edge

И добавьте следующий контент:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Materialize and AdonisJS</title>
@entryPointStyles('app')
@entryPointScripts('app')
</head>
<body>
<main>
<div>
<h1 class="title"> Hi there! </h1>
<p class="subtitle">
The number of unique visitors is: <strong><span id="count"></span></strong>
</p>
</div>
</main>
<script>
var eventSource = new EventSource("http://127.0.0.1:3333/visitors");
const count = 0;
eventSource.onmessage = function(e) {
const data  = JSON.parse(e.data)
//const count = omit(data, 'mz_timestamp', 'mz_diff', 'mz_progressed')
const { mz_diff, mz_progressed } = data;
data.forEach(entry => {
if(entry.mz_diff == -1){
console.log('Old count: ' + entry.count)
} else {
console.log('New count: ' + entry.count)
let countDiv = document.getElementById("count");
countDiv.innerHTML = entry.count;
}
})
};
</script>
</body>
</html>

Краткое изложение основных моментов, о которых нужно помнить:

  • new EventSource: Сначала мы определяем новую EventSource и указываем нашу конечную точку /visitors.
  • eventSource.onmessage: Затем мы прослушиваем новые сообщения для отображения в EventStream.
  • JSON.parse(e.data): После этого парсим наши данные
  • data.forEach: Наконец, мы запускаем цикл и обновляем общий счетчик уникальных посетителей на странице.

Теперь, если бы вы посетили свое приложение AdonisJS, вы бы увидели следующий вывод:

Как видите, вместо того, чтобы делать огромное количество запросов AJAX, мы просто подключаемся к потоку и обновляем нашу веб-страницу с последними изменениями от Materialize!

Заключение

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

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

Материализировать демо

Чтобы узнать больше об AdonisJS, вы также можете найти документацию здесь:

Документация AdonisJS

Вы можете найти ссылку на исходный код AdonisJS из этой демонстрации здесь:

Потоковая передача данных с файлами Materialise и AdonisJS Demo

Чтобы узнать больше о Streaming SQL, обязательно ознакомьтесь с этим постом здесь:

Потоковый SQL: что это такое и чем он полезен?

Надеюсь, что это было полезно!

Первоначально опубликовано на https://devdojo.com.