...

суббота, 25 мая 2019 г.

[Перевод] Настоящее реактивное программирование в Svelte 3.0

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

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

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

Хорошо, теперь давайте углубимся в тему!


Но сначала, React

Прежде чем рассказывать, почему я считаю, что Svelte всех порвёт, давайте взглянем на этот недавний твит от человека по имени Dan, и попытаемся понять, что он имел ввиду:

Хм, а почему он тогда называется React?

Ещё одна оговорка: эта статья никоим образом не предназначена для критики React. Я решил использовать его в качестве примера, потому что большинство людей, которые читают эту статью, имели дело с React в тот или иной момент своей жизни. Просто сейчас это лучший пример для противопоставления Svelte.

Что же имел в виду Dan, и как это повлияло на то, как мы сейчас пишем код? Чтобы ответить на эти вопросы, позвольте мне упрощённо рассказать о том, как React работает под капотом.

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

Затем, при изменении данных (например, после вызова this.setState или useState), React проделывает небольшую работу, чтобы определить, какие части приложения нужно перерисовать.

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

Всё происходит очень быстро, потому что обновлять виртуальной DOM намного дешевле, чем реальный, и React обновляет только необходимые кусочки реального DOM. Эта статья намного лучше объясняет этот процесс.

Вероятно, вы заметили одну особенность. Если вы не сообщите React, что данные изменились (вызвав this.setState или эквивалентный хук), виртуальный DOM не изменится, и от React не последует никакой реакции (та-дам! ).

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


Хорошо, теперь про Svelte

Svelte — это совершенно новый способ создавать веб-приложения невероятно быстрым, эффективным и по-настоящему реактивным способом. И всё это без использования виртуального DOM и с меньшим количеством строк кода, чем понадобилось бы в другом фреймворке или библиотеке.

Это всё, конечно, звучит прекрасно, но чем он отличается от множества других JavaScript библиотек и фреймворков? — спросите вы. Я расскажу.


1. Настоящая реактивность

Svelte — это не библиотека. Svelte — это не фреймворк. Скорее, Svelte — это компилятор, который получает ваш код и выдает нативный JavaScript, который напрямую взаимодействует с DOM без необходимости в каком-либо посреднике.

Погоди-погоди, что? Компилятор? Да  — компилятор. И это чертовски хорошая идея, я не могу понять, почему она не была столь очевидной до сих пор. Дальше расскажу, почему я считаю её очень крутой.

Вот цитата из доклада Rich Harris на конференции YGLF 2019:


Svelte 3.0 выносит реактивность из API компонента в сам язык.

Что это означает? Что ж, мы уже знаем, что React (и большинство других фреймворков) требует использовать API, чтобы сообщить ему, об изменении данных (вызов this.setState или useState) и записать их виртуальный DOM.

Необходимость вызова this.setState в React (и иных UI фреймворках и библиотеках) означает, что реактивность вашего приложения теперь привязана к определенному API, без которого оно вообще ничего не будет знать об изменениях в данных.

Svelte использует другой подход.

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

1. (() => {
2.   let square => number => number * number;
3.
4.   let secondNumber = square(firstNumber);
5.   let firstNumber = 42;
6.
7.   console.log(secondNumber);
8. })();

При исполнении этого кода сверху вниз, будет показана ошибка в строке №4, потому что для secondNumber используется значение firstNumber, которое на этот момент ещё не было инициализировано.

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

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

1. Зависит ли новая переменная 'square' от любой другой переменной?
     - Нет, инициализирую её

2. Зависит ли новая переменная 'secondNumber' от любой другой переменной?
     - Она зависит от 'square' и 'firstNumber'. Я уже инициализировал 'square', но ещё не инициализировал 'firstNumber', что я сейчас и сделаю.

3. Прекрасно, я инициализировал 'firstNumber'. Теперь я могу инициализировать 'secondNumber' используя 'square' и 'firstNumber'
     - Есть ли у меня все переменные, требуемые для запуска выражения 'console.log'?
     - Да, запускаю его.

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

Когда компилятор доходит до строки №4, он обнаруживает, что у него нет firstNumber, поэтому он приостанавливает дальнейшее выполнение и просматривает весь код, в поисках инициализации этой переменной. Что ж, именно это происходит в строке №5, поэтому сначала исполняется строка №5, а затем исполнение кода вернётся к строке №4 и пойдёт далее.


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

Итак, как это соотносится с тем, как Svelte реализует свою настоящую реактивность? Вы можете обозначить любое выражение JavaScript специальной меткой. Это выглядит следующим образом: $: foo = bar. То есть, всё, что нужно сделать, это добавить метку с именем $ перед выражением foo = barstrict mode этого сделать не получится, если foo не была определена ранее).

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

Это и есть реактивность! Прямо сейчас мы использовали часть стандартного API самого JavaScript для достижения настоящей реактивности без необходимости возиться со сторонними API типа this.setState.

Вот как это выглядит на практике:

1. // ванильный js
2. let foo = 10;
3. let bar = foo + 10; // bar теперь равен 20
4. foo = bar // bar всё ещё равен 20 (нет реактивности)
5. bar = foo + 10 // теперь bar равен 30

6. // svelte js
7. let foo = 10;
8. $: bar = foo + 10; // bar равен 20
9. foo = 15 // тут bar станет равным 25, потому что зависит от значения foo

Обратите внимание, что в этом примере нам не нужно было заново пересчитывать bar с новым значением foo, ни напрямую, ни повторным исполнением bar = foo + 10, ни путём вызова метода API, вроде this.setState ({bar = foo + 10}). Это делается автоматически.

То есть, когда вы изменяете foo на 15, bar автоматически обновится на 25, и вам не нужно вызывать никакое API, чтобы сделать это. Svelte уже обо всём знает.

Часть скомпилированного Javascript кода, приведенного выше примера, выглядит примерно так:

1. //... 
2. function instance($$self, $$props, $$invalidate) {
3.   let foo = 10; // bar равен 20

4.   $$invalidate('foo', foo = 15) // тут bar станет равным 25, потому что зависит от значения foo

5.   let bar;

6.   $$self.$$.update = ($$dirty = { foo: 1 }) => {
7.     if ($$dirty.foo) { $$invalidate('bar', bar = foo + 10); }
8.   };

9.   return { bar };
10. }
11. //...

Не торопитесь читать дальше, изучите этот фрагмент кода. Не спеша.

Заметили, что обновление значения foo происходит до того, как bar будет объявлен? Это потому, что компилятор анализирует Svelte-код в топологическом порядке, а не построчно сверху вниз.

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

Примечание: В строке №4, значение bar не будет обновлено, пока следующая итерация цикла EventLoop не подчистит все хвосты.

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


2. Краткость

Помните, ранее я писал, что Svelte позволяет делать больше, написав при этом меньше строк кода? Я покажу простой компонент в React и его эквивалент в Svelte, и вы сами убедитесь:

17 строк кода против 29

Эти два приложения полностью идентичны по функциональности, но вы можете видеть, сколько кода нам потребовалось написать в React.js —  и даже не просите меня делать это ещё и в Angular .

Я старейший из разработчиков

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

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

Хотя это может показаться очевидным, но быстро становится понятно, насколько полезнее писать меньше строк кода, когда вы начинаете создавать большие и более сложные приложения. Я лично потратил часы, пытаясь понять, как работает большой React-компонент, который написал мой коллега.

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


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

Хорошо, мы увидели, что Svelte по-настоящему реактивный и позволяет делать больше с меньшими усилиями. Как насчет производительности? И насколько удобны приложения, написанные полностью в Svelte?

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

Однако, недостатком этого подхода является то, что в случае изменения данных компонента React будет повторно перерисовывать не только сам компонент, но и все его дочерние элементы независимо от того, менялись они или нет. Вот почему в React существуют такие методы API, как shouldComponentUpdate, useMemo, React.PureComponent и т.д.

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

Svelte не использует виртуальный DOM, но как же тогда он решает проблему перерисовки DOM согласно новым данным состояния? Что ж, позвольте мне снова привести цитату Rich Harris из его замечательного выступления на YGLF:


