Русский
Русский
English
Статистика
Реклама

Graphql

GraphQL Typescript любовь. TypeGraphQL v1.0

26.08.2020 14:06:49 | Автор: admin


ЗTypeGraphQL v1.0


19 августа вышел в релиз фреймворк TypeGraphQL, упрощающий работу с GraphQL на Typescript. За два с половиной года проект обзавёлся солидным комьюнити и поддержкой нескольких компаний и уверено набирает популярность. Спустя более 650 коммитов у него более 5000 звёзд и 400 форков на гитхабе, плод упорной работы польского разработчика Михала Литека. В версии 1.0 значительно улучшилась производительность, схемы получили изоляцию и избавились от прежней избыточности, появились две крупные фичи директивы и расширения, фреймворк был приведён к полной совместимости с GraphQL.

Для чего этот фреймворк?


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

  1. Сначала нужно создать все необходимые типы GraphQL в формате SDL (Schema Definition Language);
  2. Затем мы создаём модели данных в ORM (Object-Relational Mapping), чтобы описать объекты в базе данных;
  3. После этого надо написать преобразователи для всех запросов, мутаций и полей, что вынуждает нас...
  4. Создавать тайпскриптовые интерфейсы для всех аргументов, инпутов и даже типов объектов.
  5. Только после этого преобразователями можно пользоваться, не забывая при этом вручную проверять рутинные вещи вроде валидации, авторизации и загрузки зависимостей.


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

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

Принцип работы


Разберём работу TypeGraphQL на примере GraphQL API для базы рецептов.

Так будет выглядеть схема в SDL:

    type Recipe {        id: ID!        title: String!        description: String        creationDate: Date!        ingredients: [String!]!    }


Перепишем её в виде класса Recipe:

    class Recipe {        id: string;        title: string;        description?: string;        creationDate: Date;        ingredients: string[];    }


Снабдим класс и свойства декораторами:

    @ObjectType()    class Recipe {        @Field(type => ID)        id: string;        @Field()        title: string;        @Field({ nullable: true })        description?: string;        @Field()        creationDate: Date;        @Field(type => [String])        ingredients: string[];    }


Подробные правила описания полей и типов в соответствующем разделе документации

Затем мы опишем обычные CRUD запросы и мутации. Для этого создадим контроллер RecipeResolver c RecipeService, переданным в конструктор:

    @Resolver(Recipe)    class RecipeResolver {        constructor(private recipeService: RecipeService) {}        @Query(returns => Recipe)        async recipe(@Arg("id") id: string) {            const recipe = await this.recipeService.findById(id);            if (recipe === undefined) {                throw new RecipeNotFoundError(id);            }            return recipe;        }        @Query(returns => [Recipe])        recipes(@Args() { skip, take }: RecipesArgs) {            return this.recipeService.findAll({ skip, take });        }        @Mutation(returns => Recipe)        @Authorized()        addRecipe(            @Arg("newRecipeData") newRecipeData: NewRecipeInput,            @Ctx("user") user: User,        ): Promise<Recipe> {            return this.recipeService.addNew({ data: newRecipeData, user });        }        @Mutation(returns => Boolean)        @Authorized(Roles.Admin)        async removeRecipe(@Arg("id") id: string) {            try {                await this.recipeService.removeById(id);                return true;            } catch {                return false;            }        }    }


Здесь декоратор @Authorized() применяется для ограничения доступа для неавторизованных (или обладающих недостаточными правами) пользователей. Подробнее про авторизацию можно почитать в документации.

Пора добавить NewRecipeInput и RecipesArgs:

@InputType()class NewRecipeInput {@Field()@MaxLength(30)title: string;@Field({ nullable: true })@Length(30, 255)description?: string;@Field(type => [String])@ArrayMaxSize(30)ingredients: string[];}@ArgsType()class RecipesArgs {@Field(type => Int, { nullable: true })@Min(0)skip: number = 0;@Field(type => Int, { nullable: true })@Min(1) @Max(50)take: number = 25;}


Length, Min и @ArrayMaxSize это декораторы из класса-валидатора, которые автоматически выполняют проверку полей.

Последний шаг собственно сборка схемы. Этим занимается функция buildSchema:

    const schema = await buildSchema({        resolvers: [RecipeResolver]    });


И всё! Теперь у нас есть полностью рабочая схема GraphQL. В скомпилированном виде она выглядит так:

type Recipe {id: ID!title: String!description: StringcreationDate: Date!ingredients: [String!]!}input NewRecipeInput {title: String!description: Stringingredients: [String!]!}type Query {recipe(id: ID!): Reciperecipes(skip: Int, take: Int): [Recipe!]!}type Mutation {addRecipe(newRecipeData: NewRecipeInput!): Recipe!removeRecipe(id: ID!): Boolean!}


Это пример базовой функциональности, на самом деле TypeGraphQL умеет использовать ещё кучу инструментов из арсенала TS. Ссылки на документацию вы уже видели :)

Что нового в 1.0


Вкратце пройдёмся по основным изменениям релиза:

Производительность


TypeGraphQL это по сути дополнительный слой абстракции над библиотекой graphql-js, и он всегда будет работать медленнее её. Но теперь, по сравнению с версией 0.17, на выборке из 25000 вложенных объектов фреймфорк добавляет в 30 раз меньше оверхеда с 500% до 17% с возможностью ускорения до 13%. Отдельные нетривиальные способы оптимизации описаны в документации.

Изоляция схем


В старых версиях схема строилась из всех метаданных, получаемых из декораторов. Каждый последующий вызов buildSchema возвращал одну и ту же схему, построенную из всех доступных в хранилище метаданных. Теперь же схемы изолированы и buildSchema выдаёт только те запросы, которые напрямую связаны с заданными параметрами. То есть изменяя лишь параметр resolvers мы поулчаем разные операции над схемами GraphQL.

Директивы и расширения


Это два способа добавить метаданные в элементы схемы: директивы GraphQL являются часть SDL и могут быть объявлены прямо в схеме. Также они могут изменять её и выполнять специфические операции, например, сгенерировать тип соединения для пагинации. Применяются они с помощью декораторов @Directive и @Extensions и различаются подходом к построению схемы. Документация Directives, Документация Extensions.

Преобразователи и аргументы для полей интерфейсов


Последний рубеж полной совместимости с GraphQL лежал здесь. Теперь можно определять преобразователи для полей интерфейса, используя синтаксис @ObjectType:

    @InterfaceType()    abstract class IPerson {        @Field()        avatar(@Arg("size") size: number): string {            return `http://i.pravatar.cc/${size}`;        }    }


Немногочисленные исключения описаны здесь.

Преобразование вложенных инпутов и массивов


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

Заключение


В течение всего времени разработки, проект оставался открытым к идеям и критике, опенсорсным и зажигательным. 99% кода написал сам Михал Литек, но и сообщество внесло огромный вклад в развитие TypeGraphQL. Теперь, с нарастающей популярностью и финансовой поддержкой, он может стать настоящим стандартом в своей области.

Сайт
Гитхаб
Доки
Твиттер Михала

Подробнее..

Онлайн-митап сообщества разработчиков MSK VUE.JS

20.07.2020 14:14:41 | Автор: admin
image

23 июля приглашаем на онлайн-митап сообщества разработчиков MSK VUE.JS.

В программе митапа:

  • Разработка конструктора отчетов c Cube.js;
  • 5 действенных техник оптимизации vue-приложений;
  • Решение проблем REST API при помощи GraphQL.

Зарегистрироваться

О митапе


Разработчик Cube.js Леонид Яковлев расскажет, как сделать конструктор отчетов при помощи cube.js на бэкенде. Подробно поговорит, что такое query builder, какие у него преимущества и покажет пример простого queryBuilder

Руководитель AFFINAGE Игорь Яковлев поделится собственными техниками, инсайтами и лайфхаками, как разрабатывать по-настоящему быстрые vue-приложения.

Разработчик Московской биржи Юлия Кузнецова расскажет, как GraphQL помогает решать проблемы REST API архитектуры и покажет, как это сделать на примере Vue Apollo.

Об экспертах


Леонид Яковлев разработчик в команде Developer Relations and Community фреймворка Cube.js.

Игорь Яковлев руководитель аутсорспродакшена по фронтенду AFFINAGE.

Юлия Кузнецова старший разработчик Московской биржи.

О MSK VUE.JS


MSK VUE.JS сообщество разработчиков Vue.js, которое регулярно проводит митапы, чтобы делиться опытом, обсуждать перспективы и строить комьюнити.

По теме:


Подробнее..

Из песочницы Urban Bot или как писать чат-ботов для Telegram, Slack, Facebook на React.js

27.07.2020 22:10:16 | Автор: admin

image


В этой статье я хочу познакомить с новой библиотекой Urban Bot, которая адаптирует React для написания чат-ботов. Ниже я расскажу, зачем эта библиотека появилась на свет, какие дает преимущества и как написать вашего первого чат-бота.


Чат-бот это чаще всего отдельный чат в мессенджере, в котором вы общаетесь не с человеком, а с программой. Он может присылать сообщения в виде текста, изображений, кнопок и многих других UI элементов и реагировать на сообщения от пользователей. Современные чат-боты это полноценные UI приложения внутри мессенджеров.

В отличии от большинства чат-бот библиотек, которые чаще всего просто оборачивают http запросы в функции с готовыми аргументами и предоставляют подписки вида bot.on('message', callback), иногда позволяя передавать контекст между вызовами, Urban Bot предлагает совершенно иной подход к разработке чат-ботов через декларативное программирование и компонентный подход. Живой пример, написанный на Urban Bot, вы можете попробовать в Telegram, cсылка на чат-бот, и посмотреть код на GitHub.


Как мы заметили выше, чат-боты это полноценные UI приложения. А какой язык в 2020 и какая библиотека наиболее подходит для разработки UI приложений? Правильно, JavaScript и React. Такая интеграция позволяет легко и непринужденно строить чат-боты любой сложности без единого знания об API мессенджеров. Далее я расскажу, как создавать простые компоненты и на их основе строить сложных чат-ботов, работать с навигацией, создавать диалоги любой вложенности, писать одно приложение и запускать в любых мессенджерах, и многое другое.


Отправка сообщений


Так выглядит самый простой пример на Urban Bot. Для отправки текстового сообщения нам нужно создать функцию и вернуть из него готовый компонент Text с текстом внутри, который мы хотим отправить. Когда компонент отрендериться, все пользователи чат-бота получат сообщение "Hello, world!".


import React from 'react';import { Text } from '@urban-bot/core';function App() {    return (        <Text>           Hello, world!        </Text>    );}

Изображение можно отправить так:


import React from 'react';import { Image } from '@urban-bot/core';function App() {    const imageByURL =  'https://some-link.com/image.jpg';    return <Image file={imageByURL} />;}

Urban Bot имеет готовый набор компонентов, для каждого вида сообщений, для файлов File, для кнопок ButtonGroup и много других, подробнее можно взглянуть здесь. В каждый из них можно передать определенный набор пропсов, например, имитировать будто бот печатает сообщение 1 секунду <Text simulateTyping={1000}>.


Получение сообщений


Мы рассмотрели как посылать сообщения, давайте разберемся как подписываться на сообщения от пользователей. За подписки в Urban Bot отвечают React Hooks.


Чтобы подписаться на текстовые сообщения, мы можем использовать хук useText.


import React from 'react';import { Text, useText } from '@urban-bot/core';function App() {    useText(({ text }) => {        console.log(`Пользователь отправил сообщение ${text}`);    });    return (        <Text>            Hello, world!        </Text>    );}

Urban Bot предоставляет готовы набор хуков для разных типов сообщений. Например, useImage, если пользователь отправил изображение, useFile и т.д. Полный список здесь. В каждый хук также приходит мета информация, кто отправил сообщение и т.д.


Эхо бот


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


В этом компоненте мы впервые добавим работу с переменными через React хук useState. Этот хук возвращает переменную и функцию, чтобы ее изменять. React.useState нужен, чтобы изменение переменной приводило к ререндеру и, соответсвенно, отправке нового сообщения. Мы определим начальное значение переменной text как "Привет" и передадим в компонент Text. Также мы подпишемся на сообщения от пользователей с помощью хука useText, и будем изменять text через функцию setText. После вызова setText React перерендерит компонент Echo с новым значением, и пользователь получит новое сообщение с тем что он сам отправил боту.


import React from 'react';import { Text, useText } from '@urban-bot/core';function Echo() {    const [text, setText] = React.useState('Привет!');    useText(({ text }) => {        setText(text);    });    return (        <Text>            {text}        </Text>    );}

Кнопки


Давайте также напишем пример с кнопками, сделаем простейший счетчик. Для этого нам понадобятся компоненты ButtonGroup и Button. Каждой кнопке мы определим свой обработчик, который будет менять count на +1 или -1 и будем передавать результат в ButtonGroup в проп title. Мы установим проп isNewMessageEveryRender как false, чтобы при последующих ререндерах отправлялось не новое сообщение с новыми кнопками, а просто изменялось начальное сообщение.


import React from 'react';import { ButtonGroup, Button } from '@urban-bot/core';function Counter() {    const [count, setCount] = React.useState(0);    const increment = () => setCount(count + 1);    const decrement = () => setCount(count - 1);    return (        <ButtonGroup title={count} isNewMessageEveryRender={false}>            <Button onClick={increment}>+1</Button>            <Button onClick={decrement}>-1</Button>        </ButtonGroup>    );}


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


Теперь, когда пользователь напишет "/echo" отрендериться компонент Echo, когда "/counter" управление перейдет в Counter. Роуты также могут принимать path как regexp.