Фреймворки — это не инструменты организации вашего кода. Это инструменты для организации вашего разума.

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

Эта простая идея, также объясняет почему Svelte очень быстрый. Svelte компилирует ваш код в эффективный низкоуровневый Javascript, который напрямую взаимодействует с DOM. Это всё прекрасно, но как Svelte решает проблему перерендеринга всего DOM целиком при изменении данных?

Разница заключается в том, каким образом традиционные фреймворки(например, React) и Svelte узнают, что в состоянии что-то изменилось. Мы уже ранее обсудили, что в React необходимо вызвать метод API, чтобы сообщить ему, когда данные изменяются. В случае Svelte достаточно просто использовать оператор присваивания =.

Если значение переменой состояния  — скажем, foo  — обновляется при помощи оператора =, Svelte, как мы уже знаем, обновит и все другие переменные, которые зависят от foo. Это позволяет Svelte перерисовывать только те части DOM, которые так или иначе получают своё значение из foo.

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


В заключение

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

Также он позволяет приложениям быть более производительными, более легковесными и при этом получается код, который легче читать. Так сможет ли Svelte заменить в ближайшее время React, Angular или любой другой традиционный UI фреймворк?

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

Подобно тому, как React своим появлением изменил разработку веб-приложений, у Svelte тоже есть потенциал изменить наше представление о фреймворках и возможно, весь процесс мышления при создании приложений.

Счастливого кодинга!

Let's block ads! (Why?)

[Из песочницы] Как устроена локализация в Netflix — перевод

Привет, Хабр! Представляю вашему вниманию перевод материала «Localization Technologies at Netflix», написанного командой Netflix про внутренние процессы локализации и программы, разработанные специально для этого.

image

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

Мы не боимся экспериментировать и пробовать новые процессы и инструменты, выступать против общепринятых в локализации норм — благодаря этому мы продвинулись так далеко! Работать в Netflix — значит быть первопроходцем.

В этой статье мы рассказываем о двух технологиях, которые приведут нас к МИРОВОМУ господству… Подробнее под катом.

Netflix Global String Repository


Netflix достиг успеха не потому, что мы делаем качественный контент, а потому, как мы подаем этот контент. Большая часть успеха — это интуитивно понятный, простой в использовании и локализованный пользовательский интерфейс (UI). Netflix доступен на разных платформах: веб-версия, Apple iOS, Google Android, Sony PlayStation, Microsoft Xbox, в телевизорах Sony, Panasonic и так далее. У каждой из этих платформ свои требования к интернализации, что представляет собой серьезный вызов для нашей команды.

Вот примеры, когда требуется локализация UI:

  • добавление нового языка
  • добавление новых функций
  • изменения в уже существующих текстах и данных

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

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

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

Для решения этих проблем мы разработали глобальное хранилище строк UI, которое называется Global String Repository; здесь хранятся локализованные строки, которые подставляются в среду для выполнения кода. Мы интегрировали Global String Repository в техпроцесс локализации, таким образом, они взаимно дополняют друг друга.

Global String Repository разделяет пакеты локализации и пространство имен (заполнители). В пакете локализации хранятся все данные по строкам на всех языках. Заполнители — это плейс-холдеры для пакетов, над которыми работает команда. Во время разработки используются стандартные заполнители. Рабочий процесс выглядит так:

  1. Разработчик вносит изменения в английскую версию строки в пакете (в пространстве имен–заполнителей)
  2. Автоматически запускается процесс перевода
  3. Лингвисты завершают перевод
  4. Переводчики делают комплекты в заполнителях доступными

Когда происходит интеграция с Global String Repository, есть два типа поведения приложения:
  • Во время исполнения: позволяет быстро вносить изменения в UI
  • Во время сборки: использование Global String Repository отдельно для локализации, а пакеты с данными — со сборкой (билдом)

Global String Repository делает возможной интеграцию на этапе сборки предоставляя доступ к локализованным данным через REST API.

Мы открываем Global String Repository через API Netflix, таким образом к нему применяются те же самые масштабирование и требования, что и к метаданным других API. Для приложений, которые интегрируются во время исполнения, это критически важная часть. У нас 60 миллионов пользователей, которые запускают Netflix на разных устройствах, поэтому Global String Repository является приоритетной задачей.

Как и у Netflix, у Global String Repository микросервисная архитектура. Микросервис — это веб-приложение на Java (выполненное в Apache Cassandra и ElasticSearch), которое размещено в трех регионах AWS. Мы собираем статистические данные для каждого запроса к API.

Интерфейс Global String Repository разработан на Node.js, Bootstrap и Backbone и размещен в AWS.

На стороне пользователя Global String Repository использует REST API для получения данных и предлагает клиент Java со встроенным кэшированием.

Несмотря на то, что мы проделали большой путь и активно развиваем Global String Repository, нам есть к чему стремиться. Вот над чем мы работаем сейчас:

  • Разрабатываем поддержку строк с числовыми переменными и строки с идентификаторами гендеров
  • Развиваем устойчивость наших технических решений к сбоям
  • Улучшаем процессы масштабирования
  • Поддерживаем экспорт в разные форматы (Android XML, Microsoft .Resx и т.д.)

Global String Repository не имеет привязки к бизнес-домену Netflix, поэтому мы планируем выпустить его как программное обеспечение с открытым исходным кодом.

Hydra


Netflix — глобальный сервис, который поддерживает множество локалей в мириадах различных комбинаций на разных устройствах/UI; ручное тестирование в таком случае не подходит. Раньше команда локализаторов и разработчиков UI тестировала все вручную на разных устройствах — от консолей до iOS и Android; так мы проверяли все строки на соответствие контексту и UI (например, нет ли «обрезания» текста).

Но философия Netflix такова — мы стремимся к совершенству. Такой подход позволяет нам переосмысливать то, что мы делаем. Так родилась Hydra.

Задача Hydra — создание каталога всех возможных вариантов уникального экрана, который будет показывать именно тот экран, который требуется (поиск осуществляется по фильтрам, например, можно выбрать устройство и локали). Например, будучи специалистом по немецкой локализации, вы можете настроить фильтрацию таким образом, что увидите весь путь, который проходят незарегистрированные пользователи на PS3, веб-сайте и Android. Эти же экраны можно посмотреть в том темпе, в котором пользователь будет открывать их на своем устройстве.

Работа с экранами в Hydra


Инструмент Hydra не работает с экранами напрямую; он служит для их каталогизации и отображения. Чтобы взять отображение экрана из каталога Hydra, мы используем нашу модель автоматизации UI. С помощью Jenkins CI тесты, управляемые данными, работают параллельно во всех поддерживаемых локалях: так создаются скриншоты, которые публикуются в Hydra с соответствующими метаданными (имя страницы, область функций, платформа UI и один критический фрагмент метаданных, — уникальное экранное определение).

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

Технология


Hydra — это полностековое веб-приложение, размещенное в AWS. Back-end на Java выполняет две основных функции: обрабатывает входящие скриншоты и предоставляет данные для бэк-энда через REST API.

image

Когда автоматизация UI отправляет экран в Hydra, сам файл изображения записывается на S3, что обеспечивает его бесконечное хранение (плюс-минус), а метаданные гораздо меньшего размера записываются в базу данных RDS, чтобы впоследствии запрашивать их через REST API. Конечные точки REST (REST endpoints) обеспечивают отображение параметров строки запроса на запросы MySQL.

Например:

REST/v1/lists/distinctList?item=feature&selectors=uigroup,TVUI;area,signupwizard;locale,da-DK

В этом запросе содержатся параметры для выбора необходимых данных из Базы:

select distinct feature where uigroup = ‘TVUI’ AND area = ‘signupwizard’ AND locale = ‘da-DK’

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

Масштабирование приложения


После установки Hydra и запуска автоматизации добавить новые локали так же легко, как добавить одну строку в существующий файл свойств, который отправляется в Data Provider фреймворка testNG. Экраны с новой локалью будут отображаться со следующими работающими сборками Jenkins.

Что дальше?


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

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

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

Let's block ads! (Why?)

От критиков к алгоритмам: затухающий голос элит в мире музыки

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

Но с каждым годом мнение элит становится все менее ценным, а на смену критикам пришли плейлисты и алгоритмы. Расскажем, как это произошло.


Фото Sergei Solo / Unsplash

Музыкальная индустрия до 19 века


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

Слово «консерватория» появилось в 16 веке и обозначало детский приют, где воспитанников обучали музыке. Консерватории, соответствующие современному определению термина — с входным конкурсом, четкой образовательной программой и карьерными перспективами —распространились по Европе лишь в 19 веке.

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

До того, как музыку Баха популяризировал Мендельсон, композитора вспоминали, в первую очередь, как выдающегося педагога.



Фото Matthew Cramblett / Unsplash

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

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

Именно поэтому, как пишет кембриджский профессор и пианист Джон Ринк (John Rink) в своем эссе из сборника «Кембриджской истории музыки», композиторы нередко делили свое творчество на недолговечные «хиты» для репертуара концертирующих исполнителей и долгоиграющую «нетленку». Производство музыки в таком контексте вставало на конвейер.

Рождение академической музыки


Устоявшийся порядок начал меняться на рубеже 18-19 веков, когда преобразилось само отношение образованных европейцев к музыке. Благодаря романтическим веяниям, широко распространилось понятие «высокой» музыки. Элиты начали видеть в европейской культуре инструментальных произведений нечто абсолютное, отличное от веяний изменчивой моды.
Сейчас такой подход к музыке мы называем академическим.

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


Фото Diliff / Wiki

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

Музыкальная критика и журнализм


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

Их задача — отличать вечное от переходящего — подчеркивала безвременность высокой музыки в академической традиции. Уже в двадцатом веке гитарист Фрэнк Заппа едко отмечал, что «говорить о музыке — все равно что танцевать об архитектуре». И вполне обоснованно.

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

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

Реакция критиков на премьеру композиции могла определить ее дальнейшую судьбу. Например, после разгрома первой симфонии Рахманинова на страницах петербургского издания «Новости и Биржевая Газета», произведение не исполняли вплоть до смерти композитора.

Учитывая необходимость в понимании технической стороны композиторского мастерства, роль критиков часто играли сами сочинители музыки. Рецензию, о которой речь шла выше, написал Цезарь Антонович Кюи — член «Могучей Кучки». Также своими рецензиями были знамениты Римский-Корсаков и Шуман.

Музыкальный журнализм стал важным элементом новой музыкальной экосистемы 19 века. И, подобно другим аспектам этой молодой «индустрии», он тоже контролировался образованной, привилегированной элитой с академическими стандартами.

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


Фото frankie cordoba / Unsplash

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

P.S. Наша недавняя серия материалов «Блеск и нищета».

Let's block ads! (Why?)

[Перевод] Автоматически импортируем избранные библиотеки в IPython или Jupyter Notebook

Всем привет!

Сегодня мы разберем очень короткий, но полезный лайфхак о том, что нужно сделать, чтобы не вводить, к примеру, «import pandas as pd» по 10 раз в день. Для этого нужно:

  1. Перейти к ~/.ipython/profile_default;
  2. Создать папку с именем startup, если ее там еще нет;
  3. Добавить новый файл Python с именем start.py;
  4. Поместите ваш любимый импорт в этот файл;
  5. Запустить IPython или Jupyter Notebook, и ваши любимые библиотеки будут автоматически загружаться каждый раз!

Для наглядности, давайте всё визуализируем. Во-первых, местоположение start.py:

image

Здесь содержимое моего файла start.py:

import pandas as pd
import numpy as np

# Pandas options
pd.options.display.max_columns = 30
pd.options.display.max_rows = 20

from IPython import get_ipython
ipython = get_ipython()

# If in ipython, load autoreload extension
if 'ipython' in globals():
    print('\nWelcome to IPython!')
    ipython.magic('load_ext autoreload')
    ipython.magic('autoreload 2')

# Display all cell outputs in notebook
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = 'all'

# Visualization
import plotly.plotly as py
import plotly.graph_objs as go
from plotly.offline import iplot, init_notebook_mode
init_notebook_mode(connected=True)
import cufflinks as cf
cf.go_offline(connected=True)
cf.set_config_file(theme='pearl')

print('Your favorite libraries have been loaded.')

Теперь, когда я запускаю сеанс IPython, я вижу:

image

Также, мы можем подтвердить, что библиотеки были загружены, проверяя globals ():

globals()['pd']
<module 'pandas' from '/usr/local/lib/python3.6/site-packages/pandas/__init__.py'>
globals()['np']
<module 'numpy' from '/usr/local/lib/python3.6/site-packages/numpy/__init__.py'>

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

На заметку:


  • Файл может быть назван как угодно (start.py просто легко запомнить), а также вы можете иметь несколько файлов startup/. Они выполняются в лексикографическом порядке при запуске IPython.
  • Если вы выполняете это в Jupyter, вы не получите ячейку с данными импорта, поэтому при совместном использовании Jupyter обязательно скопируйте содержимое start.py в первую ячейку. Это позволит людям узнать, какие библиотеки вы используете.
  • Если вы работаете на нескольких компьютерах, вам придется повторить шаги. Убедитесь, что вы используете тот же скрипт start.py, чтобы получить тот же импорт!
  • Спасибо ответу из Stack Overflow и официальной документации.

Если у кого-то есть, что дополнить или поделиться лайфхаками — пишите в комментариях.

Всем знаний!

Подпишитесь на канал «Нейрон» в Телеграме (@neurondata) ― там свежие статьи и новости из мира науки о данных появляются каждую неделю. Спасибо всем, кто помогает и делится полезными ссылками, особенно Виталию Орешкину и Андрею Бондаренко.

Также не будем забывать, и автоматизировать и сокращать всё эффективно:

image

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

Let's block ads! (Why?)

[Из песочницы] Поднимаем читаемость кода в iOS разработке

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

Представили?

Смогли бы вы понять, о чем книга?

Насколько быстро вы смогли бы найти интересующий вас отрывок?

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

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

Для удобства я буду использовать слово класс (class), но подразумевать любой вид типа (class, struct, enum).

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

Для начала давайте сравним один и тот же код в двух вариантах.

Пример беспорядочного класса:


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

Пример чистого класса


Пустая строка 38 — делайте отступ в одну строку от последнего метода, чтобы было видно где заканчивается последняя закрывающая скобка метода, а где заканчивается сам класс.

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

Основные принципы для формирования чистой структуры класса:


  1. Всегда используем // MARK: -
  2. Даем названия меток и устанавливаем их очередность
  3. Выносим логику из методов жизненного цикла в отдельные методы
  4. Используем extension для реализации протоколов
  5. Выделяем логически связанные элементы
  6. Убираем неиспользуемое
  7. Автоматизируем рутину

1. Всегда используем // MARK: -


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

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


Посмотреть оглавление файла можно нажав на кнопку после стрелочки направо (>) в самом верху файла после названия данного файла или ctr + 6 (document items menu).

2. Даем названия меток и устанавливаем их очередность


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

3. Выносим логику из методов жизненного цикла в отдельные методы


Логика внутри методов жизненного цикла ViewController'a должна быть вынесена в отдельные методы, даже если придется создавать метод с одной строчкой кода. Сегодня одна, а завтра десять.
За счет того, что детали реализации вынесены в сторонние методы, логика жизненного цикла становится яснее.

4. Используем extension для реализации протоколов


Выносите реализацию протоколов в extension с пометкой // MARK: — SomeProtocol:
По данной метке будет лежать все, что связанно с этим протоколом  -  все что есть только тут и больше никуда ходить не надо, в противном случае методы и свойства протокола буду разбросаны по всему классу.

5. Выделяем логически связанные элементы


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

6. Убираем неиспользуемое


Не оставляйте лишние комментарии (дефолтные), пустые методы или мертвый функционал  -  это засоряет код. Обратите внимание на класс AppDelegate, скорее всего вы обнаружите там пустые методы с комментариями внутри.

7. Автоматизируем рутину