import React from 'react';import { Router, Route } from '@urban-bot/core';import { Echo } from './Echo';import { Counter } from './Counter';function App() {    return (        <Router>            <Route path="/echo">                <Echo />            </Route>            <Route path="/counter">                <Counter />            </Route>        </Router>    );}

Визуальный пример, чтобы увидеть как будет работать код.


image


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


Форматирование текста


Urban Bot позволяет стилизовать сообщения через привычные HTML теги. Писать жирным <b>, курсивом <i>, зачеркнутым <s>, переносить строки <br /> и так далее, полный список здесь.


Пример
const someCode = `function sum2() {    return 2 + 2;}if (sum2() !== 4) {    console.log('WTF');}`;<Text>    Usual text    <br />    <b>Bold text</b>    <br />    <i>Italic text</i>    <br />    <u>Underscore text</u>    <br />    <s>Strikethrough text</s>    <br />    <q>quote</q>    <br />    <b>        Bold and <s>Strikethrough text</s>    </b>    <br />    <code >Code 2 + 2</code >    <br />    <pre>{someCode}</pre>    <br />    <a href="http://personeltest.ru/aways/github.com/urban-bot/urban-bot">External link</a></Text>


Диалоги


Чат-боты могут вести диалоги с пользователем, что-то спрашивать, реагировать на сообщения в зависимости от ответа пользователя. В Urban Bot встроен компонент Dialog, который позволяет легко создавать диалоги любой сложности.


Пример плоского диалога. Бот задаст первый вопрос, после ответа задаст второй, потом третий, потом запустит callback onFinish с готовыми ответами. На каждом шаге можно обрабатывать ответ пользователя через callback onNext.


import React from 'react';import { Dialog, DialogStep, Text } from '@urban-bot/core';function FlatDialogExample() {    return (        <Dialog onFinish={(answers) => console.log(answers)}>            <DialogStep                content={<Text>Привет, как тебя зовут?</Text>}                 id="name"                onNext={(name) => console.log(name)}            >                <DialogStep                    content={<Text>Cколько тебе лет?</Text>}                    id="age"                >                    <DialogStep                         content={<Text>Из какого ты города?</Text>}                        id="city"                    />                </DialogStep>            </DialogStep>        </Dialog>    );}

Можно получать на следующем шаге прошлый ответ через паттерн render-props .


function FlatDialogExample() {    return (        <Dialog>            <DialogStep content={<Text>Привет, как тебя зовут?</Text>}>                {(name) => (                    <DialogStep                         content={<Text>{`${name}, cколько тебе лет?`}</Text>}                    />                )}            </DialogStep>        </Dialog>    );}

Можно добавить валидацию на каждый шаг.