Чтобы не писать вручную в каждом классе // MARK: — SomeMark, используйте Code Snippet.


Пишем метку, выделяем ее, далее Editor -> Create Code Snippet, даем ей название и вызываем по шорткату.

// MARK: — Bonus


  1. Отмечайте класс ключевым словом final если данный класс не будет иметь наследников  -  проект быстрее компилируется, а код быстрее работает.
  2. Отмечайте свойства, аутлеты и методы ключевым словом private  —  они будут доступны только внутри класса и не будут в публичном списке свойств и методов, если они там не нужны.

Желаю всем успехов в разработке приложений и пусть ваш класс станет чище!

// MARK: — Помощь в написании статьи
Сергей Пчеляков
Алексей Плешков medium.com/@AlekseyPleshkov

// MARK: — Links
Ray Wenderlich code style

Let's block ads! (Why?)

Huawei получает новый удар: компания лишилась возможности выпускать смартфоны с microSD

За последние несколько недель компания Huawei получила множество проблем, вызванных указом президента США Дональда Трампа. С китайским производителем электронных устройств разорвали отношения такие компании, как Google, Qualcomm, Microsoft и другие. Телефоны Huawei, по всей видимости, в августе лишатся сервисов Google.

Кроме того, в будущем компания не сможет выпускать свои телефоны с модулем карт памяти формата microSD. Причина — односторонний разрыв отношений с китайской компанией ассоциацией SD Association. Таким образом, компания Huawei будет вынуждена исключить модуль из всех своих смартфонов, включая линейки Mate 2019 года, линейку Honor и другие.
Ассоциация не делала предварительных заявлений, а просто обновила список своих членов, в котором теперь нет Huawei. Об этом ясно говорит обновление списка компаний, которые являются членами ассоциации.

Сама она была основана в январе 2000 года, это некоммерческая организация. Ее основали такие производители электроники, как Toshiba, Panasonic и SanDisk. Цель ассоциации — проектирование, развитие и продвижение стандарта карт памяти SD — всех без исключения форматов. Накопители предназначены для работы в потребительской электронике — смартфонах, ноутбуках, планшетах и т.п. Сейчас в эту ассоциацию входят около 1000 компаний.

Руководство компании уже заявило о том, что планирует использовать в своих телефонах собственные накопители информации, которые получили название NM Card (Nano Memory Card). По размеру они не превосходят NanoSIM.

Производит эти карты памяти не сама компания, а ее партнер — Toshiba. По словам разработчиков, несмотря на малые размеры накопитель довольно быстрый. Скорость обмена данными достигает до 90 Мб/с. Они уже продаются в некоторых крупных сетях магазинов компьютерной и бытовой техники. Что касается других производителей, то они пока не анонсировали поддержку нового формата в своих телефонах.

Кроме возможности работать с microSD-картами, компания Huawei потеряла доступ к сервисам Google, получив лишь временную лицензию, которая будет работать до августа этого года. Кроме того, разрыв сотрудничества с Microsoft чреват отсутствием обновлений для ОС Windows 10 на ноутбуках и планшетах, производимых компанией.

На днях работу с Huawei прекратил и британский производитель чипов ARM. На базе архитектуры этих чипов китайская компания производит собственные мобильные процессоры. ARM отозвала лицензию на свою интеллектуальную собственность. Причина — присутствие в технологиях ARM американских наработок. Так что будущее мобильных процессоров Kirin пока что не слишком радужное.

Тем не менее, руководство китайского производителя делает оптимистичные заявления. Так, смартфоны будут переведены на процессоры сторонних разработчиков, вместо Android компания Huawei планирует использовать собственную операционную систему. Также китайцы ищут альтернативу ОС от Microsoft.

Let's block ads! (Why?)

[Перевод] Fancy Euclid's “Elements” in TeX

[Из песочницы] Как я писал свой мониторинг

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

Когда я был молод и горяч и не знал, куда девать свою энергию, я решил немного пофрилансить. Мне удалось быстро набить рейтинг и я нашёл пару постоянных клиентов, которые попросили поддерживать их сервера на постоянной основе.
Первое, о чём я подумал, это необходимость мониторинга. Решил сделать как умные люди, не изобретать велосипед, а посмотреть готовые варианты, такие как Munin или Zabbix. Но сразу обнаружилось, что Web-версия требует хорошего интернет соединения, особенно если открывать впервые с телефона. Если же ты отдыхаешь на природе вдали от города, получить стабильную связь сложно. Поэтому был выбран консольный вариант мониторинга.

В качестве консольного мониторинга мне хорошо помог atop и программа для чтения логов atop’а — atopsar. Их уже упоминали на habr, atop даже разобрали, а вот про atopsar ничего почти не рассказали.

Установка


Очень простая установка, всего три команды.

#Centos

yum install atop

#Debian/Ubuntu
apt-get install atop

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

#Debian/Ubuntu/Centos

/etc/default/atop 

Стандартный файл:
 #cat /etc/default/atop
INTERVAL=60                    #Время, через которое создаётся снимок нагрузки в секундах, по умолчанию каждые 10 минут
LOGPATH="/var/log/atop"        #Путь до папки хранения логов
OUTFILE="$LOGPATH/daily.log"   #Название файла логов за сегодняшний день


Добавляем в автозапуск
#Debian/Ubuntu/Centos
systemctl enable atop 

Запускаем atop как демон
#Debian/Ubuntu/Centos
systemctl start atop  

Для ленивых собрал в одну команду
#Centos
yum install atop && systemctl enable atop && systemctl start atop

#Debian/Ubuntu
apt-get install atop && systemctl enable atop && systemctl start atop

Atopsar


Вместе с atop ставится и atopsar, это удобный консольный анализатор бинарных логов, которые ведёт демон atop. Конечно можно читать логи и самим atop’ом, но это не так удобно, если требуется захватить большой интервал времени.

Небольшой ликбез по работе atopsar.

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

Ключи, которые я использую:

-A = вывести всю информацию из лога
= вывести информацию по нагрузки на ядра процессора, ключ по умолчанию
-m = нагрузка на оперативную память и swap
-d = дисковая активность
-O = топ-3 процессов нагрузки на CPU
-G = топ-3 процессов нагрузки на RAM
-D = топ-3 процессов нагрузки на диск
-N = топ-3 процессов нагрузки на сеть
-r = указать путь до лога, который хотите прочитать, если надо посмотреть нагрузку за прошедшие дни
-b = время, с которого начать вывод
-e = время, на котором надо закончить вывод
-M = создаёт дополнительный столбец в конце, в котором помечается критичность строки (+ есть нагрузка, * — критическая нагрузка)

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

Уведомления


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

Я один слежу за серверами, поэтому уведомлять нужно туда, где я всегда смогу это увидеть и хоть как-то на это среагировать.

В начале были SMS — быстро, надёжно, бесплатно. Но потом мобильные операторы прикрыли бесплатную SMS рассылку через свои шлюзы.
Почта — долго, могут быть проблемы с доставкой.
Мессенджеры — надо ставить на телефон, необходимо создавать ботов.

В результате поиска был выбран мессенджер Телеграмм за простоту и удобное приложение на телефоне и десктопе.

Создал своего бота с помощью botfather.
После положил на сервер несколько скриптов, отслеживающих нагрузку на сервер (IDL, smartct и др.l), наличие ошибок вида «oom killer», ошибки при создании бэкапа и другие операции, которые необходимо контролировать.

Скрипты довольно простые, написанные на bash, например, проверка LA и уведомление о превышении Load Averadge’м количества ядер на сервере.

if [ ${LA[0]} -gt 2000 ] || [ ${LA[1]} -gt 3000 ] || [ ${LA[2]} -gt 4000 ]
    then
        wget -O /dev/null "https://api.telegram.org/$bot_id:$bot_key/sendMessage?chat_id=$chat_id&text=На сервере $ip LA $LAd"
        wget -O /dev/null "https://api.telegram.org/$bot_id:$bot_key/sendMessage?chat_id=$chat_id&text=`top -b -n 1 | grep Cpu`"
        wget -O /dev/null "https://api.telegram.org/$bot_id:$bot_key/sendMessage?chat_id=$chat_id&text=Топ 5 процессов `top -b -n 1 | grep -A 5 'PID USER' | tail -5`"
    fi

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

Единственный нюанс — если сервер находится в России (и у вас нет IPv6 на сервере), то необходимо пользоваться прокси. Для этого в начале скрипта надо прописать строку подключения к прокси:

export https_proxy=http://логин:пароль@IP.адрес:порт

Это не конец


Идёшь ты себе спокойно по горам с рюкзаком за спиной, отдыхаешь от цивилизации, и тут телефон, случайно поймав связь, кидает уведомление о возникшей на твоём сервере проблеме. Что делать? Безмятежное настроение как ветром сдуло. Звонить жене и надиктовывать команды? Ха-ха!

Нужно было срочно придумать какой-то способ устранения возникших проблем быстро и без наличия хорошего интернета. Здесь меня опять спас мессенджер (#телеграммживи). Я научил своего бота общаться только со мной, игнорируя всех остальных. Теперь, вместе с уведомлением о проблеме, мне приходит немного больше данных, по которым я понимаю, кто источник проблемы, и могу попробовать ее решить удалённо. Достаточно просто написать сообщение боту, подкинуть телефон повыше, чтобы это сообщение ушло, и вуаля — бот пошёл делать твою работу. Таким образом я могу, например, убить какой-нибудь неугодный процесс, перезапустить демон, заблокировать IP и прочее.

Сюда же я перенёс будущие нужные запросы от клиентов, например, срочный сброс паролей пользователям (ибо “Аааа, мы не можем попасть на сервер, мы теряем миллионы!”), поиск пользователя, имеющего доступ в нужную папку, включение и выключение сайта и другие. Конечно же я постоянно дорабатываю функционал бота, так как фантазия клиентов подкидывает порой неожиданные и не предусмотренные мной запросы. Но основные удовлетворены.

Имеется и версия для VK, но она как-то не прижилась.

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

Let's block ads! (Why?)

[Из песочницы] Что нужно знать перед переходом на Akka toolkit для реализации Event Sourcing и CQRS

Здравствуйте, уважаемые читатели Хабра. Меня зовут Рустем и я главный разработчик в казахстанской ИТ-компании DAR. В этой статье я расскажу, что нужно знать перед тем, как переходить на шаблоны Event Sourcing и CQRS с помощью Akka toolkit.

Примерно с 2015 года мы начали проектировать свою экосистему. После анализа и опираясь на опыт работы со Scala и Akka, решили остановиться на Akka toolkit. У нас были и удачные реализации шаблонов Event Sourcing c CQRS и не очень. Накопилась экспертиза в этой области, которой я хочу поделиться с читателями. Мы рассмотрим, как Akka реализует эти паттерны, а также какие инструменты доступны и поговорим о подводных камнях Akka. Надеюсь, что после прочтения этой статьи, у вас будет больше понимания рисков перехода на Akka toolkit.

На темы CQRS и Event Sourcing было написано много статей на Хабре и на других ресурсах. Данная статья предназначена для читателей, которые уже понимают, что такое CQRS и Event Sourcing. В статье я хочу сконцентрироваться на Аkka.


Domain-Driven Design

Про Domain-Driven Design (DDD) писали много материалов. Есть как противники, так и сторонники такого подхода. От себя хочу добавить, что если вы решили перейти на Event Sourcing и CQRS, то будет не лишним изучить DDD. К тому же, философия DDD чувствуется во всех инструментах Аkkа.

На самом деле, Event Sourcing и CQRS — это только маленькая часть большой картины под названием Domain-Driven Design. При проектировании и разработке, у вас могут возникнуть много вопросов о том, как правильно реализовать эти шаблоны и интегрировать в экосистему, а знание DDD облегчить вам жизнь.


В данной статье, термин сущность (entity по DDD), будет обозначать Persistence Actor который имеет уникальный идентификатор.

Почему Scala?

У нас часто спрашивают, почему Scala, а не Java. Одна из причин — это Akka. Сам фреймворк, написан на языке Scala c поддержкой языка Java. Здесь нужно сказать, что так же есть реализация на .NET, но это уже другая тема. Чтобы не вызывать дискуссию, я не буду писать, чем Scala лучше или хуже, чем Java. Я лишь рассказу пару примеров, которые, по-моему мнению, у Scala есть преимущество перед Java при работе с Akka:


  • Неизменяемые объекты. В Java нужно писать неизменяемые объекты самому. Поверьте, это не легко и не совсем удобно постоянно писать финальные параметры. В Scala case class уже неизменяемый со встроенной функцией copy
  • Стиль написания кода. При реализации на Java, вы все равно будете писать в стиле Scala, то есть, функционально.

Вот пример реализации actor на Scala и Java:

Scala:

object DemoActor {
  def props(magicNumber: Int): Props = Props(new DemoActor(magicNumber))
}

class DemoActor(magicNumber: Int) extends Actor {
  def receive = {
    case x: Int => sender() ! (x + magicNumber)
  }
}

class SomeOtherActor extends Actor {
  context.actorOf(DemoActor.props(42), "demo")
  // ...
}

Java:

static class DemoActor extends AbstractActor {
  static Props props(Integer magicNumber) {
    return Props.create(DemoActor.class, () -> new DemoActor(magicNumber));
  }

  private final Integer magicNumber;

  public DemoActor(Integer magicNumber) {
    this.magicNumber = magicNumber;
  }

  @Override
  public Receive createReceive() {
    return receiveBuilder()
        .match(
            Integer.class,
            i -> {
              getSender().tell(i + magicNumber, getSelf());
            })
        .build();
  }
}

static class SomeOtherActor extends AbstractActor {
  ActorRef demoActor = getContext().actorOf(DemoActor.props(42), "demo");
  // ...
}

(Пример взят с отсюда)

Обратите внимание на реализацию метода createReceive() на примере языка Java. Внутри, через фабрику ReceiveBuilder, реализуется pattern-matching. receiveBuilder() — метод от Akka для поддержки лямбда-выражений, а именно pattern-matching в Java. В Scala это реализуется нативно. Согласитесь, код в Scala короче и легче читаем.


  • Документация и примеры. Несмотря на то, что в официальной документации есть примеры на Java, на просторах интернета почти все примеры на Scala. Так же, вам будет легче ориентироваться в исходниках библиотеки Akka.

По части производительности, разницы между Scala и Java не будет, так как все крутится в JVM.

Хранилище

До реализации Event Sourcing с помощью Akka Persistence, рекомендую заранее выбрать базу для постоянного хранения данных. Выбор базы зависит от требований к системе, от ваших желаний и предпочтений. Данные можно хранить как в NoSQL и RDBMS, так и в файловой системе, например LevelDB от Google.

Важно отметить, что Akka Persistence не отвечает за запись и чтение данных из базы, а делает это через плагин, который должен реализовать Akka Persistence API.

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

Для постоянного хранения данных, мы решили остановиться на Cassandra. Дело в том, что нам нужна была надежная, быстрая и распределенная база. К тому же, Typesafe сами сопровождают плагин, который полностью реализует Akka Persistence API. Он постоянно обновляется и в сравнение с другими, у плагина Cassandra написана более полная документация.

Стоит оговориться, что у плагина также есть несколько проблем. Например, все еще нет стабильной версии (на момент написания этой статьи, последняя версия 0.97). Для нас самой большой неприятностью, с которой мы встретились при использовании данного плагина, была потеря событий при считывании Persistent Query для некоторых сущностей. Для полной картины, ниже приведена схема CQRS:

Persistent Entity распределяет события сущностей на тэги по алгоритму consistent hash (например на 10 шардов):

Затем, Persistent Query подписывается на эти тэги и запускает поток, который складывает данные в Elastic Search. Так как Cassandra в кластере, события будут разбросаны по нодам. Некоторые ноды могут проседать и будут отвечать медленнее остальных. Нет гарантии, что вы получите события в строгом порядке. Для решения этой проблемы, плагин реализован так, что если он получит неупорядоченное событие, например entity-A event NR 2, то он определенное время ждет первоначальное событие и если его не получит, то просто проигнорирует все события данной сущности. Даже по этому поводу были дискуссии в Gitter. Если кому интересно, можно прочитать переписку между @kotdv и разработчиками плагина: Gitter

Как можно решить это недоразумение:


  • Нужно обновить плагин до последней версии. В последних версиях разработчики Typesafe решили много проблем, связанных с Eventual Consistency. Но, мы все еще ждем стабильной версии
  • Были добавлены более точные настройки компонента, который отвечает за получение событий. Можно попробовать увеличить время ожидания неупорядоченных событий для более надежной работы плагина: cassandra-query-journal.events-by-tag.eventual-consistency.delay=10s
  • Настроить Cassandra согласно рекомендациям DataStax. Поставить garbage collector G1 и выделить как можно больше памяти для Cassandra.

В конце концов, проблему с недостающими событиями мы решили, но теперь есть стабильная задержка данных на стороне Persistence Query (от пяти до десяти секунд). Было решено оставить подход для данных, которые используются для аналитики, а там, где важна скорость, мы вручную публикуем события на шину. Главное выбрать подходящий механизм обработки или публикации данных: at-least-once или at-most-once. Хорошее описание от Akka можно почитать здесь. Для нас было важно соблюдать консистенцию данных и поэтому, после успешной записи данных в базу, мы ввели переходное состояние, которое контролирует успешную публикацию данных в шине. Ниже приведен пример кода:


object SomeEntity {

  sealed trait Event {
    def uuid: String
  }

  /**
    * Событие, которое отправляется на сохранение.
    */
  case class DidSomething(uuid: String) extends Event

  /**
    * Индикатор, который указывает что последнее событие было опубликовано.
    */
  case class LastEventPublished(uuid: String) extends Event

  /**
    * Контейнер, который хранит текущее состояние сущности.
    * @param unpublishedEvents – хранит события, которые не опубликовались.
    */
  case class State(unpublishedEvents: Seq[Event])

  object State {
    def updated(event: Event): State = event match {
      case evt: DidSomething =>
        copy(
          unpublishedEvents = unpublishedEvents :+ evt
        )
      case evt: LastEventPublished =>
        copy(
          unpublishedEvents = unpublishedEvents.filter(_.uuid != evt.uuid)
        )
    }
  }
}

class SomeEntity extends PersistentActor {
  …
  persist(newEvent) { evt =>
    updateState(evt)
    publishToEventBus(evt)
  }
  …
}

Если по каким-либо причинам не удалось опубликовать событие, то при следующем старте SomeEntity, он будет знать, что событие DidSomething не дошел до шины и повторит попытку повторно опубликовать данные.


Сериализатор

Сериализация — это не менее важный пункт в использовании Akka. У него есть внутренний модуль — Akka Serialization. Этот модуль используется для сериализации сообщений при обмене ими между актерами и при хранении их через Persistence API. По умолчанию используется Java serializer, но рекомендуется использовать другой. Проблема в том, что Java Serializer медленный и занимает много места. Есть два популярных решения- это JSON и Protobuf. JSON, хоть и медленный, но его проще реализовать и поддерживать. Если нужно минимизировать расходы на сериализацию и хранение данных, то можно остановиться на Protobuf, но тогда процесс разработки пойдет медленнее. Помимо Domain Model, придется писать еще Data Model. Не стоит забывать про версионность данных. Будьте готовы постоянно писать маппинг между Domain Model и Data Model.

Добавили новое событие — пишите маппинг. Изменили структуру данных — пишите новую версию Data Model и измените функцию маппинга. Не забывайте еще про тесты для сериализаторов. В общем, работы будет не мало, но в итоге получите слабосвязанные компоненты.


Выводы


  • Тщательно изучите и выберите подходящую для себя базу и плагин. Рекомендую выбирать плагин, который хорошо сопровождается и не остановится в разработке. Область относительно новая, есть еще куча недоработок, которые только предстоит решить
  • Если выберите распределенное хранилище, то придется решать проблему с задержкой до 10 секунд самому, либо смириться с этим
  • Сложность сериализации. Вы можете пожертвовать скоростью и остановиться на JSON, либо выбрать Protobuf и писать множество адаптеров и поддерживать их.
  • Есть и плюсы этого шаблона, это слабо связанные компоненты и независимые команды разработчиков, которые строят одну большую систему.

Let's block ads! (Why?)

Интернет-спутники SpaceX, выстраивающиеся на орбите Земли, попали на видео


На этой фотографии спутники еще не отделились, через несколько секунд они отправятся в «свободное плавание»

По словам Илона Маска, главы компании SpaceX, спутники интернет-связи Starlink успешно выведены на орбиту. Сейчас их ровно 60, это примерно в 200 раз меньше, чем планируется (всего спутников должно быть около 12 тысяч). Спутники были выведены на орбиту в течение часа после отделения от ракеты Falcon 9.

Маск сообщил, что движки спутников, которые служат для маневрирования, работают отлично. Рабочая высота для спутников — 550 километров. Астроном из Голландии смог снять стройный ряд спутников в момент маневрирования и начального момента их выхода на орбиту.
Изображение получено на камеру WATEC 902H с оптикой Canon FD 1.8/50 мм.


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

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


Сеть не будет бесплатной. Ценовая политика пока неизвестна, но Илон Маск рассчитывает на $20-50 млрд ежегодного дохода от этой сети. Об этом предприниматель рассказал репортёрам ранее.

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

Что касается спутниковой сети, то SpaceX — далеко не единственная компания, которая разворачивает спутниковую интернет-сеть. Аналогичную задачу сейчас решают компании OneWeb, Amazon, Telesat.

Let's block ads! (Why?)

[Перевод] Искусство создания органических 3D-моделей: субдермальные шейдеры

Daniel Bauer рассказал о создании реалистичной модели зубов и субдермальных шейдеров в ZBrush, Toolbag и Substance Painter.


Ресурсы


Свой проект я начал со сбора референсов в Google, Pinterest и Youtube.

Сегодня можно найти огромное изобилие ресурсов и проблема заключается в фильтрации такого объёма информации. Для упорядочивания своих ресурсов я пользуюсь PureRef. На таких досках можно хранить технические подробности, например, анатомические размеры, или значения отражаемости и изображения зубов. Сначала на моей доске не было CGI, потому что я стремился черпать вдохновение из реальных примеров.


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

Скульптинг


Для скульптинга основных объёмов я использовал ZBrush. Я не строго придерживался формы, потому что хотел, чтобы образ хорошо читался на расстоянии. Очень часто я сильно отдалял модель, чтобы проверить её. Для получения удовлетворительного результата мешу пришлось претерпеть множество итераций. Кстати, можно воспользоваться режимом ZBrush «see-through» и сравнить свой скульптинг с 3D-сканами на Sketchfab.

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


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

Я использовал transpose line ZBrush, чтобы измерить каждый зуб и привести их все к верному размеру. Многие люди испытывают трудности с единицами измерения ZBrush, поэтому дам совет:

  • Добавьте в сцену вспомогательный объект единичного размера, перенесите его с помощью GoZ в 3D-пакет, отмасштабируйте до 10мм и верните через GoZ обратно в ZBrush.
  • Теперь перетащите Transpose Line с одной стороны куба на другую и задайте масштаб в 1 Unit. Так вы сможете использовать Transpose Line, которая считывает точные значения.


Я скульптил каждый зуб по отдельности и это даёт мне полный контроль над запеканием в Toolbag или в Substance Painter. Если каждый инструмент (subtool) будет храниться отдельно, то это обеспечивает гибкость при необходимости модифицирования зуба. Когда я понял, насколько сложны на самом деле человеческие зубы, этот метод позволил мне прислушиваться к отзывам других художников и без проблем устранять недостатки форм. В первом проходе некоторые детали преувеличены, а другие отсутствуют:

Совет: из-за особенностей реализации подразделений (subdivisions, SD), при возврате к уровню 1 SD меш может сжаться. Это приводило к нежелательному эффекту, ведь мои зубы, дёсны и blend mesh должны идеально сочетаться. Я написал макрос, сохранявший morphtarget меша перед каждой командой subdivide и восстанавливающий его исходный объём.

Я использовал этот макрос как мою основную команду subdivide, привязанную к CTRL+D. Скрипт можно скачать отсюда.

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

Плавность расположения полигонов зубов и дёсен помогает blendmesh сохранить все сложные формы.

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


Вот пара хороших ресурсов по топологии:
Закончив с топологией и скульптингом, я импортирую decimated-меши в Marmoset Toolbag. Это помогает получить общее представление о внешнем виде модели при разных моделях освещения.

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

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

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


UV-развёртка


Можно импортировать меши в 3ds Max или другое похожий пакет для создания развёртки и планировки текстуры. Мне хотелось сохранить UDIM-развёртку, чтобы иметь готовую к продакшену модель на разных платформах. Я перенёс через GoZ мои инструменты 3ds Max для указания швов разрезов, а затем выполнил «quick-Peel» и операцию упаковки. Затем вернул всё через GoZ обратно в ZBrush, чтобы воспользоваться его мощным инструментом создания развёрток («use existing seems»). Это очень важно, если вы хотите экспортировать карты смещений из ZBrush.

Совет: многое о развёртках можно узнать у Тима Бергхольца.

Раскрашивание текстур и смещение


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

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


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

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


Совет: создайте тёмно-серый цвет во всех наборах текстур и переключайте режим смешивания на «replace», чтобы сразу же проверять поведение карты roughness при различных условиях освещения.
  • Непрозрачность для blendmesh

Сначала выберите для этого набора текстур отдельный шейдер. Идеальные результаты получаются с шейдером «metal/rough-with-alpha-test». К другим наборам текстур примените «skin-shader».

Добавьте в «Texture Set Settings» канал opacity и создайте fill layer внизу стека слоёв с нейтральными нормалями и обнулённой информацией об opacity.

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

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


Совет: экспортировать свою маску можно через меню набора текстур или просто нажав правую клавишу мыши на стеке маски и выбрав «Export mask to file».
  • Displacement

Карты смещений я экспортировал из ZBrush.

В нём есть удобная функция, анализирующая инструменты и выравнивающая карту смещений, чтобы покрыть все высокие и низкие значения. Я использовал Multi Map Exporter, чтобы объединить отдельные инструменты с относящимися к ним тайлам UV, использовав следующие параметры:


Шейдер


Для каждого набора текстур я создал в Marmoset Toolbag свой шейдер. Это позволило мне использовать разные параметры шейдеров для языка, зубов и дёсен.

Перед настройкой шейдеров SSS и смещения важно правильно задать масштаб сцены. В Toolbag есть ползунки «Scale» и «Scale-Center», и я воспользовался ими, чтобы подчеркнуть эффект смещения:


  • Шейдер зубов

В Toolbag есть хороший подповерхностный шейдер, который при наличии качественных карт может создавать отличные результаты. Чтобы добавить вариативности в запечённую карту нормалей, я использовал карту нормалей деталей. По своей природе зубы кажутся просвечивающими (translucent), и в достижении этого эффекта сильно помогает хорошая карта просвечиваемости (translucency map). Я запёк в Substance Painter карту толщины и изменил её в соответствии со своими потребностями.

Вот параметры моего шейдера для зубов:


Модель затенения


Я считаю, что самое важное в шейдере зубов — это модель отражения, для которой я выбрал параметр «Refractive». Индекс преломления определяет, насколько путь луча искажается или преломляется при попадании в материал. Очень важно придать материалам правильный внешний вид. Изучив вопрос, я узнал, что IOR дентина равен 1.540.

Чтобы подчеркнуть сниженные значения отражаемости во впадинах, я добавил в шейдер карту полостей и передвинул ползунок «specular cavity» на 0.4. Ползунок Diffuse Cavity я оставил на нуле, потому что моя Albedo map уже содержала заливку полостей красным цветом.

Вот сравнение между моделями преломлённого и зеркального отражения:


Совет: посмотрите объяснения модели зеркального отражения в видео Джо Уилсона и Ли Деновалда:



  • Шейдер дёсен

Физически верное значение зеркального отражения для кожи примерно равно 0.028. Это стало для меня хорошей опорной точкой.

Для дёсен я увеличил этот показатель до 0.07. Здесь нужно придерживаться низких значений, иначе шейдер может создать эффект металла. Придать результату убедительности может небольшой Fresnel.

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


Совет: здесь можно узнать о техниках реалистичного рендеринга кожи в реальном времени.
  • Шейдер слюны

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

Для идеального смешения между зубами и дёснами я использовал метод Refraction with Dithering. Вот параметры шейдера:


Результат, полученный после использования описанных техник:


Заключение


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

Это был очень увлекательный проект. У него всё ещё есть «шероховатости», но он стал отличным обучающим процессом по анатомии и материалам.

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

Спасибо за чтение! Надеюсь, эта статья была вам полезна.

Об авторе


Меня зовут Дэниел Бауэр, я 3D-художник и специализируюсь на моделировании органики. Свою карьеру я начал в небольшой студии 2D-анимации, где и познакомился с магией 3D-моделирования. Карьерный путь привёл меня к решению таких задач, как затенение и моделирование для CGI в области автомобилестроения. В свободное время я полюбил заниматься моделированием органики и продолжаю это делать и по сей день.

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

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

Let's block ads! (Why?)

[Из песочницы] Как работает конфигурация в .NET Core

Давайте отложим разговоры о DDD и рефлексии на время. Предлагаю поговорить о простом, об организации настроек приложения.

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

public IConfiguration Configuration { get; set; }
public IHostingEnvironment Environment { get; set; }

public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
   Environment = environment;
   Configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .AddJsonFile($"appsettings.{Environment.EnvironmentName}.json")
            .Build();
}

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


Как было раньше

Как и у любой истории, у этой статьи есть начало. Одним из первых вопросов после перехода на ASP.NET Core были трансформации конфигурационных файлов.


Вспомним как это было ранее c web.config

Конфигурация состояла из нескольких файлов. Основным был файл web.config, и к нему уже применялись трансформации (web.Development.config и др.) в зависимости от конфигурации сборки. При этом активно использовались xml-атрибуты для поиска и трансформации секции xml-документа.

Но как мы знаем в ASP.NET Core файл web.config заменен на appsettings.json и привычного механизма трансформаций больше нет.


Что нам говорит google?

Результатом поиска " Трансформации в ASP.NET Core " в google стал следующий код:

public IConfiguration Configuration { get; set; }
public IHostingEnvironment Environment { get; set; }

public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
   Environment = environment;
   Configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .AddJsonFile($"appsettings.{Environment.EnvironmentName}.json")
            .Build();
}

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

И такой:

public IConfiguration Configuration { get; set; }
public IHostingEnvironment Environment { get; set; }

public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
   Environment = environment;
   Configuration = new ConfigurationBuilder()
            .AddJsonFile($"appsettings.{Environment.EnvironmentName}.json")
            .Build();
}

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

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

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

Давайте разберемся, как работает конфигурация в .NET Core.


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

Конфигурация в .NET Core представлена объектом интерфейса IConfiguration.

public interface IConfiguration
{
   string this[string key] { get; set; }

   IConfigurationSection GetSection(string key);

   IEnumerable<IConfigurationSection> GetChildren();

   IChangeToken GetReloadToken();
}

  • [string key] индексатор, который позволяет по ключу получить значение параметра конфигурации
  • GetSection(string key) возвращает секцию конфигурации, которая соответствует ключу key
  • GetChildren() возвращает набор подсекций текущей секции конфигурации
  • GetReloadToken() возвращает экземпляр IChangeToken, который можно использовать для получения уведомлений при изменении конфигурации

Конфигурация представляет собой набор пар "ключ-значение". При чтении из источника конфигурации (файл, переменные окружения) иерархические данные приводятся к плоской структуре. Например json-объект вида

{
 "Settings": {
  "Key": "I am options"
 }
}

будет приведен к плоскому виду:

Settings:Key = I am options

Здесь ключом является Settings:Key, а значением I am options.
Для наполнения конфигурации используются провайдеры конфигурации.


Провайдеры конфигурации

За чтение данных из источника конфигурации отвечает объект интерфейса
IConfigurationProvider:

public interface IConfigurationProvider
{
   bool TryGet(string key, out string value);

   void Set(string key, string value);

   IChangeToken GetReloadToken();

   void Load();

   IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath);
}

  • TryGet(string key, out string value) позволяет по ключу получить значение параметра конфигурации
  • Set(string key, string value) используется для установки значения параметра конфигурации
  • GetReloadToken() возвращает экземпляр IChangeToken, который можно использовать для получения уведомлений при изменении источника конфигурации
  • Load() метод который отвечает за чтение источника конфигурации
  • GetChildKeys(IEnumerable<string> earlierKeys, string parentPath) позволяет получить список всех ключей, которые предоставляет данный поставщик конфигурации

Из коробки доступны следующие провайдеры:


  • Json
  • Ini
  • Xml
  • Environment Variables
  • InMemory
  • Azure
  • Кастомный провайдер конфигурации

Приняты следующие соглашения использования провайдеров конфигурации.


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

Если мы создаем экземпляр web-сервера используя CreateDefaultBuilder, то по умолчанию подключаются следующие провайдеры конфигурации:


  • ChainedConfigurationProvider через этот провайдер можно получать значения и ключи конфигурации, которые были добавлены другими провайдерами конфигурации
  • JsonConfigurationProvider использует в качестве источника конфигурации json-файлы. Как можно заметить, в список провайдеров добавлены три провайдера данного типа. Первый использует в качестве источника appsettings.json, второй appsettings.{environment}.json. Третий считывает данные из secrets.json. Если выполнить сборку приложения в конфигурации Release, третий провайдер не будет подключен, потому что не рекомендуется использовать секреты в Production-среде
  • EnvironmentVariablesConfigurationProvider получает параметры конфигурации из переменных окружения
  • CommandLineConfigurationProvider позволяет добавлять аргументы командой строки в конфигурацию

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

Если в провайдере CommandLineConfigurationProvider имеется элемент с ключом key и в провайдере JsonConfigurationProvider имеется элемент с ключом key, элемент из JsonConfigurationProvider будет заменен элементом из CommandLineConfigurationProvider так как он регистрируется последним и имеет больший приоритет.


Вспомним пример из начала статьи
public IConfiguration Configuration { get; set; }
public IHostingEnvironment Environment { get; set; }

public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
   Environment = environment;
   Configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .AddJsonFile($"appsettings.{Environment.EnvironmentName}.json")
            .Build();
}

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


Кастомный провайдер конфигурации

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

public interface IConfigurationSource
{
    IConfigurationProvider Build(IConfigurationBuilder builder);
}

Интерфейс состоит из единственного метода Build, который принимает в качестве параметра IConfigurationBuilder и возвращает новый экземпляр IConfigurationProvider.

Для реализации своих поставщиков конфигурации нам доступны абстрактные классы ConfigurationProvider и FileConfigurationProvider. В этих классах уже реализована логика методов TryGet, Set, GetReloadToken, GetChildKeys и остается реализовать только метод Load.

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

Создадим класс YamlConfigurationProvider и сделаем его наследником FileConfigurationProvider.

public class YamlConfigurationProvider : FileConfigurationProvider
{
    private readonly string _filePath;

    public YamlConfigurationProvider(FileConfigurationSource source) 
        : base(source)
    {
    }

    public override void Load(Stream stream)
    {
        throw new NotImplementedException();
    }
}

В приведенном фрагменте кода можно заметить некоторые особенности класса FileConfigurationProvider. Конструктор принимает экземпляр FileConfigurationSource, который содержит в себе IFileProvider. IFileProvider используется для чтения файла, и для подписки на событие изменения файла. Также можно заметить, что метод Load принимает Stream в котором открыт для чтения файл конфигурации. Это метод класса FileConfigurationProvider и его нет в интерфейсе IConfigurationProvider.

Добавим простую реализацию, которая позволит считать yaml-файл. Для чтения файла я воспользуюсь пакетом YamlDotNet.


Реализация YamlConfigurationProvider
 public class YamlConfigurationProvider : FileConfigurationProvider
{
    private readonly string _filePath;

    public YamlConfigurationProvider(FileConfigurationSource source) 
        : base(source)
    {
    }

    public override void Load(Stream stream)
    {
        if (stream.CanSeek)
        {
            stream.Seek(0L, SeekOrigin.Begin);
            using (StreamReader streamReader = new StreamReader(stream))
            {
                var fileContent = streamReader.ReadToEnd();
                var yamlObject = new DeserializerBuilder()
                    .Build()
                    .Deserialize(new StringReader(fileContent)) as IDictionary<object, object>;

                Data = new Dictionary<string, string>();

                foreach (var pair in yamlObject)
                {
                    FillData(String.Empty, pair);
                }
            }
        }
    }

    private void FillData(string prefix, KeyValuePair<object, object> pair)
    {
        var key = String.IsNullOrEmpty(prefix)
            ? pair.Key.ToString() 
            : $"{prefix}:{pair.Key}";

        switch (pair.Value)
        {
            case string value:
                Data.Add(key, value);
                break;

            case IDictionary<object, object> section:
            {
                foreach (var sectionPair in section)
                    FillData(pair.Key.ToString(), sectionPair);

                break;
            }
        }
    }
}

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


Реализация YamlConfigurationSource
public class YamlConfigurationSource : FileConfigurationSource
{
    public YamlConfigurationSource(string fileName)
    {
        Path = fileName;
        ReloadOnChange = true;
    }

    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        this.EnsureDefaults(builder);
        return new YamlConfigurationProvider(this);
    }
}

Тут важно отметить, что для инициализации свойств базового класса необходимо вызвать метод this.EnsureDefaults(builder).

Для регистрации кастомного провайдера конфигурации в приложении необходимо добавить экземпляр провайдера в IConfigurationBuilder. Можно вызвать метод Add из IConfigurationBuilder, но я сразу вынесу логику инициализации YamlConfigurationProvider в extension-метод.


Реализация YamlConfigurationExtensions
public static class YamlConfigurationExtensions
{
    public static IConfigurationBuilder AddYaml(
        this IConfigurationBuilder builder, string filePath)
    {
        if (builder == null)
            throw new ArgumentNullException(nameof(builder));

        if (string.IsNullOrEmpty(filePath))
            throw new ArgumentNullException(nameof(filePath));

        return builder
            .Add(new YamlConfigurationSource(filePath));
    }
}

Вызов метода AddYaml
public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((context, builder) =>
            {
                builder.AddYaml("appsettings.yaml");
            })
            .UseStartup<Startup>();
}

Отслеживание изменений

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


  • Поставщик конфигурации отслеживает изменение источника конфигурации
  • Если произошло изменение конфигурации, создается новый IChangeToken
  • При изменении IChangeToken вызывается перезагрузка конфигурации

Посмотрим как реализовано отслеживание изменений в FileConfigurationProvider.

ChangeToken.OnChange(
    //producer
    () => Source.FileProvider.Watch(Source.Path), 
    //consumer
    () => {                         
        Thread.Sleep(Source.ReloadDelay);
        Load(reload: true);
    });

В метод OnChange статического класса ChangeToken передается два параметра. Первый параметр это функция которая возвращает новый IChangeToken при изменении источника конфигурации (в данном случае файла), это т.н producer. Вторым параметром идет функция-callback (или consumer), которая будет вызвана при изменении источника конфигурации.
Подробнее о классе ChangeToken.

Не все провайдеры конфигурации реализуют отслеживание изменений. Этот механизм доступен для потомков FileConfigurationProvider и AzureKeyVaultConfigurationProvider.


Заключение

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

Данная статья затрагивает лишь основы. Помимо основ нам доступны IOptions, сценарии пост-конфигурации, валидация настроек и многое другое. Но это уже другая история.

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

Let's block ads! (Why?)