function FlatDialogExample() {    return (        <Dialog onFinish={(answers) => console.log(answers)}>            <DialogStep                content={<Text>Привет, как тебя зовут?</Text>}                id="name"                validation={{                     isValid: (answer) => answer !== 'Самуэль',                     errorText: 'Самуэль заблокирован.'                 }}            >                // ...            </DialogStep>        </Dialog>    );}

Также возможно построение более сложных диалогов, когда бот отвечает определенным образом в зависимости от ответа пользователя. Для предопределенных ответов можно использовать кнопки.


import React from 'react';import { Dialog, DialogStep, Text, ButtonGroup, Button } from '@urban-bot/core';function TreeDialogExample() {    return (        <Dialog>            <DialogStep                content={                    <ButtonGroup title="Привет, что вы хотите купить?">                        <Button id="hat">Футболка</Button>                        <Button id="glasses">Очки</Button>                    </ButtonGroup>                }            >                <DialogStep                    match="hat"                    content={                        <ButtonGroup title="Футболка какого размера?">                            <Button id="m">S</Button>                            <Button id="s">M</Button>                            <Button id="l">L</Button>                        </ButtonGroup>                    }                />                <DialogStep                    match="glasses"                    content={                        <ButtonGroup title="Очки какого цвета?">                            <Button id="black">Черный</Button>                            <Button id="white">Белый</Button>                        </ButtonGroup>                    }                />            </DialogStep>        </Dialog>    );}

Состояние


Что вы можете использовать для управления состоянием? Все то же что и в любом React приложении. Можете использовать React useState и передавать состояние ниже по дереву компонентов через пропсы или React Context. Можно использовать библиотеки для управления состоянием: Redux (пример), MobX (пример), Apollo и любые другие, которые обычно используют вместе с React, вы можете даже переиспользовать готовые части из готовых React Web или React Native приложений, так как Urban Bot использует тот же чистый React, который работает в миллионах приложений.


Сессия


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


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


function Counter() {    const [count, setCount] = React.useState(0);    const increment = () => setCount(count + 1);    const decrement = () => setCount(count - 1);    return (        <ButtonGroup title={count} isNewMessageEveryRender={false}>            <Button onClick={increment}>+1</Button>            <Button onClick={decrement}>-1</Button>        </ButtonGroup>    );}

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


function Counter() {    const [count, setCount] = useGlobalCount();   // ...}

Также, в любом месте приложения мы можем получить данные о конкретном чате, какой пользователь написал, с каким никнеймом и т.д. Если чат-бот персональный, то chat и from будут совпадать.


import React from 'react';import { Text, useText, useBotContext } from '@urban-bot/core';function UserId() {    const { chat } = useBotContext();    useText(({ from }) => console.log(`Пришло сообщение от ${from.username}`));   return <Text>Чат id {chat.id}</Text>;}

Типизация


Urban Bot написан на TypeScript, соответсвенно проект полностью типизирован, и если вы пишете на TypeScript, вам будет очень удобно.


Запуск в мессенджерах


Большой плюс Urban Bot, что он не привязан ни к одному мессенджеру. Есть основной пакет @urban-bot/core, который позволяет создавать абстрактных чат-ботов, а уже их подключать к определенным мессенджерам. В данный момент есть поддержка Telegram, Slack, Facebook. В дальнейшем, мы планируем добавлять любые мессенджеры, где есть чат-боты и открытое API. Если вам интересно, и вы хотите писать Urban Bot приложения для других мессенджеров, скажем Viber, Discord или у вас есть свой мессенджер то пишите к нам в группу https://t.me/urbanbotjs, одной просьбы будет достаточно, чтобы появилось большая мотивация реализовать ваш функционал.


Теперь перейдем к практике. Выше мы создавали компоненты и приложение, но не запускали его. Для запуска нам нужно выбрать мессенджер или мессенджеры, в которых мы хотим запустить наше приложение.


Скажем, у нас есть готовое приложение App и мы хотим его запустить его в Telegram. Для этого нам понадобится класс UrbanBotTelegram из пакет @urban-bot/telegram. Функция render из @urban-bot/core подобная ReactDOM.render и компонент Root. Мы создаем экземпляр UrbanBotTelegram и передаем туда бот токен из Telegram, также можно передать isPolling, чтобы не настраивать вебхук, и бот работал локально. Готовый экземпляр мы передаем в компонент Root, и оборачиваем наше готовое приложение и, соответсвенно, передаем все в функцию render, которая запустит все процессы.


Более подробная инструкция с видео как разрабатывать локально и деплоить Telegram бота можно прочитать в инструкции.


import React from 'react';import { render, Root } from '@urban-bot/core';import { UrbanBotTelegram } from '@urban-bot/telegram';import { App } from './App';const urbanBotTelegram = new UrbanBotTelegram({    token: 'telegramToken',    isPolling: true,});render(    <Root bot={urbanBotTelegram}>        <App />    </Root>);

Если нам нужно запустить два и более мессенджера, просто используем пакеты других мессенджеров, передаем туда нужные данные и делаем по аналогии с Telegram.


// ...import { UrbanBotSlack } from '@urban-bot/slack';// ...render(    <Root bot={urbanBotTelegram}>        <App />    </Root>);const urbanBotSlack = new UrbanBotSlack({    signingSecret: 'slackSigningSecret',    token: 'slackToken',});render(    <Root bot={urbanBotSlack}>        <App />    </Root>);

Прямой вызов API


С помощью Urban Bot вы можете создавать чат-ботов просто описывая их через компоненты. А что если вам будет нужно вручную вызвать API? Каждый экземпляр UrbanBot* содержит в себе API клиент для активного мессенджера. Рассмотрим пример для Telegram.


Мы можем получить bot с помощью хука useBotContext. bot содержит client и type c типом мессенджера. client будет представлять собой экземпляр библиотеки node-telegram-bot-api . В любом месте приложения можно получить client и вызвать любой метод на ваше усмотрение, скажем блокировать пользователя, если он написал нецензурное сообщение.


import React from 'react';import { useText, useBotContext } from '@urban-bot/core';function SomeComponent() {    const { bot } = useBotContext();    useText(({ text, chat, from }) => {        if (text.includes('бл***')) {            bot.client.kickChatMember(chat.id, from.id);        }    });    // ...}

В каждом мессенджере уникальный API. Если вы разрабатываете несколько мессенджеров одновременно, можно отделять функционал сравнивая bot.type.


import { useBotContext } from '@urban-bot/core';import { UrbanBotTelegram } from '@urban-bot/telegram';import { UrbanBotSlack } from '@urban-bot/slack';function SomeComponent() {    const { bot } = useBotContext();    if (bot.type === UrbanBotTelegram.type) {        // telegram api        bot.client.kickChatMember(/* ... */);    }    if (bot.type === UrbanBotSlack.type) {        // slack api        bot.client.conversations.kick(/* ... */);    }    // ...}

Как попробовать?


У Urban Bot есть стартер, который позволит вам начать разрабатывать чат-ботов за минуту, сделан по аналогии с create-rect-app. Все что вам нужно, чтобы попробовать Urban Bot это выполнить команду в терминале для


TypeScript


npx create-urban-bot my-app

JavaScript


npx create-urban-bot my-app --template js

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


Итого


Несмотря на то что Urban Bot запустился только недавно, на мой взгляд это библиотека с огромным потенциалом. Только представьте, если у вас есть базовые знания React, вы можете написать чат-бот любой сложности на все возможные платформы, создавать и использовать библиотеки с готовым набором компонентов на манер ui-kit, переиспользовать код между вашими другими UI приложеними на React, будь то web или mobile.


Если вы уже разрабатывали чат-ботов, попробуйте Urban Bot, вы почувствуете как библиотека делает за вас кучу работы. Если никогда не разрабатывали, но имеете представление о React, то напишете вашего первого чат-бота за 5 минут. Если вам понравилась идея и хотите, чтобы проект развивался дальше, вступайте в нашу группу в Telegram, ставьте звезду на гитхабе, будем рады любому фидбеку.


Полезные ссылки


Сайт
Github
Группа в Telegram
Наглядный чат-бот в Telegram, с открытым кодом.
Как создать Todo List чат-бот в Telegram с помощью React.js

Подробнее..

Перевод Путь к Федеративному GraphQL

10.09.2020 12:19:55 | Автор: admin
Картинка с dgraph.ioКартинка с dgraph.io

Программисты любят хорошие истории, поэтому надеюсь что пятилетний путь к композитному API с помощью GraphQL в боевой среде (на пике выдающей 110 запросов в секунду при 100мс задержке) будет интересен.

[Если вы спешите, проскрольте ниже к урокам и гляньте на открытый код graphql-schema-registry]

schema registry с примером тестовой схемыschema registry с примером тестовой схемы

Задача

Годами, Pipedrive (которому в начале 2020 стукнуло уже 10 лет), давал публичный REST API где были и недокументированные пути для нашего веб приложения. Один из них /users/self, который изначально задумывался для загрузки информации о пользователе, нормально с течением времени стал грузить все что надо для первой загрузки страницы, доходя до 30 разных типов сущностей. Сам он выдавался PHP монолитом, который по натуре синхронный. Мы пытались его распараллелить, но неудачно.

/users/self распределение задержки для оставшегося траффика/users/self распределение задержки для оставшегося траффика

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

Прототип с прямым доступом к БД

Давайте вернемся назад в прошлое, когда наши разработчики стали экспериментировать с graphql.

Года 3-4 назад, в команде разработки рынка приложений, я услышал от Паши, нашего фулл-стек разработчика новенькие для меня термины - elixir и graphql. Он участвовал в тестовом проекте в котором напрямую работал с MySQL и выдавал основные сущности Pipedrive по /graphql.

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

Прототип со склейкой

Переносимся в 2019, когда я заметил еще один тестовый репозиторий от другого коллеги, который стал использовать GraphQL stitching с определением сущностей с помощью graphql-compose и резолверами которые делали запрос уже к нашему REST API. Как можете представить, это уже было значительным улучшением, потому что нам не надо было переопределять всю бизнес-логику и graphql становился просто приятной оберткой.

Из недостатков этого решения:

  • Производительность. Небыло dataloader'а, поэтому был риск N+1 сетевых запросов. Небыло ограничения сложности запроса и какого-либо промежуточного кеширования.

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

Подготовка

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

Сбор ожиданий разработчиков

В компании некоторые разработчики были довольно скептичны и предлагали построить свое решение просто склеивая запросы воедино, отправляя по POST и надеясь на свой gateway который будет распараллеливать обработку.

Другие считали что graphql еще слишком сырой что-бы использовать в жизни и лучше подождать. Третьи предлагали рассмотреть альтернативы, типа Protobuf и Thrift, а в качестве транспорта рассмотреть GRPC,OData.

Четвертые напротив, на полном ходу писали graphql под одиночные сервисы (insights, teams) и выкатывали в лайв, однако не могли повторно использовать сущности (например User). В Праге ребята написали все на typescript + relay на фронтенде, который нам еще предстоит перенести в федерацию.

Изучать новую технологию было круто.

Строгий, само-документирующийся API для фронтендеров? Глобально определяемые сущности уменьшающие дублирование и улучшающие прозрачность и владение среди всех команд? Gateway который автоматом делает под-запросы и между сервисами без избыточной выборки? Ухх.

Впрочем, я знал что нам понадобится управлять схемой как-то динамически, что-бы не полагаться на захардкоженые значения и видеть что происходит. Нечно типа Confluents schema-registryилиAtlassians Braid, но они либо под Кафку делались, либо на Java, которую мы не хотели поддерживать.

План

Я выступил с инженерной миссией у которой было 3 цели:

  • Уменьшение первичной загрузки страницы (воронка продаж) на 15%. Достижимо если заменить некоторые REST запросы в один /graphql

  • Уменьшение траффика на 30%. Достижимо если перенести загрузку всех сделок для воронки продаж в graphql и спрашивать меньше свойств.

  • Использовать строгую типизацию (что-бы фронтендерам надо было писать меньше кода в защитном стиля)

Мне повезло, трое разработчиков присоединились к миссии, в т.ч. автор прототипа.

Сетевые запросы по мере загрузки веб-приложенияСетевые запросы по мере загрузки веб-приложения

Изначальный план по сервисам выглядел так:

Сервисы над которыми предстояло бы работатьСервисы над которыми предстояло бы работать

Тут schema-registry был бы общим сервисом, который мог бы хранить схему любого типа, получая на входе что вы в него кинете (swagger, typescript, graphql, avro, proto). На выходе он мог бы так же выдавать что вы хотите.

Gateway периодически опрашивал бы schema-registry и вызывал бы уже сервисы которые владеют данными согласно запросу и схеме. В свою очередь frontend компоненты могли бы скачивать актуальную схему для autocomplete и самих запросов.

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

Итоги

Главную цель по замене /users/self в веб-приложении мы сделали в течение первых двух недель ? (ничоси!). А вот полировка всего решения что-бы оно быстро и надежно работало, заняло все оставшееся время.

К концу миссии (в феврале 2020), мы каким-то чудом достигли 13% ускорения первой загрузки страницы и 25% ускорения при повторной загрузке (из-за добавленного кеширования), согласно синтетическому UI тесту который мы гоняли на Datadog.

Уменьшить трафик нам не удалось, потому что мы не достигли рефакторинга вида воронки продаж - там все еще REST.

Что-бы ускорить добавление сервисов в федерацию другими командами (у нас 600+ человек), мы записали обучающие видео что-бы все были в курсе как оно все работает вместе. После окончания миссии IOS- и Android- клиенты тоже мигрировали на graphql и в целом были рады.

Выученные уроки

Глядя на дневник миссии из этих 60 дней, могу выделить наибольшие трудности что-бы вам было бы попроще

Управляйте своей схемой

Могли бы мы построить это сами? Возможно, но это не было бы так отполировано.
Mark Stuart, Paypal Engineering

В течение первых пары дней я попробовал Apollo studioи ихинструментарий для терминала по проверке схемы. Сервис прекрасный и работает из коробки с минимальными настройками gateway.

Написанный нами инструментарий для валидации схемыНаписанный нами инструментарий для валидации схемы

Однако несмотря на все фишки этого облачного сервиса, я посчитал что для такого важного дела как маршрутизация траффика было бы опрометчиво полагаться на сторонний сервис из-за рисков для жизнеспособности нашего дела, несмотря на богатый функционал и сколько-то выгодные расценки. Поэтому мы написали свой сервис с минимально нужным функционалом и теперь отправили его в opensource - graphql-schema-registry.

Вторая причина была в том что-бы следовать модели распределенной архитектруры датацентров Pipedrive. У нас нет центрального датацентра, каждый DC самодостаточен. Это дает нам как более высокую надежность, так и способность открывать новые датацентры в Китае, России, Иране или на Марсе ?

Версионируйте свою схему

Федеративная схема и graphql gateway очень хрупкие. Если у вас появится конфликт имен у типов или неверная ссылка между типами в одном из сервисов, то gateway может этого не пережить.

По умолчанию, gateway напрямую опрашивает все сервисы касательно их схемы, поэтому достаточно одного из сервисов с ошибкой что-бы поломать всем трафик. Apollo studio решает это посредством проверки схемы еще до деплоя и отказе если видит какой-то конфликт.

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

Мы же напротив, связали версии микросервисов (генерируемые на основе хешей докер-образов) со схемой. Микросервисы регистрируют уже в ходе своей работы и делают это один раз без последующих повторений. Gateway делает выборку всех федерированных сервисов из консула и спрашивает schema-registry составить схему /schema/compose , предоставляя их версии.

Если schema-registry видит что предоставленные версии несовместимы, она откатывается на прошлые версии

Регистрация схемы при старте сервисаРегистрация схемы при старте сервиса

Микросервисы могут выдавать как REST так и Graphql API, поэтому мы используем оповещения тревоги при неудачной регистрации схемы, оставляя при этом работу REST.

Определение схемы на основе REST не просто

Поскольку я не знал как лучше перенести схему из нашего REST в graphql, я сначала попробовал openapi-to-graphql, нормально наша документация на тот момент не имела детального описания запросов и ответов.

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

В дальнейшем это нам аукнулось, так как оказалось что REST API иногда зависит от того кто делает запрос, либо зависит от внутреннего состояния и логики.

Например после настраиваемые поля данных меняют REST API в зависимости от пользователя. Если его добавить к сделке, то он оно будет выдаваться с ключем-хешем на уровне deal.pipeline_id. С федеративным graphql, динамическая схема невозможно, поэтому нам пришлось передвигать эти настраиваемые поля в отдельный список

Другая проблема это стиль наименований в json-ответе. Мы хотели поддерживать верблюжийСтиль, но почти весь REST отвечал нам в змеином_стиле, пришлось пока остаться на смешанном.

федеративный граф данных Pipedrive (слева) с 2 сервисами (из 539) и еще-не-федеративный граф сервиса leads (справа)федеративный граф данных Pipedrive (слева) с 2 сервисами (из 539) и еще-не-федеративный граф сервиса leads (справа)

CQRS и кеш

Модель данных в Pipedrive не может полагаться на простой TTL-кеш.

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

Что-бы делать параллельные запросы к PHP-монолиту, мы написали сервис на nodejs (назвали его monograph), который кеширует все что отвечает монолит в memcached. Этот кеш надо чистить из монолита в зависимости от бизнес-логики. Это немного антипаттерн, потому что кеш получается общим для разных сервисов и сильно их связывает.

Тут можно заметить CQRS pattern. Впрочем, такой кеш позволяет ускорять 80% запросов и дает итоговую среднюю задержку такую же как и у монолита.

Время средней задержки ответа монолита (слева) и graphql gateway (справа) в регионе US на основе NewRelicВремя средней задержки ответа монолита (слева) и graphql gateway (справа) в регионе US на основе NewRelic

Еще одна сложность - язык пользователя. Как только он меняется, нам надо чистить уйму разных сущностей - от типов активности до того какие настройки должны быть при показе гугл карт. И что еще хуже - язык уже управляется не в монолите, а был вынесен в отдельный сервис identity. Связывать его с общим кешем очень не хотелось.

Identity владеет данными пользователей, поэтому мы сделали так что он кидает событие через kafka, которое ловит monograph и чистит все нужные кеши. Тут есть недостаток, что событие приходит с задержкой (может максимум 1 сек), поэтому чистка кеша происходит не мгновенно. Нормально это не критично, потому что пользователь не так быстро уходит со страницы что-бы сделать повторный запрос к бекенду и заметить что в кешах еще старый язык.

Следите за производительностью

Производительность была главной целью и для этого нам пришлось хорошо прокачаться в знании APM и выслеживании распределенных запросов между микросервисами что-бы определить самые медленные места. Мы на тот момент использовали Datadog и он хорошо себя зарекомендовал.

Для ускорения, мы кешировали все 30 параллельных запросов в memcached. Но по ходу дела возникали проблемы. Например на картинке слева видно что некоторые резолверы делают запрос в memcached и получают ответ за 10мс, а другие тупят, словно жду своей очереди выростая до 220мс. Как оказалось, это было из-за того что я использовал один и тот же хост mcrouter. Как только я стал их ротировать, мемкеш стал отдавать все за 20мс максимум.

Что-бы уменьшить количество этих сетевых запросов к мемкешу, мы склеили его в один используя getMultiфункцию. Получается что несмотря на то что все resolver'ы выполняются независимо, они блочатся на 5мс с помощью debounce и склеиваются воедино.

На картинке справа видна другая проблема. Эти желтые линии это налог graphql gateway на приведение данных к строгому типу. Причем чем больше данных вы гоняете через сервис, тем дольше будет эта фаза выполнятся.

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

Нам пришлось вынести их в отдельный graphql запрос, который делается после первого /graphql запроса на инициализацю страницы. Так что сейчас мы на деле делаем уже порядка 3-5 независимых запросов, в зависимости от тайминга фронтенда (там тоже debounce зависящий от параллельной подгрузки FE компонентов)

Не следите за производительностью

Казалось бы полностью противоположный урок, но на деле это значит что в лайв-среде вам лучше отключить мониторинг приложения (APM) и tracing:true, если вы используете их.

Отключение отслеживания у нас ускорило среднюю задержку в два раза с 700мс до 300мс для нашей тестовой компании. Насколько я понимаю, причина крылась в функциях которые замеряют время (типа performance.now()), которые необходимы для замера каждого резолвера.

Мы делали профайлинг graphql gateway с помощью Chrome DevTools, но так просто вызов этих мелких функций, раскиданных повсюду не заметишь.

Бен тутделал замеры и тоже пришел к этому.

Попробуйте ранний запрос

На фронтенде, время создания graphql запроса для нас оказалось сложновато. Я хотел передвинуть первый /graphql как можно раньше (даже до загрузки всего кода в vendors.js) в графике сетевых запросов. Мы смогли это сделать, но веб-приложение из-за этого стало значительно сложней.

Что-бы делать запрос вам понадобится graphql клиенти парсингgql литералов для определения самого запроса. Дальше его надо либо выдавать в отдельном bundle, либо отказываться от всех удобств и использовтать сырой fetch. Даже если делать сырой запрос, дальше надо что-то делать с ответом - распределять данные по моделям либо сохранять в глобальную переменную. Вобщем мы решили отказаться от этого и надеяться на server-side-rendering и service workers в будущем.

Перенос /graphql запроса левейПеренос /graphql запроса левей

Оценивайте сложность запроса

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

Сначала мы попробовали graphql-cost-analysisбиблиотеку, но в итоге написали свою, поскольку захотели больше фич - множителях при выдаче списков данных, вложенности и рекурсии, а так же определении типов влияния на инфраструктуру (сетевой запрос, БД, CPU, работа с файлами). Тут сложней всего оказалось внедрять поддержку своей директивы в gateway и schema-registry. Надеюсь мы нашу библиотеку тоже опубликуем.

У схемы много лиц

Работать со схемой в js/typescript на низком уровне порой мучительно. Это начинаешь понимать когда пробуешь интегрировать существующий graphql сервис в федерацию.

Например настройки koa-graphqlиapollo-server-koa ожидают что вы создадите вложенный объект GraphQLSchema который включает и сами резолверы. Но федеративный apollo/server хочет схему и резолверы отдельно:

buildFederatedSchema([{typeDefs, resolvers}])

В другом случае, как я уже писал, вам захочется определить схему с помощью gql литерала, или сохранить схему в schema.graphql файл, а когда вам надо будетделать проход по всей схеме для оценки сложности, понадобитсяASTNode(и функции parse/buildASTSchema)

Плавная canary деплой

Во время миссии мы выкатывали изменения сначала всем разработчикам что-бы отследить очевидные ошибки.

Под конец миссии в феврале, мы выкатили graphql только на 100 компаний-"везунчиков". Дальше мы медленно выкатывали их до 1000 компаний, потом 1%, 10%, 30%, 50% и наконец в июне выкатили всем.

Деплоили мы просто используя логику деления company ID без остатка. В дополнение, у нас было несколько настроек - кому еще включать, кому не включить никогда и рубильник выключающий graphql сразу у всех на фронтенде. Было очень полезно во время инцидентов сразу избавиться от подозрений что это все из-за нового graphql и упростить дебаг.

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

Надежды и мечты

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

Как только наш graphql API станет стабильным и достаточным для использования любыми пользователями, его можно выкатить в качестве второй версии нашего API. Для публикации впрочем понадобятся еще директивы для ограничения доступа к сущностям согласно OAuth видимости (для приложений из рынка) и согласно нашим продуктам.

В самой schema-registry надо добавить отслеживание клиентов, связать ее gateway для получения аналитики трафика что-бы проще было понять какие поля можно удалять, надо добавить фильтрацию и подсветку сущностей в зависимости от директив цены и видимости, валидировать именование, хранить постоянные запросы (persisted query), публиковать историю изменений итоговой схемы для сторонних разработчиков.

Кроме того, поскольку у нас есть сервисы на go, еще непонятно как стоит делать внутренний трафик - стоит ли использовать GRPC из-за скорости и надежности, либо использовать независимые graphql эндпоинты, либо ставить централизованный внутренний gateway. Если GRPC лучше только из-за его бинарности, может мы можем написать библиотечку которая тоже упакует graphql данные с помощью msgpack?

Что касается внешнего мира, надеюсь Apollo со своим проектом Constellation ускорят обработку запроса на Rust что-бы небыло этих 10% налога на производительность и смогут федерировать graphql сервисы без серьезных изменений последних.

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

Подробнее..

Соединяем Redux и GraphQL на простом примере

19.10.2020 12:14:37 | Автор: admin

Попробуем соединить Redux и GraphQL без использования Apollo Client или Relay.

Что такое Redux

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

Псевдокод:

function dispatch(action) {    state = reducer(state, action);}

Для соединения реакта и редакса используется библиотека react-redux. Она позволяет получать данные из state внутри компонентов и инициировать их обновление через dispatch.

Что такое GraphQL

GraphQL для клиента это просто POST-запрос с особым форматом body:

REST

GraphQL

GET /books/id

POST /graphql
query{ book(id) {} }

POST /books
{}

POST /graphql
mutation{ addBook() {...} }

UPDATE /books/id
{}

POST /graphql
mutation{ updateBook(id) {...} }

DELETE /books/id

POST /graphql
mutation{ deleteBook(id) {} }

GraphQL без Apollo Client (и Relay)

Apollo-client это самая популярная библиотека для работы с GraphQL. Она предоставляет интерфейс для работы с запросами внутри компонентов, а также решает задачу хранения кэша. Вместо Redux предполагается использовать внутренние механизмы. Однако эти механизмы далеко не такие простые и удобные.

Вместо Apollo Client для примера воспользуемся такой функцией:

const graphqlAPI = (query, variables) => fetch("/graphql", {    method: "POST",    body: JSON.stringify({ query, variables })});

redux-toolkit

Я буду использовать официальный набор утилит от redux redux-toolkit. Они решают самую частую претензию к редаксу бесконечное количество шаблонного кода. В этот набор входит:

  • createEntityAdapter для упрощения работы с редьюсерами и селекторами.

  • createAsyncThunk для упрощения работы с сайд-эффектами и API.

  • createSlice для упрощения работы с actions и разрешения мутабельности.

Пример

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

// booksSlice.jsimport {  createEntityAdapter,  createAsyncThunk,  createSlice} from "@reduxjs/toolkit";// Наш редьюсер будет хранить список книг.// createEntityAdapter генерирует готовый набор функций// для добавления, обновления и удаления книг внутри редьюсера// (addOne, addMany, updateOne, removeOne, setAll, ...), const books = createEntityAdapter();// createAsyncThunk позволяет работать с асинхронным кодом.// Например, с запросами к API.// getBooks работает как набор actions, которые можно диспатчить в приложении.export const getBooks = createAsyncThunk("get books", async () =>   await graphqlAPI(`    query {      books  {        id        title      }    }  `));export const addBook = createAsyncThunk("add book", ({ title }) =>  graphqlAPI(`    mutation ($title: string!){      add_book(objects: { title: $title }) {        id        title      }    }  `, { title }));// createSlice  обертка над createAction и createReducer.// Она позволяет работать со стейтом мутабильно.export const booksSlice = createSlice({  name: "books",  initialState: books.getInitialState(),  extraReducers: {    // Работаем с результатом запроса getBooks    [getBooks.fulfilled]: (state, action) => {      // setAll автоматически создан в createEntityAdapter      books.setAll(state, action.payload);    },    // Работаем с результатом запроса addBook    [addBook.fulfilled]: (state, action) => {      // addOne автоматически создан в createEntityAdapter      books.addOne(state, action.payload);    },  },});// createEntityAdapter генерирует набор готовых селекторов// (selectIds, selectById, selectAll, ...)export const booksSelectors = books.getSelectors((s) => s.books);export default booksSlice.reducer;
// index.jsimport { useDispatch, useSelector } from "react-redux";import { getBooks, addBook, booksSelectors } from "./booksSlice";export function Books() {  const dispatch = useDispatch();  // booksSelectors.selectAll втоматически создан через createEntityAdapter  const books = useSelector(booksSelectors.selectAll);   useEffect(() => {    dispatch(getBooks());  }, [dispatch]);  return (    <div>      <button onClick={() => dispatch(addBook({ title: "New Book" }))}>        Add book      </button>      <ul>        {books.map((b) => <li key={b.id}>{b.title}</li>)}      </ul>    </div>  );}

Что дальше

Поэкспериментировать с Redux можно локально.
Для настройки окружения достаточно одной команды:
npx create-react-app my-app --template redux

Для Typescript:
npx create-react-app my-app --template redux-typescript

Вместо fetch можно воспользоваться библиотекой graphql-request.

Создать бэкенд на GraphQL можно без написания кода.
Для этого подходят такие проекты как hasura или prisma.

Подробнее..

Перевод Как создать приложение-чат за двадцать минут

09.11.2020 12:16:47 | Автор: admin
image

Мой отец любит напоминать мне, что, будучи компьютерным инженером в 1970-х, он был программистом до того, как программирование стало модным. Пару раз он даже показывал старые скрипты Fortran и COBOL. Прочитав этот код, я с уверенностью могу сказать, что программирование сегодня определенно круче.

Отличительная черта современных языков программирования и сред разработки это то, насколько меньше кода приходится писать разработчику. Используя высокоуровневые языки вместе со множеством доступных API-интерфейсов, пакетов с открытым исходным кодом и платных сервисов, приложения даже со сложными требованиями можно создавать довольно быстро.

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

Сколько было бы построено небоскребов, если бы строители сами добывали себе сталь?

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

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

Как сделать приложение для чата


Давайте быстро создадим что-нибудь, что раньше занимало бы дни или недели. Мы сделаем Public Chat Room приложение, которое использует WebSockets для обмена сообщениями в реальном времени.

WebSockets нативно поддерживаются всеми современными браузерами. Однако наша цель выяснить, какие инструменты мы можем использовать в работе, а не изобретать их. Учитывая это, мы будем использовать следующие технологии:

  • 8base управляемый GraphQL API
  • VueJS JavaScript фреймворк

Стартовый проект и полный файл README можно найти в этом репозитории GitHub. Если вы хотите просмотреть только готовое приложение, загляните в ветку public-chat-room.

Кроме того, в видео ниже (на английском языке) более подробно объясняется каждый шаг.

Начнем.

Семь шагов для создания чат приложения:


1. Настройка проекта


Клонируйте стартовый проект и перейдите в директорию группового чата. Вы можете сами определить, использовать yarn или npm для установки зависимостей проекта. В любом случае, нам нужны все NPM пакеты, обозначенные в файле package.json.

# Клонируем проектgit clone https://github.com/8base/Chat-application-using-GraphQL-Subscriptions-and-Vue.git group-chat# Переходим в директориюcd group-chat# Устанавливаем зависимостиyarn

Чтобы взаимодействовать с GraphQL API, мы должны настроить три переменные среды. Создайте файл .env.local в корневой директории с помощью следующей команды, и приложение Vue после инициализации автоматически установит переменные среды, которые мы добавили в этот файл.

echo 'VUE_APP_8BASE_WORKSPACE_ID=<YOUR_8BASE_WORKSPACE_ID>
VUE_APP_8BASE_API_ENDPOINT=https://api.8base.com
VUE_APP_8BASE_WS_ENDPOINT=wss://ws.8base.com' \
> .env.local


Оба значения VUE_APP_8BASE_API_ENDPOINT и VUE_APP_8BASE_WS_ENDPOINT менять не нужно. Необходимо только установить значение VUE_APP_8BASE_WORKSPACE_ID.

Если у вас есть воркспейс 8base, который вы хотите использовать для создания чат-приложения по нашему руководству, обновите файл .env.local, указав свой идентификатор воркспейса. Если нет получите идентификатор воркспейса, выполнив шаги 1 и 2 из 8base Quickstart.

2. Импорт схемы


Теперь нам нужно подготовить серверную часть. В корне этого репозитория вы должны найти файл chat-schema.json. Чтобы импортировать его в рабочую область, нужно просто установить командную строку 8base и залогиниться, а затем импортировать файл схемы.

# Установка 8base CLIyarn global add 8base-cli# Аутентификация CLI8base login# Импортируем схему в нашу рабочую область8base import -f chat-schema.json -w <YOUR_8BASE_WORKSPACE_ID>

3. Доступ к API


Последняя задача по бэкенду разрешить публичный доступ к GraphQL API.

В консоли 8base перейдите в App Services > Roles > Guest. Обновите разрешения, установленные как для сообщений, так и для пользователей, чтобы они были или отмечены галочкой, или установлены как All Records (как показано на скриншоте ниже).

Роль Guest определяет, что разрешено делать пользователю, отправившему неаутентифицированный запрос к API.

image
Редактор ролей в консоли 8base.

4. Пишем GraphQL запросы


На этом этапе мы собираемся определить и выписать все запросы GraphQL, которые нам понадобятся для нашего компонента чата. Это поможет нам понять, какие данные мы будем читать, создавать и прослушивать (через WebSockets) с помощью API.

Следующий код следует поместить в файл src / utils / graphql.js. Прочтите комментарии над каждой экспортированной константой, чтобы понять, что выполняет каждый запрос.

/* gql преобразует строки запроса в документы graphQL */import gql from "graphql-tag";/* 1. Получение всех пользователей онлайн-чата и последних 10 сообщений */export const InitialChatData = gql`{  usersList {    items {      id      email    }  }  messagesList(last: 10) {    items {      content      createdAt      author {        id        email      }    }  }}`;/* 2. Создание новых пользователей чата и назначение им роли гостя */export const CreateUser = gql`mutation($email: String!) {  userCreate(data: { email: $email, roles: { connect: { name: "Guest" } } }) {    id  }}`;/* 3. Удаление пользователя чата*/export const DeleteUser = gql`mutation($id: ID!) {  userDelete(data: { id: $id, force: true }) {    success  }}`;/* 4. Подписка на создание и удаление пользователей чата */export const UsersSubscription = gql`subscription {  Users(filter: { mutation_in: [create, delete] }) {    mutation    node {      id      email    }  }}`;/* 5. Создание новых сообщений чата и связывание их с автором */export const CreateMessage = gql`mutation($id: ID!, $content: String!) {  messageCreate(    data: { content: $content, author: { connect: { id: $id } } }  ) {    id  }}`;/* 6. Подписка на создание сообщений чата. */export const MessagesSubscription = gql`subscription {  Messages(filter: { mutation_in: create }) {    node {      content      createdAt      author {        id        email      }    }  }}`;


5. Настройка Apollo клиента для подписок


Когда наши запросы GraphQL написаны, самое время настроить наши модули API.

Во-первых, давайте займемся клиентом API с помощью ApolloClient с его обязательными настройками по умолчанию. Для createHttpLink мы предоставляем наш полностью сформированный эндпоинт воркспейса. Этот код находится в src/utils/api.js.

import { ApolloClient } from "apollo-boost";import { createHttpLink } from "apollo-link-http";import { InMemoryCache } from "apollo-cache-inmemory";const { VUE_APP_8BASE_API_ENDPOINT, VUE_APP_8BASE_WORKSPACE_ID } = process.env;export default new ApolloClient({link: createHttpLink({  uri: `${VUE_APP_8BASE_API_ENDPOINT}/${VUE_APP_8BASE_WORKSPACE_ID}`,}),cache: new InMemoryCache(),});// Note: Чтобы узнать больше о параметрах, доступных при настройке // ApolloClient, обратитесь к их документации.

Затем займемся клиентом подписки, используя subscriptions-transport-ws и isomorphic-ws. Этот код немного длиннее, чем предыдущий, поэтому стоит потратить время на чтение комментариев в коде.

Мы инициализируем SubscriptionClient, используя наш эндопоинт WebSockets и workspaceId в параметрах connectionParams. Затем мы используем этот subscriptionClient в двух методах, определенных в экспорте по умолчанию: subscribe() и close().

subscribe позволяет нам создавать новые подписки с обратными вызовами данных и ошибок. Метод close это то, что мы можем использовать, чтобы закрыть соединение при выходе из чата.

import WebSocket from "isomorphic-ws";import { SubscriptionClient } from "subscriptions-transport-ws";const { VUE_APP_8BASE_WS_ENDPOINT, VUE_APP_8BASE_WORKSPACE_ID } = process.env;/*** Создайте клиент подписки, используя соответствующие*переменные среды и параметры по умолчанию.*/const subscriptionClient = new SubscriptionClient(VUE_APP_8BASE_WS_ENDPOINT,{  reconnect: true,  connectionParams: {    /**      * Workspace ID ОБЯЗАТЕЛЬНО должен быть установлен, иначе *конечная точка Websocket не сможет*сопоставить запрос с соответствующим воркспейсом      */    workspaceId: VUE_APP_8BASE_WORKSPACE_ID,  },},/**  * Конструктор для реализации WebSocket, совместимой с W3C. *Используйте это, если ваше окружение не имеет встроенного собственного *WebSocket (например, с клиентом NodeJS)  */WebSocket);export default {/**  * Принимает запрос подписки, любые переменные и обработчики колбэков *'data и 'error  */subscribe: (query, options) => {  const { variables, data, error } = options;  /**    * Запускает новый запрос на подписку.    */  const result = subscriptionClient.request({    query,    variables,  });  /**    * Функцию отписки можно использовать для закрытия *определенной подписки, в отличие от ВСЕХ подписок, *поддерживаемых subscriptionClient    */  const { unsubscribe } = result.subscribe({    /**      * При получении события результат передается в *колбэк данных, указанный разработчиком.      */    next(result) {      if (typeof data === "function") {        data(result);      }    },    /**      * Каждый раз при получении ошибки она передается в колбэк ошибок, указанный разработчиком.      */    error(e) {      if (typeof error === "function") {        error(e);      }    },  });  return unsubscribe;},/**  * Закрывает subscriptionClient соединение.  */close: () => {  subscriptionClient.close();},};// Примечание. Чтобы узнать больше о SubscriptionClient и его параметрах, // пожалуйста, обратитесь к их документации.

6. Написание компонента Vue


Теперь у нас есть все необходимое для создания публичного чата. Осталось только написать один компонент GroupChat.vue.

Загрузите компонент с помощью yarn serve, и продолжим.

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

Скрипт компонента


Сначала нам нужно импортировать наши модули, простые стили и GraphQL запросы. Всё это находится в нашем каталоге src / utils.
Объявите следующие импорты в GroupChat.vue.

/* API модули */import Api from "./utils/api";import Wss from "./utils/wss";/* graphQL операции */import {InitialChatData,CreateUser,DeleteUser,UsersSubscription,CreateMessage,MessagesSubscription,} from "./utils/graphql";/* Стили */import "../assets/styles.css";

Компонентные данные


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

/* imports ... */export default {name: "GroupChat",data: () => ({  messages: [],  newMessage: "",  me: { email: "" },  users: [],}),};

Хуки жизненного цикла


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

/* ипорты... */export default {/* остальные параметры ... *//**  * Хук жизненного цикла, выполняющийся при создании компонента.  */created() {  /**    * Подписка на событие, которое срабатывает при создании или удалении пользователя    */  Wss.subscribe(UsersSubscription, {    data: this.handleUser,  });  /**    * Подписка на событие, которое срабатывает при создании сообщения    */  Wss.subscribe(MessagesSubscription, {    data: this.addMessage,  });  /**    * Получение начальные данные чата (пользователи и последние 10 сообщений)    */  Api.query({    query: InitialChatData,  }).then(({ data }) => {    this.users = data.usersList.items;    this.messages = data.messagesList.items;  });  /**    * Колбэк вызывается при обновлении страницы, чтобы закрыть чат    */  window.onbeforeunload = this.closeChat;},/**  * Хук жизненного цикла, выполняющийся при уничтожении компонента.  */beforeDestroy() {  this.closeChat();},};

Методы компонента


Мы должны добавить определенные методы, предназначенные для обработки каждого вызова / ответа API (createMessage, addMessage, closeChat, и т.д.). Все они будут сохранены в объекте методов нашего компонента.

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

Большинство этих методов говорят сами за себя. В любом случае, прочтите комментарии в следующем коде.

/* импорты ... */export default {/* остальные параметры ... */methods: {  /**    * Создание нового пользователя, используя указанный адрес электронной почты.    */  createUser() {    Api.mutate({      mutation: CreateUser,      variables: {        email: this.me.email,      },    });  },  /**    * Удалиние пользователя по его ID.    */  deleteUser() {    Api.mutate({      mutation: DeleteUser,      variables: { id: this.me.id },    });  },  /**    * Наши пользователи подписываются на события создания и обновления, и поэтому *нам нужно выбрать соответствующий метод для обработки ответа в зависимости от *типа мутации.**Здесь у нас есть объект, который ищет тип мутации по имени, возвращает *его и выполняет функцию, передавая узел события.    */  handleUser({    data: {      Users: { mutation, node },    },  }) {    ({      create: this.addUser,      delete: this.removeUser,    }[mutation](node));  },  /**    * Добавляет нового пользователя в массив users, сначала проверяя, *является ли добавляемый пользователь текущим пользователем.    */  addUser(user) {    if (this.me.email === user.email) {      this.me = user;    }    this.users.push(user);  },  /**    * Удаляет пользователя из массива users по ID.    */  removeUser(user) {    this.users = this.users.filter(      (p) => p.id != user.id    );  },  /* Создать новое сообщение */  createMessage() {    Api.mutate({      mutation: CreateMessage,      variables: {        id: this.me.id,        content: this.newMessage,      },    }).then(() => (this.newMessage = ""));  },  /**    * Наша подписка на сообщения проверяет только событие создания.  *Поэтому все, что нам нужно сделать, это поместить его в наш массив *сообщений.    */  addMessage({    data: {      Messages: { node },    },  }) {    this.messages.push(node);  },  /**    * Мы хотим закрыть наши подписки и удалить пользователя. Этот метод можно вызвать в нашем хуке жизненного цикла beforeDestroy и любом другом соответствующем колбэке.    */  closeChat () {    /* Закрытие подписки перед выходом */    Wss.close()    /* Удаление участника */    this.deleteUser();    /* Установка значения по умолчанию */    this.me = { me: { email: '' } }  }},/* Хуки ... */}

Шаблон компонента


И последнее, но не менее важное: у нас есть компонент GroupChat.vue.

Есть тысячи отличных руководств о том, как создавать красивые пользовательские интерфейсы. Это не одно из них.

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

Как всегда, читайте встроенные комментарии к коду.

<template><div id="app">  <!--    Представление чата должно отображаться только в том случае, если текущий пользователь имеет идентификатор. В противном случае отображается форма регистрации..    -->  <div v-if="me.id" class="chat">    <div class="header">      <!--        Поскольку мы используем подписки, которые работают в режиме реального времени, количество пользователей, которые сейчас находятся в сети, будет динамически корректироваться.        -->      {{ users.length }} Online Users      <!--       Пользователь может выйти из чата, вызвав функцию closeChat..        -->      <button @click="closeChat">Leave Chat</button>    </div>    <!--    Каждое сообщение, которое мы храним в массиве сообщений, мы будем отображать в этом div. Кроме того, если идентификатор участника сообщения совпадает с идентификатором текущего пользователя, мы присвоим ему класс me.      -->    <div      :key="index"      v-for="(msg, index) in messages"      :class="['msg', { me: msg.participant.id === me.id }]"    >      <p>{{ msg.content }}</p>      <small        ><strong>{{ msg.participant.email }}</strong> {{ msg.createdAt        }}</small      >    </div>    <!--Инпут сообщения привязан к свойству данных newMessage.      -->    <div class="input">      <input        type="text"        placeholder="Say something..."        v-model="newMessage"      />      <!--       Когда пользователь нажимает кнопку отправки, мы запускаем функцию createMessage.        -->      <button @click="createMessage">Send</button>    </div>  </div>  <!--   Процесс регистрации просит пользователя ввести адрес электронной почты. Как только инпут теряет фокус, вызывается метод createUser.    -->  <div v-else class="signup">    <label for="email">Sign up to chat!</label>    <br />    <input      type="text"      v-model="me.email"      placeholder="What's your email?"      @blur="createUser"      required    />  </div></div></template>

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

7. Заключение и тестирование


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

Надеюсь, вы также узнали, как инициализировать ApolloClient и SubscriptionClient для эффективного выполнения запросов GraphQL, мутаций и подписок в воркспейсе 8base, а также немного о VueJS.

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

Создайте чат-приложение с 8base


8base это готовый к работе бессерверный бэкенд-как-сервис, созданный разработчиками для разработчиков. Платформа 8base позволяет разработчикам создавать потрясающие облачные приложения с использованием JavaScript и GraphQL. Узнайте больше о платформе 8base здесь.
Подробнее..

Программа HolyJS нюансы DevTools, минусы GraphQL, инструменты a11y

01.04.2021 14:11:52 | Автор: admin


Осталось меньше месяца до конференции HolyJS (20-23 апреля, онлайн) пора рассказать, что именно там будет. Под катом описания докладов с разбивкой по тематическим блокам. А для начала несколько вопросов для затравки:


  • В чём недостатки GraphQL?
  • Зачем OCaml на фронтенде?
  • Чего вы не знаете о DevTools?
  • Как писать надёжные тесты для Vue?
  • Как сделать свой DSL-язык легко и непринуждённо?
  • Как добиться на дешёвом устройстве плавности дорогого?
  • Как отобразить 100500 метрик и не сойти с ума?
  • Как принести в JS типы ещё радикальнее, чем в TypeScript?

Ответы на всё это и многое другое в докладах.


Оглавление


Инструменты
Производительность
Фреймворки
Языки
Микрофронтенды
Основы
Визуал
Заключение



Инструменты


Всё, что вам нужно DevTools, Виталий Фридман


DevTools очень продвинутый и полезный набор инструментов, но также он может быть устрашающим и непосильным. Давайте исправим это. Виталий пройдёт через десятки полезных и скрытых фич DevTools, советов и приёмов, объяснит пугающий перформанс и сетевые панели, покажет навороченные экспериментальные фичи и процесс дебага для работы с CSS и JS.


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


Чем хороша тема: DevTools использует каждый, но там столько подводных камней, что без помощи не разобраться! Возможно, сверхсеньоры и не найдут чего-то прорывного, но даже им будет интересно увидеть flow работы Виталия.




Сделать сайт доступным за 60 секунд, Глафира Жур и Денис Бирюк


Все вокруг говорят о доступности: она нужна, важна и должна быть добавлена в проект на самом раннем этапе. Но никто не говорит, как рядовому фронтенд-разработчику, путающему a11y и 11ty, разобраться в вопросе. Какой пакет подключить в Webpack, чтобы проблемы решались на этапе сборки? Где в консоли найти отчёт об ошибках a11y? И как проверить результат своей работы после деплоя?


Чем хороши спикеры: Два спикера два мира. Глафира эксперт по интерфейсам, Денис любит прикручивать автоматизацию к фронтенд-проектам.


Чем хороша тема: Рассказывает про доступность с точки зрения инженера: что и как можно автоматизировать, какой тулинг позволяет проверять доступность, и всё подобное, что так любят программисты.


Кому будет полезно: Знаете про доступность, но не практикуете, потому что ну это же сложно? Тут расскажут, что конкретно добавить в ваш проект, чтобы пользователям было удобнее.




WebXR в реальной жизни, Роман Пономарев


Дополненная и виртуальная реальности отличные инструменты для демонстрации продукта. А браузер всегда под рукой и позволяет визуализировать что угодно без скачивания приложений. В докладе рассмотрим существующие технологии для работы с AR и VR в вебе, стандарты, лежащие в их основе, а также затронем состояние XR-индустрии в целом, увидим демки и узнаем, как можно продать это всё бизнесу.


Чем хорош спикер: У Романа есть практический опыт в теме. Некоторые зрители также могут знать его по Фронтенд Юности.


Чем хороша тема: Даже в доковидные времена Virtual/Augmented Reality всё больше проникала в нашу жизнь, а уже теперь и подавно. Тем временем спецификация WebXR взрослеет и позволяет делать всё больше.




Make your authentication flow perfect, Anton Nemtsev


Есть очень много сайтов, где нужно зайти, ввести email, указать телефон, подтвердить это все через ввод кода из SMS. Авторизация и регистрация постоянно балансирует между удобно и секьюрно, причем чаще в сторону секьюрно.
Как сделать так, чтобы было и удобно, и секьюрно в докладе Антона Немцева.


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


Кому будет полезно: Если вы работали с формами авторизации и регистрации, либо вам интересно, как можно сделать их удобными




Воркшоп. GitLab + CI/CD + JavaScript = любовь, Виталий Слободин


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


Чем хорош спикер: Ранее на HolyJS Виталий рассказывал про headless browsers, и доклад приняли отлично: глубоко знать тему ему помог личный опыт разработки PhantomJS. А теперь он работает в GitLab так что снова поговорит о том, к чему сам причастен.


Чем хороша тема: В результате развития подхода DevOps сейчас JS-разработчикам самим приходится думать про то, как их код будет тестироваться и собираться. Виталий обещал дать набор готовых рецептов.




Serverless и Edge Computing на практике, Алексей Тактаров


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


В докладе Алексей рассмотрит практическое применение лямбд от Vercel и Cloudflare: проектирование фронтенд-микросервисов, их авторизацию, работу с CDN и особенности кеширования на пограничных (edge) серверах. Будут затронуты архитектурные приемы для построения легко масштабируемых сервисов на примере задач: рендеринг PDF/DOCX-документов, генерация above-the-fold CSS для страниц на лету в Cloudflare Workers, отрисовка OG-картинок.


Чем хорош спикер: Алексей очень опытный спикер и специалист очень широкого спектра: отлично разбирается во фронтенд-разработке, дизайне, бекенде, инфраструктуре, безопасности, оптимизации и много другом. А благодаря стартаперскому бэкграунду понимает, что полезно бизнесу.


Чем хороша тема: Достаточно высокая практическая польза. Много примеров и будет показан реальный опыт. Применение этих знаний можно легко использовать для улучшения архитектуры и скорости проектов многих зрителей. Доклад охватывает достаточно широкую аудиторию подходит практически для любого уровня.




Гиперавтоматизированный пайплайн, или Почему матрица должна победить, Алексей Золотых


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


Но настало время машин. Алексей расскажет о том, как быстро захватить власть в проекте на JavaScript или TypeScript. Как унижать и угнетать разработчиков так, чтобы они при этом оставались эффективными и счастливыми. И как не допустить появления Нео!


Чем хорош спикер: Пишет на JavaScript c 2007 года, давно участвует в HolyJS в качестве участника программного комитета.


Чем хороша тема: В наше девопсовое время вопросы автоматизации пайплайнов всё актуальнее.




Воркшоп: Знакомство с MobX, Назим Гафаров


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


В этом воркшопе мы напишем приложение на React и TypeScript, используя MobX для управления состоянием. Начнем с фундаментальных понятий, таких как наблюдаемые объекты, действия и реакции. Вместе с основами глубже рассмотрим варианты решения проблемы окрашивания асинхронных функций. В конце рассмотрим полезные архитектурные паттерны вроде композиции классов и способов внедрения модели.



Производительность


Быстрый и красивый веб на дешевом девайсе с голосовым, пультовым и тач-управлением, Павел Ремизов


Вот реальная инженерная задача: есть SberBox за 3 000 рублей и SberPortal за 30 000. Производительность разная, но написанный на веб-технологиях интерфейс должен работать одинаково плавно. Павел Ремизов расскажет, как решали эту задачу, как ускоряли анимации и как использовали React, как кэшировали статику на девайсах и уменьшали размеры бандла, как отлаживали и измеряли производительность.


Чем хорош спикер: Имеет очень хорошую экспертизу в производительности и в реальных аспектах ее оценки


Чем хороша тема: Реальные устройства, юзкейсы, пользователи никаких коней в вакууме! Происходят реальные эксперименты на в боевых условиях, и их результаты не всегда совпадают с общепринятым мнением.




Оптимизация производительности высоконагруженного поиска на стороне фронтенда, Даниил Гоник, Ян Штефанец


Даниил Гоник и Ян Штефанец работают над редактором наподобие Google Docs.


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


Чем хороши спикеры: Докладчики глубоко и много работали с созданием редактора в вебе, поэтому экспертизы и знания нюансов много.


Кому будет полезно: Если вы реализовывали подсветку элементов на сайте по тексту, если вы работали когда-либо с WYSIWYG




Стабильность React Native-приложения с круглосуточным up time, Евгений Гейманен


Евгений Гейманен расскажет поучительную историю про проблемы производительности одного React Native-приложения. Из нее вы узнаете:


  • Как оптимизировать перерисовку виртуального React DOM;
  • Как правильно мутировать redux-store, коннектить к нему компоненты и описывать селекторы;
  • Как находить проблемные места в Android-версии React Native-приложения;
  • Какими инструментами стоит обзавестись для того, чтобы держать руку на пульсе приложения и начать думать как приложение.

Чем хорош спикер: Евгений Lead Developer в Bolt и ему есть что рассказать как с точки зрения технологий, так и с точки зрения истории


Чем хороша тема: Мониторинг это одна боль. Мониторинг приложений установленных на куче девайсов другая. Это реальный опыт решения некоторого количества проблем производительности на примере React Native приложения




Производительность в полевых условиях, Александр Шушунов


Вы уже прочитали все статьи Эдди Османи. Выучили, как расшифровываются аббревиатуры RAIL, PRPL, LCP и прочие. Свежий перформанс-гайд Фридмана второй месяц висит в открытой вкладке. Но как все это применить к текущему проекту? Как улучшить производительность здесь и сейчас? Александр не знает и не будет давать общие советы зато расскажет, что сделал он. И надеется, что его опыт борьбы за секунды и байты покажется вам интересным.


Чем хороша тема: Сугубо практический взгляд на производительность от человека, повидавшего виды. Будет отличным дополнением к докладу Виталия Фридмана. Причем зачастую хороших результатов можно достичь малой кровью


Кому будет полезно: Если вам интересна тема производительности клиентских приложений. Особенно, если вы никогда специально не работали в этом направлении, и хотите посмотреть на свой текущий проект под другим углом




Фреймворки


А нужен ли нам GraphQL?, Павел Черторогов


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


И в этом докладе речь пойдёт как раз о том, каких ситуациях GraphQL нужен (например, когда модели постоянно развиваются и количество полей растет), в каких не нужен (например, когда ведется работа с файлами или другими бинарными данными), и что делать, если ситуация спорная (попадаем в обе категории сразу). Будут разобраны сильные и слабые стороны и найдены разумные компромиссы.




How we built our custom React SSR Engine, Erzhan Torokulov


Эржан Торокулов расскажет, как несколько лет назад в Toptal сделали React Rendering Engine: он уже тогда умел делать ряд вещей, которые теперь знакомы по инструментам вроде Gatsby и Next.js. Компания по-прежнему использует этот инструмент для своего сайта и продолжает его развивать.


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


Чем хороша тема: Никого не удивить сайтом на React, Vue или даже Svelte, и доклады про React стали очередными. Индустрия перестала строить велосипеды и догадалась положить рельсы. Но иногда хочется большего и это такой случай.




Как в GitLab @vue/test-utils обновляли, Илья Климов


За последний год Илья дважды обновлял @vue/test-utils в GitLab и оба раза это заняло огромное количество времени. Для того, чтобы это сделать, ему пришлось отправить более десятка pull-request'ов во @vue/test-utils, обсудить в RFC внутри GitLab возможность создания собственного форка, глубоко разобраться в механизмах реактивности Vue и нещадно воевать за качество кода и тестов как в GitLab, так и в самом @vue/test-utils.


Теперь Илья со зрителями HolyJS будет разбираться в следующих вопросах:


  • где спрятана сложность в тестировании подобных систем;
  • какие ошибки были допущены разработчиками @vue/test-utils и можно ли было их избежать;
  • как магия реактивности усложняет построение надежной системы и как с этим бороться;
  • как писать надежные тесты для Vue.

Доклад будет интересен не только тем, кто использует Vue.js в повседневной разработке, но и всем, кто верит в unit-тестирование как ключ к управляемости любого проекта.





Языки


Свой язык с поддержкой sourcemaps за полчаса, Дмитрий Карловский


DSL (Предметно-ориентированный язык) техника для высокоуровневого описания чего-либо (конфигурации, UI, баз-данных). С помощью DSL можно решить множество проблем бизнеса (и множество создать). Дмитрий поговорит об этой теме, раскроет нюансы sourcemaps и расскажет, как в два счёта реализовать свой язык с их поддержкой. А также как легко и просто трансформировать AST, даже если вы никогда с ним не работали и ничего не знаете про sourcemaps.


Чем хорош спикер: Является автором собственного инструмента для работы с DSL.


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




Зачем OCaml на фронтенде, Дмитрий Коваленко


Возможно, вы слышали о таких технологиях, как ReasonML/Resсript и bucklescript. Всё это OCaml на фронтенде. Но как всё это работает? Как OCaml компилируется в JS? Почему это круто? Всё это вы узнаете в данном докладе.


Чем хорош спикер: Дмитрий один из немногих людей, использующих OCaml на практике.


Чем хороша тема: Про Ocaml много кто слышал, но мало кто представляет, что это такое и чем он замечателен. Если вы хотите разнообразить свои знания мира языков транспилируемых в JS и узнать (а возможно и подсесть на) что-то новое вам сюда.




Strict Types in JavaScript, Виктор Вершанский


Говорят, что JavaScript динамически типизированный. А если хочешь другой типизации, надо использовать что-то вроде TypeScript, который не заставляет писать без ошибок типов в runtime.


Но что, если это не совсем так? Что, если можно у чистого JS заставить его runtime работать с типами по всей строгости и получать связанные с этим ошибки? Если вы думаете, что это невозможно нам есть о чём поговорить.


Чем хороша тема: Такое решение еще никто не представлял, это самый эксклюзивный доклад из всех возможных.


Кому будет полезно: Всем, кто:


  • задумывался про проблему строгой типизации в рантайме
  • устал ловить TypeError в рантайме
  • хочет поломать мозг неординарными решениями



How to outsmart time: Building futuristic JavaScript applications using Temporal, Ujjwal Sharma


Почти 25 лет JS-разработчики страдали каждый раз, когда нужно было что-то сделать со временем и датой. Было сломано много костылей, сорвано много подорожников, и прожжено много стульев. И вот, наконец, будущее наступило: появилось долгожданное Temporal API, которое позволяет работать с временем и датой, как все давно мечтали. Уджвал Шарма покажет, как это всё работает.


Чем хорош спикер: Данный спикер помогает разрабатывать этот стандарт (ТС39 делегат) и внедряет его в браузеры.


Чем хороша тема: Поскольку текущий Date API не слишком хорош, разработчики подключают внешние библиотеки для работы с датами вроде date-fns, moment, luxon и тд. Temporal предложение для самого языка, это будущий стандарт работы с датами.





Микрофронтенды


Микросервисы, которые делаем мы, Олег Сметанин


Олег представит обзор способов проектирования микросервисов, API, использования шаблонов и паттернов взаимодействия; DevOps и тестирования на основе обобщения опыта проектирования и аудирования приложений с микросервисной архитектурой для разных индустрий: финансы, ритейл, ресурсы. Вы узнаете, как использовать TypeScript/NestJS для реализации микросервисов под NodeJS для cloud-native-приложений и как применять TypeScript/React для фронта.


Чем хорош спикер: У спикера большой опыт в построении больших систем с высокой нагрузкой.


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




Микрофронтенды на модулях и веб-компонентах, Юрий Караджов


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


Чем хорош спикер: У докладчика есть интересное мнение, отчасти затрагивающее вопрос, поднятый Павлом Чертороговым на прошлой HolyJS. Умеет и готов обсуждать архитектурный паттерн с разных неожиданных сторон.


Чем хороша тема: Доклад про лёгкий и прозрачный способ внедрения микрофронтендов. Анализ преимуществ и недостатков. Практическая применимость и подводные камни для популярных фреймворков.




One logic to rule them all, from clot to wire, Владимир Минкин


Презентация концепции Strings API с библиотекой Wire для создания веб и мобильных приложений, которые повторно используют бизнес-логику в языке Dart. Цель этого доклада: продвинуть идеи создания программных систем, состоящих из плагинов отдельных компонентов и блоков, которые можно тестировать индивидуально, визуально и в модульных тестах.


Разделение ответственности это не новая идея и того же можно добиться с помощью уже существующих инструментов. Разница с Wire заключается в том, что он предоставляет простой и семантически красивый API с четким разделением задач на уровне API-библиотеки: общение в одном месте, данные в другом. В докладе будет уделено внимание архитектуре систем визуального ПО и тому, что всё это MVC, а также архитектуре Flux.


Чем хорош спикер: Владимир обладает хорошим опытов разработки приложений в ООП стиле. Автор библиотек. Имеет опыт боевой опыт разработки на Dart и Haxe. Он может помочь зрителю посмотреть на разработку под другим углом.


Чем хороша тема: Эта тема напрямую касается практических аспектов разработки больших приложений. Это очень полезно нашим зрителям, так как выходит за рамки документации и учить принимать иненерсные решения. В докладе описан подход, который перекликается с первоначальным представлением об ООП





Основы


Напомни через минуту, или Как считать время в браузере, Никита Дубко


Пришла как-то к разработчику Мефодию задача: показывать через заданное пользователем время попап с напоминалкой, что время прошло. Написал Мефодий setTimeout, запушил, тикет закрыл. А пользователь так и не дождался своей напоминалки.


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


Чем хорош спикер: Обладает огромным опытом выступлений и предоставил очень интересную тему


Чем хороша тема: Не так много докладов есть вообще, которые бы покрывали тему устройства API для замера времени. Данная тема интересна как сама по себе, так и с точки зрения формирования практик измерений производительности приложения исходя из данных знаний.




Оптимизация синхронной асинхронности, Дмитрий Махнев


Дмитрий Махнёв хочет показать проблему обманчивости простоты async/await на реальном кейсе и приблизительные пути решения и профиты от этого.
Что вас ждет в докладе:
очевидное нахождение проблемы синхронной асинхронности в реальной задаче (индексе сайта);
удивительно неправильная попытка решения;
героическое ускорение примерно на порядок без переписывания на Rust;
неловкая ситуация с unhandledRejection, пролетающей сквозь try/catch;
пара полезных абстракций.


Чем хорош спикер: Дмитрий ранее состоял в программном комитете HolyJS, работает в JetBrains. Увлеченный инженер, выступающий за фундаментальный подход ко всему.


Чем хороша тема: async/await, которого боялись несколько лет назад, стал чем-то обыденным, повседневным, повсеместным. Раньше у нас был callback hell, then hell. Нет, await hell не выглядит чем-то страшным для читабельности кода. Он в определённых ситуациях критически влияет на производительность. Дима на собственном примере покажет, как правильно работать с async-await.




Machine Learning and JavaScript. Unleashing the power of sentiment analysis, Artur Nizamutdinov


Если вы работали с ML на Python и хотите перенести часть опыта в браузер (или не имели дела с МL, но очень хотите попробовать) приходите на доклад Артура Низамутдинова. Он покажет на живом примере, для каких задач можно использовать машинное обучение в браузере, и разберёт дообучение моделей.


Чем хорош спикер: У Артура сильная экспертиза. А еще он сильно переработал доклад конкретно для нашей аудитории. Так что тут будут не просто знания, а знания, поданные именно для слушателей конференции


Чем хороша тема: ML сейчас почти так же моден, как JavaScript. И этот доклад совмещает в себе эти два мира, показывая на практическом примере, для каких задач мы можем использовать ML в браузере.





Визуальное


Анализ больших географически распределенных данных через визуализацию на карте, Никита Швыряев


Доклад основан на опыте построения трёх систем для бизнес-анализа географически распределенных данных. В основном доклад будет построен вокруг использования библиотеки Mapbox GL JS. Рассмотрим востребованную функциональность, осветим проблемы и решения во многих задачах (от загрузки данных до кластеризации). Целевая аудитория JS-разработчики, которые работают или планируют работать с визуализацией на карте.


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


Чем хороша тема: В докладе рассматриваются практические примеры построения архитектуры для анализа больших географически распределённых данных через визуализации на карте. Рассматриваются паттерны, которые не стоит использовать при работе с картами, кратко вводится необходимая терминология.




Как отобразить 100500 метрик распределенной системы и не сойти с ума, Андрей Гончаров


Представьте распределённый кластер из 100 машин. Каждая собирает по 100 разных метрик. А теперь представьте график со всеми метриками сразу. Получилось? Вот и у Hazelcast не получилось. Поговорим, что делать, когда хочется показать много и сразу в условиях ограниченного экранного пространства и конечного числа глаз пользователей. Вспомним, что такое интеграл. Вместе посмотрим, чем он может быть лучше арифметического среднего и медианы.


Чем хорош спикер: Андрей работает в компании, которая оперирует огромным количеством машин и метрик на них. Задача правильного и, что самое главное, доверительного отображения метрик первостепенная.


Чем хороша тема: Любой бизнес любит метрики продуктовые, технические. Почти любой разработчик знает что такое медиана, 75, 90, 95 перцентиль. Правда, не всегда понятно пора бежать чинить, мы горим. Андрей расскажет, как можно обрабатывать метрики, чтобы они лучше отображали существующую картину.




Браузерный игровой движок как pet-проект, Михаил Реммеле


Михаил расскажет о своем опыте работы над pet-проектом. Игровой движок штука достаточно сложная, это огромный источник всевозможных задач и очень нетипичный опыт. Писать его можно бесконечно долго. Интересующие темы могут меняться, но в таком проекте всегда можно подобрать подходящую для исследований задачу. Технологии в данном случае: JavaScript, WebGL, Webpack.


Чем хорош спикер: Опыт работы в геймдеве, крайне редкий среди спикеров JS-конференций. Опыт разработки собственного игрового движка под браузер.


Чем хороша тема: Не распространённый, а возможно и редкий опыт в мире фронтенда. В докладе будет рассказано как автор с нуля написал свой игровой движок и с какими архитектурными сложностями столкнулся.





Заключение


Доклады это хорошо. Но напомним, что HolyJS не сводится только к тому, чтобы сидеть и слушать:


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


Напоминаем, HolyJS пройдет с 20 по 23 апреля в онлайне. Вся информация и билеты на сайте.

Подробнее..

Okdb библиотека для совместной работы

07.10.2020 12:08:05 | Автор: admin


Организация совместной работы в приложении всегда кажется большим геморроем и отметается на этапе планирования большинства приложений. По сути, сейчас принято пилить реалтайм-взаимодействие только там, где это часть обязательного функционала, в качестве дополнительной фишки его не встретить. Разработчики из Anwendo хотят исправить эту ситуацию и представляют okdb библиотеку и сервис, позволяющие снять с себя львиную долю проблем и сложности, связанных с организацией совместных рабочих сессий.


Реализация


Фронтенд подключается через Realtime API и позволяет синхронизировать курсоры, имена и фокус пользователей с бэкэндом на GraphQL.

Realtime API работает на вебсокетах, пересылая только изменения, внесённые пользователем. Синхронизацией всех поступающих изменений занимается бэкэнд-сервис. В доках можно почитать API reference.



GraphQL пока умеет работать только с PostgreSQL с полным покрытием таблиц, индексов и связей. Остальные базы на подходе.



Собираем пример


Нам потребуется node.js и, конечно, git.

git clone https://github.com/okdb-io/okdb-sample-todo.git cd okdb-sample-todo/backendnpm installnpm run server


Сервер запустится на 7899 порту. Кстати, исходник здесь: github.com/okdb-io/okdb-sample-todo/blob/master/backend/server.js

Собственно запуск сервиса происходит здесь:

const OkdbServer = require("okdb-server");// Create and start server on 7899 port by default.// CORS is disabled by default, let's enable it,// because demo frontend and backend run on different ports on localhostconst options = {    cors: {        enabled: true    }}const okdb = new OkdbServer(options);{...}// sample authentication, e.g. should validate your own auth tokenokdb.handlers().auth((token) => {    if(token === "12345") {        return { id: "1", name: "User"}    }        return null;});


Собираем фронт:

cd okdb-sample-todo/frontendnpm installnpm start


Код App.js здесь: github.com/okdb-io/okdb-sample-todo/blob/master/frontend/src/App.js

Теперь на 3000 можно открыть приложение в нескольких браузерах и посмотреть на магию.

Если лень билдить всё вручную, можно воспользоваться готовыми демками на codesandbox:



Бэк



Также доступны и более функциональные демки:

Таблица



Лагучий графический редактор



Текстовый редактор



Заключение


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



На правах рекламы


Серверы для размещения сайтов и любых других задач это про наши эпичные! Все серверы защищены от DDoS-атак, автоматическая установка удобной панели управления VestaCP. Лучше один раз попробовать ;)

Подробнее..

Наш опыт с графовой базой данных Dgraph в Kubernetes

28.10.2020 12:04:04 | Автор: admin


Недавно перед нами встала задача развернуть Dgraph в кластере Kubernetes. В этой статье я поделюсь полученным опытом: с чем мы столкнулись во время деплоя и последующего использования этого приложения в различных окружениях, от dev до production.

Что вообще такое Dgraph? Это горизонтально масштабируемая графовая (GraphQL) база данных с открытым кодом, созданная в стартапе Dgraph Labs. Краткое сравнение её основных возможностей с другими подобными решениями приведено здесь. В целом же не буду подробно останавливаться на описании Dgraph, т.к. на хабре уже была подробная статья о причинах появления проекта и особенностях реализации. Для желающих начать практическое знакомство с Dgraph также рекомендую официальную документацию.

В контексте статьи нам важно знать, что инсталляция Dgraph состоит из следующих компонентов:

  • Dgraph Zero, который контролирует кластер Dgraph, присваивает серверы группам и балансирует данные между группами серверов;
  • Dgraph Alpha, содержащий предикаты и индексы;
  • Ratel пользовательский интерфейс.

Деплой в Kubernetes


Для деплоя в Kubernetes мы использовали Helm-чарт, предлагаемый в официальной документации проекта. По умолчанию он разворачивает по 3 экземпляра Dgraph Zero и Dgraph Alpha (в StatefulSet'ах), а также один Deployment с Ratel.

Значительных модификаций чарта мы не производили, хотя изменили число реплик, добавили requests/limits, node affinity, tolerations и добавили дополнительный контейнер (nginx) в podы компонента Alpha (о его назначении см. ниже) и Ingress для отдачи дампа.

Особенности Ratel


После деплоя чарта кластер Dgraph успешно собрался: никаких дополнительных манипуляций для этого не потребовалось, все работает из коробки. Чтобы увидеть это собственными глазами и убедиться, что кластер собрался, достаточно в веб-браузере зайти в Ratel (например, по ClusterIP сервиса ratel-public), во вкладку Cluster. Вот как выглядит страница, когда у нас одна группа узлов Alpha:



А вот когда две:



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

Здесь же мы столкнулись и с первой особенностью полученной инсталляции: Ratel работает в режиме direct access mode, то есть мы обращаемся к серверу Dgraph напрямую из браузера.

Все заголовки, с которыми мы приходим в Ratel, пробрасываются в запрос к Dgraph Alpha это стоит учитывать, если вы хотите использовать базовую авторизацию или HTTPS при добавлении Ingress для Ratel и Dgraph Server (Alpha). Также стоит учитывать, что при работе с Dgraph через Ratel конечный пользователь должен иметь доступ как до Ratel, так и до Server: иначе подключение не произойдет, так как сам Ratel в коннекте к серверу не участвует и никакие запросы не проксирует.

Примечание по масштабированию и шардированию


Dgraph шардирует данные между группами узлов. По умолчанию мы имеем настройку по 3 узла на группу, а чартом в начале статьи выкатывается 3 реплики Alpha и 3 Zero. Соответственно, если будет добавлен 4-й экземпляр Alpha, Zero создаст еще одну группу узлов, на 7-м экземпляре 3-ю группу и т.д.

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

Теперь подробнее об операциях экспорта/импорта в контексте реальных задач.

Возможности экспорта и импорта


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

Dgraph имеет два инструмента для загрузки файлов экспорта:

  1. Bulk Loader используется для инициализации нового кластера Dgraph с существующими данными;
  2. Live Loader для импорта данных в уже работающий кластер.

Поскольку мы делали импорт данных с production-окружения в dev, необходимости инициализировать кластер при каждом импорте не было. Для такой задачи подходит второй инструмент Live Loader.

В pod с Alpha мы добавили контейнер с nginx для раздачи файлов экспорта, а в pod'ы Dgraph скрипты для экспорта и импорта. Получившаяся конфигурация скриптов импорта и экспорта выглядела следующим образом:

apiVersion: v1kind: ConfigMapmetadata:  name: dump-scriptsdata:  dump.sh: |    #!/bin/bash    mkdir -p /dgraph/export    rm -rf /dgraph/export/*    curl localhost:8080/admin/export    LAST_DIR=$(ls -ltr /dgraph/export/ | grep '^d' | tail -1| awk '{print $NF}')    LAST_RDF=$(ls -t /dgraph/export/"$LAST_DIR" | grep rdf | head -n1)    LAST_SCHEMA=$(ls -t /dgraph/export/"$LAST_DIR" | grep 'schema' | head -n1)        cp /dgraph/export/"$LAST_DIR"/"$LAST_RDF" /dgraph/export/rdf.gz    cp /dgraph/export/"$LAST_DIR"/"$LAST_SCHEMA" /dgraph/export/schema.gz    chmod -R 755 /dgraph/export  restore.sh: |    #!/bin/bash    mkdir -p /dgraph/restored    rm -rf /dgraph/restored/*    curl -o /dgraph/restored/rdf.gz https://someurl/export/rdf.gz    curl -o /dgraph/restored/schema.gz https://someurl/export/schema.gz    dgraph live -f /dgraph/restored/rdf.gz --format=rdf -s /dgraph/restored/schema.gz -z dgraph-zero-0.dgraph-zero.${POD_NAMESPACE}.svc.cluster.local:5080

Далее остается лишь запускать dump.sh в pod'е dgraph-alpha в окружении-источнике и restore.sh в окружении, где требуется получить актуальную БД.

Описанная схема будет актуальна и для обновления между разными версиями Dgraph. Переключение между минорными версиями мы пока не пробовали, но просто изменить тег образа, скажем, с 1.2.6 на 20.03.3 и запустить Dgraph с теми же данными, ожидаемо, не выйдет. Зато успешно проходит обновление через инициализацию нового кластера версии 20.03.3 и последующую загрузку в него файла экспорта из 1.2.6.

Заключение


По имеющемуся у нас сейчас опыту, Dgraph работает стабильно: при запуске этой БД в эксплуатацию с Kubernetes в production-среде проблем не возникало. В документации проекта есть отдельный раздел, посвящённый разворачиванию БД в K8s. Кстати, ее авторы сообщают, что проверяли запуск в Kubernetes 1.14 и 1.15 для GKE и EKS, а в нашем случае инсталляция работает в K8s 1.16 на bare metal.

На что стоит обратить внимание?

  • Когда групп экземпляров Alpha становится несколько (просто >1), следите за балансировкой шардов, чтобы избежать неравномерной нагрузки на экземпляры групп.
  • Как написано выше, есть особенности в работе с графическим интерфейсом (Ratel). Хотя лично нам ограничения, связанные с direct access mode, не показались критичными.

P.S.


Читайте также в нашем блоге:

Подробнее..

Перевод Примеры GraphQL на Java для начинающих со Spring Boot

02.08.2020 22:16:32 | Автор: admin

В этой статье мы рассмотрим пример GraphQL на Java и создадим простой сервер GraphQL со Spring Boot.



Таким цыпочкам тоже нравятся примеры GraphQL на Java со Spring Boot!


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


В этой статье мы рассмотрим пример GraphQL на Java и создадим простой сервер GraphQL со Spring Boot.


Добавление зависимостей Maven


Создайте пример Spring Boot приложения и добавьте следующие зависимости.


  1. graphql-spring-boot-starter используется для включения сервлета GraphQL и будет доступен по пути /graphql. Он инициализирует GraphQLSchema бин.
  2. graphql-java позволяет нам писать схемы на языке схем GraphQL, который прост для понимания.
  3. graphiql-spring-boot-starter предоставляет пользовательский интерфейс, с помощью которого мы сможем тестировать наши запросы на GraphQL и просматривать определения запросов.
        <dependency>        <groupId>com.graphql-java</groupId>        <artifactId>graphql-spring-boot-starter</artifactId>        <version>5.0.2</version>    </dependency>    <dependency>        <groupId>com.graphql-java</groupId>        <artifactId>graphql-java-tools</artifactId>        <version>5.2.4</version>    </dependency>    <dependency>        <groupId>com.graphql-java</groupId>        <artifactId>graphiql-spring-boot-starter</artifactId>        <version>5.0.2</version>    </dependency>
    

    Вот полное содержимое файла POM.

    <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://personeltest.ru/away/maven.apache.org/POM/4.0.0"     xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance"     xsi:schemaLocation="http://personeltest.ru/away/maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.techshard.graphql</groupId><artifactId>springboot-graphql</artifactId><version>1.0-SNAPSHOT</version><parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.1.6.RELEASE</version>    <relativePath /></parent><properties>    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding></properties><dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-data-jpa</artifactId>    </dependency>    <dependency>        <groupId>com.graphql-java</groupId>        <artifactId>graphql-spring-boot-starter</artifactId>        <version>5.0.2</version>    </dependency>    <dependency>        <groupId>com.graphql-java</groupId>        <artifactId>graphql-java-tools</artifactId>        <version>5.2.4</version>    </dependency>    <dependency>        <groupId>com.graphql-java</groupId>        <artifactId>graphiql-spring-boot-starter</artifactId>        <version>5.0.2</version>    </dependency>    <dependency>        <groupId>com.h2database</groupId>        <artifactId>h2</artifactId>        <scope>runtime</scope>    </dependency>    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <version>1.18.8</version>        <optional>true</optional>    </dependency></dependencies><build>    <plugins>        <plugin>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-maven-plugin</artifactId>        </plugin>    </plugins></build></project>
    

    Создание сущности и репозитория JPA


    Давайте создадим простую сущность с именем Vehicle и соответствующий JPA репозиторий. Мы будем использовать Lombok, чтобы избежать написания шаблоного кода, такого как геттеры, сеттеры и так далее.



    package com.techshard.graphql.dao.entity;import lombok.Data;import lombok.EqualsAndHashCode;import javax.persistence.*;import java.io.Serializable;import java.time.LocalDate;@Data@EqualsAndHashCode@Entitypublic class Vehicle implements Serializable {    private static final long serialVersionUID = 1L;    @Id    @Column(name = "ID", nullable = false)    @GeneratedValue(strategy = GenerationType.AUTO)    private int id;    @Column(name = "type", nullable = false)    private String type;    @Column(name = "model_code", nullable = false)    private String modelCode;    @Column(name = "brand_name")    private String brandName;    @Column(name = "launch_date")    private LocalDate launchDate;    private transient  String formattedDate;    // Getter and setter    public String getFormattedDate() {        return getLaunchDate().toString();    }}
    

    Вот соответствующий репозиторий JPA.


    package com.techshard.graphql.dao.repository;import com.techshard.graphql.dao.entity.Vehicle;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repositorypublic interface VehicleRepository extends JpaRepository<Vehicle, Integer> {}
    

    Схема GraphQL


    GraphQL поставляется с собственным языком для написания схем GraphQL, который называется Schema Definition Language (SDL язык определения схемы). Определение схемы состоит из всех функций API, доступных в конечной точке.


    Типичный пример схемы GraphQL будет выглядеть так:


    type Vehicle {id: ID!,type: String,modelCode: String,brandName: String,launchDate: String}type Query {vehicles(count: Int):[Vehicle]vehicle(id: ID):Vehicle}type Mutation {createVehicle(type: String!, modelCode: String!, brandName: String, launchDate: String):Vehicle}
    

    Создайте папку graphql в папке src/main/resources и в ней создайте файл vehicleql.graphqls. Скопируйте вышеуказанное содержимое и вставьте его в файл vehicleql.graphqls. Обратите внимание, что именем файла может быть любое имя по вашему выбору. Просто убедитесь, что у имени файла есть расширение .graphqls.


    В приведенной выше схеме каждый объект имеет определенный тип. Система типов в GraphQL является базовым компонентом и она представляет тип объекта, который можно получить от сервиса и полей, которые содержит объект.


    В нашей схеме есть объект с именем Vehicle, который является нашим объектом домена. Тип Query представляет запрос, который можно сделать на сервер GraphQL для извлечения данных. Этот запрос является интерактивным, данные можно изменить, и новые результаты можно увидеть. Структура запроса и результат одинаковы. Это важно в мире GraphQL, потому что мы всегда получаем ожидаемый результат.


    Ниже в этой статье мы увидим рабочий пример.


    Тип Mutation представляет запросы, которые используются для выполнения операций записи данных.


    Root Query


    Объекты Query или Mutation являются основными объектами GraphQL. У них нет связанных классов данных. В таких случаях классы распознавателя (resolver) будут реализовывать GraphQLQueryResolver или GraphQLMutationResolver. Эти распознаватели будут искать методы, которые соответствуют полями в соответствующих основных типах.


    Давайте определим основные распознаватели для Vehicle.


    package com.techshard.graphql.query;import com.coxautodev.graphql.tools.GraphQLQueryResolver;import com.techshard.graphql.dao.entity.Vehicle;import com.techshard.graphql.service.VehicleService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.util.List;import java.util.Optional;@Componentpublic class VehicleQuery implements GraphQLQueryResolver {    @Autowired    private VehicleService vehicleService;    public List<Vehicle> getVehicles(final int count) {        return this.vehicleService.getAllVehicles(count);    }    public Optional<Vehicle> getVehicle(final int id) {        return this.vehicleService.getVehicle(id);    }}
    

    В этом классе реализованы методы для получения одного объекта Vehicle и списка объектов Vehicle. Обратите внимание, что мы определили эти методы в нашей схеме выше.


    Теперь давайте определим распознаватель мутаций.


    package com.techshard.graphql.mutation;import com.coxautodev.graphql.tools.GraphQLMutationResolver;import com.techshard.graphql.dao.entity.Vehicle;import com.techshard.graphql.service.VehicleService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.time.LocalDate;@Componentpublic class VehicleMutation implements GraphQLMutationResolver {    @Autowired    private VehicleService vehicleService;    public Vehicle createVehicle(final String type, final String modelCode, final String brandName, final String launchDate) {        return this.vehicleService.createVehicle(type, modelCode, brandName, launchDate);    }}
    

    В этом классе у нас есть только один метод для создания объекта Vehicle, и это соответствует типу Mutation в нашем определении схемы.


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


    package com.techshard.graphql.service;import com.techshard.graphql.dao.entity.Vehicle;import com.techshard.graphql.dao.repository.VehicleRepository;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.time.LocalDate;import java.util.List;import java.util.Optional;import java.util.stream.Collectors;@Servicepublic class VehicleService {    private final VehicleRepository vehicleRepository ;    public VehicleService(final VehicleRepository vehicleRepository) {        this.vehicleRepository = vehicleRepository ;    }    @Transactional    public Vehicle createVehicle(final String type,final String modelCode, final String brandName, final String launchDate) {        final Vehicle vehicle = new Vehicle();        vehicle.setType(type);        vehicle.setModelCode(modelCode);        vehicle.setBrandName(brandName);        vehicle.setLaunchDate(LocalDate.parse(launchDate));        return this.vehicleRepository.save(vehicle);    }    @Transactional(readOnly = true)    public List<Vehicle> getAllVehicles(final int count) {        return this.vehicleRepository.findAll().stream().limit(count).collect(Collectors.toList());    }    @Transactional(readOnly = true)    public Optional<Vehicle> getVehicle(final int id) {        return this.vehicleRepository.findById(id);    }}
    

    Тестирование приложения


    Приложение теперь готово к тестированию. Запустите приложение Spring Boot и откройте в браузере эту ссылку: http://localhost:8080/graphiql. Мы увидим хороший пользовательский интерфейс, как показано ниже.



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



    Теперь запустите следующий запрос.


    mutation {  createVehicle(type: "car", modelCode: "XYZ0192", brandName: "XYZ", launchDate: "2016-08-16")   {    id  }}
    

    Это создаст строку в таблице Vehicle. Результат должен быть:


    {  "data": {    "createVehicle": {      "id": "1"    }  }}
    

    Давайте теперь запустим запрос, чтобы получить данные.


    query {  vehicles(count: 1)   {    id,     type,     modelCode}}
    

    Вывод будет выглядеть так:


    {  "data": {    "vehicles": [      {        "id": "1",        "type": "bus",        "modelCode": "XYZ123"      }    ]  }}
    

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


    Вывод


    В этой статье мы рассмотрели базовый пример GraphQL на Java со Spring Boot. Ознакомьтесь с подробной документацией здесь.


    Полный исходный код этого руководства можно найти на GitHub.


    Дальнейшее чтение


    Introduction to GraphQL


    GraphQL: Core Features, Architecture, Pros, and Cons


    Если вам понравилась эта статья и вы хотите больше узнать о GraphQL, ознакомьтесь с этой коллекцией учебников и статей по всем вопросам, связанным с GraphQL.


    Прим. Переводчика.
    На русском языке также есть Руководство по GraphQL для начинающих
Подробнее..
Категории: Java , Graphql , Spring-boot

До 40 релизов в день в Enterprise наша сool story

17.06.2021 12:20:28 | Автор: admin

Пару слов о нас: мы команда банка Открытие, которая отвечает за разработку всех розничных фронтов от рабочего места сотрудника в отделении до мобильных приложений физических лиц. В последние пару лет мы переживаем взрывной рост в несколько раз у нас более 400 сотрудников ИТ и мы продолжаем расти и расти. Как оказалось, многие решения, которые были приняты на старте нашей работы, оказались верными. И о некоторых из них мы вам расскажем. Готовы? Поехали!

Один из вопросов на старте цифровой трансформации банка был таким: как нам хранить исходный код используя монорепозиторий для хранения всех миросервисов или много репозиториев? Мы выбрали такой подход: для каждого приложения, библиотеки или микросервиса создаем отдельный репозиторий.

В рамках дискуссий внутри команды для нас было очевидно, что:

  • Монорепоэто решение, которое невзлетает из коробки, так как у нас банально много кода (на сегодня его около 100 GB), который просто будет долго выкачиваться к себе на локальную машину изрепозитория;

  • Когда мы попытаемся открыть такой огромный проект вIDE, она скорее всего приляжет;

  • При одновременной работе сотни разработчиков над одной кодовой базой потенциально ненулевые риски получитьpullrequest-ы на десятки тысяч строк кода, которые очень долго нужно будет вычитывать, чтобы понять,что вообще меняется.Которые имеют непредсказуемые срокиревью, тестирования и сложный вывод впрод;

  • Оставался открытым вопрос разграничения доступа внутрирепозитория(большие компании, которыепиарилиподход смонорепонесколько лет назад,решали этокастомнымсамописом, который разграничивал доступ, например, на уровне папок);

  • Очевидным плюсом было удобстворазруливаниязависимостей (в одномрепозиторииэто можноконтролировать на уровне кода). Так чтоесли отказываться отмонорепозитория, то надо было придумать как решить эту проблему.

Кирпичики, из которых складывается наше решение

Учитывая номер версии МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ, следует увеличивать:

  1. МАЖОРНУЮ версию, когда сделаны обратно несовместимые изменения в работе сервиса;

  2. МИНОРНУЮ версию, когда вы добавляете новую функциональность, ненарушая обратной совместимости;

  3. ПАТЧ-версию, когда вы делаете какие-то внутренние исправления в работе сервиса, которые никак не меняют внешние контракты.

Дополнительные обозначения дляпредрелизныхибилд-метаданных возможны как дополнения к МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ формату.

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

  • Публикация контрактов

В конвейер сборки сервисов внедрен процесс генерации и публикации контрактов, в которых однозначно описан интерфейс взаимодействия с ними. Доменные сервисы общаются с внешними потребителями наружу черезGraphQL(на самом деле у нас500+ сервисов, которые поделены на доменные группы, наподобие модной нынче и активно продвигаемойdomainorientedmicroservicearchitecture(DOMA). У каждой доменной группы есть только один внешнийGraphQLфасад, а внутри сервисы общаются поRabbitMQ, но для простоты понимания рассмотрим только пример с доменными сервисами). Сам контракт представляет из себя SDL схему, котораягенеритсяпри помощи стандартного функционала из коробкибиблиотекиqraphql-dotnetпри помощи утилитыgraphql-sdl-exporter. На самом деле утилиту мы написали сами, но решили ей поделиться и выложили еев паблик.

Что происходит после коммита:

Весь процесс контроля автоматизирован и встроен в конвейер сборки.

Шаги:

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

  2. После из этого контракта (SDL схемы) при помощи утилит специфичных для каждого языка генерируются клиенты для различных потребителей на их языках, которые представляют собой пакеты, хранящиеся в репозитории пакетного менеджера. В нашем случае это NuGet, NPM, Graddle для .Net, JS и Java соответственно. В качестве инструмента для хранения и управления пакетов используется Arfifactory. Версия контракта (сервиса-поставщика) соответствует версии пакета. Например, сервис Payments версии 2.1.3 имеет контракт версии 2.1.0 (не 2.1.3, т.к. перегенерация для версий от 2.1.1 и выше не требуется, а потребуется только для 2.2.0).

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

    Очень важный момент: Для feature веток и ветки master существуют различные хранилища пакетов: Release для master, Snapshot - для feature-веток. Причем из ветки master нет доступа к пакетам из Snapshot.

Рассмотрим пару примеров, которые продемонстрируют работу такого подхода.

Пусть сервис Accounts версии 4.7.2 потребляет сервис Payments версии 2.1.3 (обращается к нему, используя контракт в виде nuget пакета версии 2.1.0). Появилась необходимость добавить новую фичу в сервис Payments.

Вариант 1:

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

Когда разработчиксервиса Payments Вася сделает коммит в свою feature ветку, увеличив версию сервиса, в хранилище Snapshot появятся новые клиенты для сервиса Payments. Разработчик сервиса Accounts Петя сможет обновить свой пакет для работы с сервисом Payments до версии 2.2.0 с новой функциональностью, сможет ей воспользоваться и реализовать свою логику работы с Payments в своей feature ветке своего репозитория.

Пока Вася доводит свой функционал до идеала, Петя допиливает свою функциональность, которая теперь зависит от версии Payments 2.2.0 и подливает свои изменения в ветку master, что в нашем случае приводит к инициации процесса релиза сервиса Accounts на прод. В процессе сборки сервис Accounts пытается разрешить свои зависимости и, в частности, получить новую версию пакета для работы с Payments 2.2.0 из хранилища Release (напомню, что master смотрит только туда), где его нет. Сборка падает с ошибкой. Автоматический контроль сработал. Петя идет к Васе, просит его побыстрее раскатать на прод новую версию сервиса Payments, после чего катит свою новую версию Accounts.

Вариант 2:

В сервисе Payments необходимо сделать обратно несовместимые изменения. Вообще мы такое категорически не приветствуем, но иногда такая необходимость возникает. Вася знает о своих потребителях (откуда скажем позже), он пишет письма ответственным и предупреждает всех о том, что собирается ломать обратную совместимость и увеличить версию своего сервиса до 3.0.0. В день Икс Вася опубликует версию Payments на тестовый контур и ждет результатов интеграционных тестов. У тех, кто не готов к обновлению сервиса Payments, попадают Beta тесты и тестовый контур станет неработоспособным в той части, которая потребляет сервис Payments. Новые контракты Payments при этом будут лежать в Snapshot. Вася не обновляет master-ветку, пока все потребители Payments не обновят свои версии пакетов для работы с Payments и не будут готовы синхроннос ним (сразу после него) раскататься на бой.

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

Кстати, информация о том, кто использует клиента к сервису позволяет построить карту зависимостей системы и визуализировать ее в виде графа.

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru