День 2378. #TipsAndTricks
Используем COPY для Экспорта/Импорта Данных в Potgresql
В PostgreSQL есть функция, позволяющая эффективно выполнять массовый импорт и экспорт данных в таблицу и из неё. Обычно это гораздо более быстрый способ загрузки данных в таблицу и извлечения из неё, чем использование команд INSERT и SELECT.
В .NET провайдер Npgsql поддерживает три режима операции COPY: бинарный, текстовый и бинарный необработанный.
1. Бинарный
Пользователь использует API для чтения и записи строк и полей, которые Npgsql кодирует и декодирует. После завершения необходимо вызвать функцию Complete() для сохранения данных; в противном случае операция COPY будет откачена при освобождении объекта записи (это поведение важно в случае возникновения исключения).
2. Текстовый
В этом режиме данные в БД и из неё передаются в текстовом или CSV-формате PostgreSQL. Пользователь должен самостоятельно отформатировать текст или CSV-файл, Npgsql предоставляет только функции чтения или записи текста. Этот режим менее эффективен, чем бинарный, и подходит, если у вас уже есть данные в CSV, а производительность не критична.
3. Бинарный необработанный
Данные передаются в двоичном формате, но Npgsql не выполняет никакого кодирования или декодирования — данные предоставляются как необработанный поток .NET. Имеет смысл только для обработки больших объемов данных и восстановления таблицы: таблица сохраняется как BLOB-объект, который впоследствии можно восстановить. Если нужно разбирать данные, используйте обычный бинарный режим.
Отмена
Операции импорта можно отменить в любой момент, освободив (dispose) NpgsqlBinaryImporter до вызова метода Complete(). Операции экспорта можно отменить, вызвав метод Cancel().
Источник: https://www.npgsql.org/doc/copy.html
Используем COPY для Экспорта/Импорта Данных в Potgresql
В PostgreSQL есть функция, позволяющая эффективно выполнять массовый импорт и экспорт данных в таблицу и из неё. Обычно это гораздо более быстрый способ загрузки данных в таблицу и извлечения из неё, чем использование команд INSERT и SELECT.
В .NET провайдер Npgsql поддерживает три режима операции COPY: бинарный, текстовый и бинарный необработанный.
1. Бинарный
Пользователь использует API для чтения и записи строк и полей, которые Npgsql кодирует и декодирует. После завершения необходимо вызвать функцию Complete() для сохранения данных; в противном случае операция COPY будет откачена при освобождении объекта записи (это поведение важно в случае возникновения исключения).
// Импорт в таблицу с 2 полями (string, int)
using (var writer = conn.BeginBinaryImport(
"COPY my_table (field1, field2) FROM STDIN (FORMAT BINARY)"))
{
writer.WriteRow("Row1", 123);
writer.WriteRow("Row2", 123);
writer.Complete();
}
// Экспорт из таблицы с 2 полями
using (var rdr = conn.BeginBinaryExport(
"COPY my_table (field1, field2) TO STDOUT (FORMAT BINARY)"))
{
rdr.StartRow();
Console.WriteLine(rdr.Read<string>());
Console.WriteLine(rdr.Read<int>(NpgsqlDbType.Smallint));
rdr.StartRow();
// пропускает поле
rdr.Skip();
// проверяет на NULL (без перехода на следующее поле)
Console.WriteLine(rdr.IsNull);
Console.WriteLine(rdr.Read<int>());
rdr.StartRow();
// StartRow() вернёт -1 в конце данных
}
2. Текстовый
В этом режиме данные в БД и из неё передаются в текстовом или CSV-формате PostgreSQL. Пользователь должен самостоятельно отформатировать текст или CSV-файл, Npgsql предоставляет только функции чтения или записи текста. Этот режим менее эффективен, чем бинарный, и подходит, если у вас уже есть данные в CSV, а производительность не критична.
using (var writer = conn.BeginTextImport(
"COPY my_table (field1, field2) FROM STDIN"))
{
writer.Write("HELLO\t1\n");
writer.Write("GOODBYE\t2\n");
}
using (var reader = conn.BeginTextExport(
"COPY my_table (field1, field2) TO STDOUT"))
{
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
}
3. Бинарный необработанный
Данные передаются в двоичном формате, но Npgsql не выполняет никакого кодирования или декодирования — данные предоставляются как необработанный поток .NET. Имеет смысл только для обработки больших объемов данных и восстановления таблицы: таблица сохраняется как BLOB-объект, который впоследствии можно восстановить. Если нужно разбирать данные, используйте обычный бинарный режим.
int len;
var data = new byte[10000];
// Экспорт table1 в массив данных
using (var inStream = conn.BeginRawBinaryCopy(
"COPY table1 TO STDOUT (FORMAT BINARY)"))
{
// Предполагаем, что данные влезут в 10000 байт
// В реальности их нужно читать блоками
len = inStream.Read(data, 0, data.Length);
}
// Импорт данных в table2
using (var outStream = conn.BeginRawBinaryCopy(
"COPY table2 FROM STDIN (FORMAT BINARY)"))
{
outStream.Write(data, 0, len);
}
Отмена
Операции импорта можно отменить в любой момент, освободив (dispose) NpgsqlBinaryImporter до вызова метода Complete(). Операции экспорта можно отменить, вызвав метод Cancel().
Источник: https://www.npgsql.org/doc/copy.html
👍16
День 2379. #ЗаметкиНаПолях
Алгоритмы Выбора Главного Узла в Распределённых БД. Начало
Распределённые системы БД нуждаются в координации для корректной работы. Когда несколько узлов реплицируют данные и обрабатывают запросы между регионами или зонами, определённый узел должен взять на себя управление операциями записи. Этот узел обычно называется лидером: единый узел, отвечающий за упорядочивание обновлений, фиксацию изменений и обеспечение согласованности системы даже в случае сбоя.
Выбор лидера существует для ответа на простой, но важный вопрос: какой узел в данный момент отвечает за управление? Процесс выбора должен выдерживать реальные нагрузки, включая сбои процессов, задержки в сети, разделы, перезапуски и непредсказуемую потерю сообщений. В случае сбоя главного узла система должна обнаружить его, согласовать замену и продолжить работу, не повреждая данные и не обрабатывая один и тот же запрос дважды. Это проблема отказоустойчивости и консенсуса, лежащая в основе проектирования распределённых БД.
Рассмотрим основные подходы к выбору лидера, каждый со своими предположениями, сильными сторонами и компромиссами.
1. Алгоритм забияки (Bully)
Основан на базовом правиле: узел с наивысшим идентификатором становится лидером. Если узел замечает, что текущий лидер не отвечает, он пытается взять на себя управление.
Каждому узлу присваивается уникальный числовой идентификатор. Чем он выше, тем выше «ранг» узла. Все узлы заранее знают идентификаторы друг друга, что важно для работы алгоритма.
Предположим, имеются узлы с идентификаторами от 1 до 5. Узел 5 в настоящее время является лидером.
Если №5 выходит из строя, и №3 замечает это (например, перестаёт получать сигналы), №3 инициирует выборы.
Вот как может работать такой процесс (см. диаграмму ниже):
A) №3 обнаруживает, что ведущий узел №5 вышел из строя.
№3 отправляет сообщения о выборах узлам 4 и 5.
B) №4 активен и отвечает «OK».
№3 отступает, увидев ответ от узла с более высоким идентификатором.
C) №4 отправляет сообщения о выборах №5.
D) Если №5 вышел из строя, №4 не получает ответа.
E) №4 становится ведущим и рассылает сообщение координатора всем узлам.
Процесс останавливается, когда все узнают, кто новый лидер.
Если узел с более низким ID инициирует выборы, а несколько узлов с более высоким ID активны, все они могут ответить сообщениями «OK» или «Я активен». Однако эти ответы не объявляют лидерство. Они лишь указывают: «Мой ID выше вашего. Вы не подходите для роли лидера. Я возьму на себя процесс выборов».
После отправки сообщения «OK» каждый из этих узлов с более высоким ID начинает выборы, отправляя сообщения о выборах узлам с ещё более высокими ID. Этот процесс каскадируется вверх по цепочке.
Предположения
- Все узлы знают идентификаторы друг друга.
- Узлы взаимодействуют по надёжным каналам связи.
- Отказ лидера может быть точно обнаружен.
Недостатки
- Коммуникационные расходы. Каждые выборы могут включать в себя поток сообщений между несколькими узлами. В большом кластере это быстро становится затратным.
- Алгоритм плохо переносит разделение. Если узел ошибочно предполагает, что лидер мёртв из-за сетевой задержки, он может начать ненужные выборы, что приведёт к путанице или дублированию сообщений о лидерстве.
- Проблема поэтапного восстановления. Если узлы возвращаются в сеть после неисправности, каждый новый узел с более высоким идентификатором может инициировать новые выборы. Система тратит больше времени на выбор лидеров, чем на саму работу.
Продолжение следует…
Источник: https://blog.bytebytego.com/p/top-leader-election-algorithms-in
Алгоритмы Выбора Главного Узла в Распределённых БД. Начало
Распределённые системы БД нуждаются в координации для корректной работы. Когда несколько узлов реплицируют данные и обрабатывают запросы между регионами или зонами, определённый узел должен взять на себя управление операциями записи. Этот узел обычно называется лидером: единый узел, отвечающий за упорядочивание обновлений, фиксацию изменений и обеспечение согласованности системы даже в случае сбоя.
Выбор лидера существует для ответа на простой, но важный вопрос: какой узел в данный момент отвечает за управление? Процесс выбора должен выдерживать реальные нагрузки, включая сбои процессов, задержки в сети, разделы, перезапуски и непредсказуемую потерю сообщений. В случае сбоя главного узла система должна обнаружить его, согласовать замену и продолжить работу, не повреждая данные и не обрабатывая один и тот же запрос дважды. Это проблема отказоустойчивости и консенсуса, лежащая в основе проектирования распределённых БД.
Рассмотрим основные подходы к выбору лидера, каждый со своими предположениями, сильными сторонами и компромиссами.
1. Алгоритм забияки (Bully)
Основан на базовом правиле: узел с наивысшим идентификатором становится лидером. Если узел замечает, что текущий лидер не отвечает, он пытается взять на себя управление.
Каждому узлу присваивается уникальный числовой идентификатор. Чем он выше, тем выше «ранг» узла. Все узлы заранее знают идентификаторы друг друга, что важно для работы алгоритма.
Предположим, имеются узлы с идентификаторами от 1 до 5. Узел 5 в настоящее время является лидером.
Если №5 выходит из строя, и №3 замечает это (например, перестаёт получать сигналы), №3 инициирует выборы.
Вот как может работать такой процесс (см. диаграмму ниже):
A) №3 обнаруживает, что ведущий узел №5 вышел из строя.
№3 отправляет сообщения о выборах узлам 4 и 5.
B) №4 активен и отвечает «OK».
№3 отступает, увидев ответ от узла с более высоким идентификатором.
C) №4 отправляет сообщения о выборах №5.
D) Если №5 вышел из строя, №4 не получает ответа.
E) №4 становится ведущим и рассылает сообщение координатора всем узлам.
Процесс останавливается, когда все узнают, кто новый лидер.
Если узел с более низким ID инициирует выборы, а несколько узлов с более высоким ID активны, все они могут ответить сообщениями «OK» или «Я активен». Однако эти ответы не объявляют лидерство. Они лишь указывают: «Мой ID выше вашего. Вы не подходите для роли лидера. Я возьму на себя процесс выборов».
После отправки сообщения «OK» каждый из этих узлов с более высоким ID начинает выборы, отправляя сообщения о выборах узлам с ещё более высокими ID. Этот процесс каскадируется вверх по цепочке.
Предположения
- Все узлы знают идентификаторы друг друга.
- Узлы взаимодействуют по надёжным каналам связи.
- Отказ лидера может быть точно обнаружен.
Недостатки
- Коммуникационные расходы. Каждые выборы могут включать в себя поток сообщений между несколькими узлами. В большом кластере это быстро становится затратным.
- Алгоритм плохо переносит разделение. Если узел ошибочно предполагает, что лидер мёртв из-за сетевой задержки, он может начать ненужные выборы, что приведёт к путанице или дублированию сообщений о лидерстве.
- Проблема поэтапного восстановления. Если узлы возвращаются в сеть после неисправности, каждый новый узел с более высоким идентификатором может инициировать новые выборы. Система тратит больше времени на выбор лидеров, чем на саму работу.
Продолжение следует…
Источник: https://blog.bytebytego.com/p/top-leader-election-algorithms-in
👍10
День 2380. #ЗаметкиНаПолях
Алгоритмы Выбора Главного Узла в Распределённых БД. Продолжение
1. Алгоритм Забияки
2. Кольцевой Алгоритм
Сообщение передается по кругу, и побеждает узел с наивысшим идентификатором. В отличие от алгоритма Забияки, здесь нет концепции прерывания или повышения приоритета. Каждый узел имеет шанс принять участие, но только один из них становится лидером.
Система предполагает логическую кольцевую топологию, где каждый узел знает о следующем. Сообщения передаются по кругу, всегда в одном направлении. Кроме того, каждый узел имеет уникальный числовой идентификатор.
Предположим, 5 узлов объединены в кольцо с идентификаторами: A(3), B(5), C(2), D(1), E(4), и обратно к узлу A (см. диаграмму ниже).
Если C обнаруживает отсутствие лидера, он начинает выборы, отправляя сообщение своему соседу (D). Сообщение содержит идентификатор 2. Вот что может произойти:
- D(1) получает идентификатор 2, сравнивает его со своим идентификатором (1) и пересылает 2 узлу E.
- E(4) видит, что 4>2, поэтому он заменяет содержимое сообщения на 4 и пересылает его (есть вариант алгоритма, в котором идентификатор не заменяется, а добавляется). Таким образом, сообщение может принять вид [2,4], но в данном примере мы будем использовать подход с заменой.
- A(3) видит, что 4>3, поэтому он сохраняет 4 и пересылает сообщение.
- B(5) снова заменяет его на 5 и пересылает сообщение.
- В конце концов, сообщение возвращается к узлу C, отправителю.
В этот момент узел C видит, что его сообщение вернулось с идентификатором 5, который является наивысшим в системе. Следовательно, он делает вывод, что узел B(5) должен быть лидером. Он рассылает сообщение координатора по кольцу, сообщая всем, что узел B теперь является лидером.
Преимущества
- Кольцо гарантирует участие каждого активного узла в выборах.
- Идентификатор с наивысшим значением естественным образом распространяется и заменяет идентификаторы с более низкими значениями по мере распространения сообщения.
- Инициатор определяет завершение цикла и принимает окончательное решение.
- Выбирается только один лидер, и два узла не могут одновременно претендовать на лидерство. Процесс гарантированно завершится, пока кольцо цело и все соединения работают.
Недостатки
- Предполагает наличие надёжной и упорядоченной доставки. Если сообщение потеряно или задержано, весь процесс выборов может остановиться.
- Разрывается в динамической топологии. Если узлы появляются и исчезают, кольцо необходимо перестраивать. Это медленно и подвержено ошибкам.
- Задержка выборов линейно растет с количеством узлов. Для возвращения к инициатору всегда требуется N переходов, даже если победитель очевиден с самого начала.
Продолжение следует…
Источник: https://blog.bytebytego.com/p/top-leader-election-algorithms-in
Алгоритмы Выбора Главного Узла в Распределённых БД. Продолжение
1. Алгоритм Забияки
2. Кольцевой Алгоритм
Сообщение передается по кругу, и побеждает узел с наивысшим идентификатором. В отличие от алгоритма Забияки, здесь нет концепции прерывания или повышения приоритета. Каждый узел имеет шанс принять участие, но только один из них становится лидером.
Система предполагает логическую кольцевую топологию, где каждый узел знает о следующем. Сообщения передаются по кругу, всегда в одном направлении. Кроме того, каждый узел имеет уникальный числовой идентификатор.
Предположим, 5 узлов объединены в кольцо с идентификаторами: A(3), B(5), C(2), D(1), E(4), и обратно к узлу A (см. диаграмму ниже).
Если C обнаруживает отсутствие лидера, он начинает выборы, отправляя сообщение своему соседу (D). Сообщение содержит идентификатор 2. Вот что может произойти:
- D(1) получает идентификатор 2, сравнивает его со своим идентификатором (1) и пересылает 2 узлу E.
- E(4) видит, что 4>2, поэтому он заменяет содержимое сообщения на 4 и пересылает его (есть вариант алгоритма, в котором идентификатор не заменяется, а добавляется). Таким образом, сообщение может принять вид [2,4], но в данном примере мы будем использовать подход с заменой.
- A(3) видит, что 4>3, поэтому он сохраняет 4 и пересылает сообщение.
- B(5) снова заменяет его на 5 и пересылает сообщение.
- В конце концов, сообщение возвращается к узлу C, отправителю.
В этот момент узел C видит, что его сообщение вернулось с идентификатором 5, который является наивысшим в системе. Следовательно, он делает вывод, что узел B(5) должен быть лидером. Он рассылает сообщение координатора по кольцу, сообщая всем, что узел B теперь является лидером.
Преимущества
- Кольцо гарантирует участие каждого активного узла в выборах.
- Идентификатор с наивысшим значением естественным образом распространяется и заменяет идентификаторы с более низкими значениями по мере распространения сообщения.
- Инициатор определяет завершение цикла и принимает окончательное решение.
- Выбирается только один лидер, и два узла не могут одновременно претендовать на лидерство. Процесс гарантированно завершится, пока кольцо цело и все соединения работают.
Недостатки
- Предполагает наличие надёжной и упорядоченной доставки. Если сообщение потеряно или задержано, весь процесс выборов может остановиться.
- Разрывается в динамической топологии. Если узлы появляются и исчезают, кольцо необходимо перестраивать. Это медленно и подвержено ошибкам.
- Задержка выборов линейно растет с количеством узлов. Для возвращения к инициатору всегда требуется N переходов, даже если победитель очевиден с самого начала.
Продолжение следует…
Источник: https://blog.bytebytego.com/p/top-leader-election-algorithms-in
👍8
День 2381. #ЗаметкиНаПолях
Алгоритмы Выбора Главного Узла в Распределённых БД. Продолжение
1. Алгоритм Забияки
2. Кольцевой Алгоритм
3. Алгоритм Paxos
Позволяет любому узлу попытаться предложить значение, а затем использует кворумное голосование для определения того, какое предложение будет принято. Лидерство переходит к узлу, который последовательно добивается успеха в этой игре.
Это мощный протокол, но его сложно реализовать правильно. Paxos безопасен даже в условиях ненадёжных сетей, задержек сообщений и частичных сбоев. Но требует точной логики, постоянного хранения данных и глубокого понимания динамики кворума.
По сути, Paxos пытается решить проблему консенсуса, то есть как заставить группу узлов согласовать единое значение, даже если некоторые из них выходят из строя или сообщения приходят в неправильном порядке. Это значение может быть чем угодно, например, записью журнала, параметром конфигурации или идентификатором ведущего узла.
Paxos разделяет процесс на два этапа:
- Подготовка/Обещание
- Предложение/Принятие
Каждый узел играет одну или несколько ролей: предлагающего, принимающего и иногда обучающегося (см. диаграмму ниже).
1. Подготовка и обещание
Любой узел может выступать в роли предлагающего. Для начала он генерирует номер предложения, который представляет собой монотонно возрастающий идентификатор.
- Предлагающий отправляет сообщение «Prepare(n)» большинству принимающих, прося их пообещать не принимать предложения с номерами меньше n.
- Когда принимающий получает сообщение «Prepare(n)», он отвечает «Promise(n)», если он ещё не обещал что-то более высокое. Вместе с обещанием он включает предложение с уже принятым им значением (если оно есть).
Этот шаг помогает подавить конфликтующие или устаревшие предложения, гарантируя, что будущие предложения будут либо более новыми, либо основаны на ранее принятых.
Вот пример:
- Предлагающий A отправляет Prepare(100)
- Принимающие 1, 2 и 3 получают его и отвечают:
№1: Promise(100), ранее принятого значения нет
№2: Promise(100), ранее принятое значение (лидер = №4)
№3: Promise(100), ранее принятого значения нет
Теперь узел A знает, что он должен сохранить «№4» в качестве кандидата на лидера, даже если изначально он его не предлагал.
2. Предложение и принятие
Предлагающий выбирает значение:
- Если какой-либо принимающий ответил ранее принятым значением, он должен повторно предложить это значение.
- Если ранее ни одно из них не было принято, он может выбрать своё собственное (например, «выбрать A лидером»).
Он отправляет сообщение «Accept(n, значение)» тому же большинству. Если принимающие за это время не обещали ничего более высокого, они отвечают «Accepted(n, значение)». Как только кворум принимает значение, оно выбирается.
Multi-Paxos
В базовом протоколе Paxos каждое значение требует нового раунда шагов Prepare/Promise и Accept/Accepted. Это затратно и неэффективно, если значения предлагаются часто.
Multi-Paxos оптимизирует этот процесс:
- Выбирается определённый предлагающий, который будет выступать в качестве стабильного лидера.
- Лидеру предоставляется возможность пропускать фазу 1 для последующих значений, повторно используя лидерство до тех пор, пока его никто не оспорит.
Это создаёт форму стабильного лидерства. Лидер не избирается посредством отдельного механизма, а становится единственным узлом, постоянно успешно предлагающим значения.
Почему Paxos сложен?
- Номера предложений должны быть уникальными и упорядоченными глобально.
- Узлы должны сохранять состояние (обещания, принятые значения) при перезапусках.
- Восстановление после частичных сбоев или передачи управления требует тщательной координации.
Продолжение следует…
Источник: https://blog.bytebytego.com/p/top-leader-election-algorithms-in
Алгоритмы Выбора Главного Узла в Распределённых БД. Продолжение
1. Алгоритм Забияки
2. Кольцевой Алгоритм
3. Алгоритм Paxos
Позволяет любому узлу попытаться предложить значение, а затем использует кворумное голосование для определения того, какое предложение будет принято. Лидерство переходит к узлу, который последовательно добивается успеха в этой игре.
Это мощный протокол, но его сложно реализовать правильно. Paxos безопасен даже в условиях ненадёжных сетей, задержек сообщений и частичных сбоев. Но требует точной логики, постоянного хранения данных и глубокого понимания динамики кворума.
По сути, Paxos пытается решить проблему консенсуса, то есть как заставить группу узлов согласовать единое значение, даже если некоторые из них выходят из строя или сообщения приходят в неправильном порядке. Это значение может быть чем угодно, например, записью журнала, параметром конфигурации или идентификатором ведущего узла.
Paxos разделяет процесс на два этапа:
- Подготовка/Обещание
- Предложение/Принятие
Каждый узел играет одну или несколько ролей: предлагающего, принимающего и иногда обучающегося (см. диаграмму ниже).
1. Подготовка и обещание
Любой узел может выступать в роли предлагающего. Для начала он генерирует номер предложения, который представляет собой монотонно возрастающий идентификатор.
- Предлагающий отправляет сообщение «Prepare(n)» большинству принимающих, прося их пообещать не принимать предложения с номерами меньше n.
- Когда принимающий получает сообщение «Prepare(n)», он отвечает «Promise(n)», если он ещё не обещал что-то более высокое. Вместе с обещанием он включает предложение с уже принятым им значением (если оно есть).
Этот шаг помогает подавить конфликтующие или устаревшие предложения, гарантируя, что будущие предложения будут либо более новыми, либо основаны на ранее принятых.
Вот пример:
- Предлагающий A отправляет Prepare(100)
- Принимающие 1, 2 и 3 получают его и отвечают:
№1: Promise(100), ранее принятого значения нет
№2: Promise(100), ранее принятое значение (лидер = №4)
№3: Promise(100), ранее принятого значения нет
Теперь узел A знает, что он должен сохранить «№4» в качестве кандидата на лидера, даже если изначально он его не предлагал.
2. Предложение и принятие
Предлагающий выбирает значение:
- Если какой-либо принимающий ответил ранее принятым значением, он должен повторно предложить это значение.
- Если ранее ни одно из них не было принято, он может выбрать своё собственное (например, «выбрать A лидером»).
Он отправляет сообщение «Accept(n, значение)» тому же большинству. Если принимающие за это время не обещали ничего более высокого, они отвечают «Accepted(n, значение)». Как только кворум принимает значение, оно выбирается.
Multi-Paxos
В базовом протоколе Paxos каждое значение требует нового раунда шагов Prepare/Promise и Accept/Accepted. Это затратно и неэффективно, если значения предлагаются часто.
Multi-Paxos оптимизирует этот процесс:
- Выбирается определённый предлагающий, который будет выступать в качестве стабильного лидера.
- Лидеру предоставляется возможность пропускать фазу 1 для последующих значений, повторно используя лидерство до тех пор, пока его никто не оспорит.
Это создаёт форму стабильного лидерства. Лидер не избирается посредством отдельного механизма, а становится единственным узлом, постоянно успешно предлагающим значения.
Почему Paxos сложен?
- Номера предложений должны быть уникальными и упорядоченными глобально.
- Узлы должны сохранять состояние (обещания, принятые значения) при перезапусках.
- Восстановление после частичных сбоев или передачи управления требует тщательной координации.
Продолжение следует…
Источник: https://blog.bytebytego.com/p/top-leader-election-algorithms-in
👍3
День 2382. #ЗаметкиНаПолях
Алгоритмы Выбора Главного Узла в Распределённых БД. Продолжение
1. Алгоритм Забияки
2. Кольцевой Алгоритм
3. Алгоритм Paxos
4. Алгоритм Raft
Raft существует, потому что Paxos, несмотря на свою мощь, сложен в реализации и ещё сложнее в понимании. Raft предоставляет те же гарантии безопасности, что и Paxos, например, отсутствие двух лидеров одновременно, согласование журнала и отслеживание прогресса при наличии кворума. Однако архитектуру проще отслеживать, отлаживать и развёртывать.
Каждый узел Raft находится в одном из трёх состояний:
- Ведомый (Follower) - пассивный узел, слушает лидера и отвечает на запросы.
- Кандидат (Candidate) - пытается стать лидером при отсутствии тактовых импульсов.
- Лидер (Leader) - активный узел, который обрабатывает все клиентские запросы и репликацию.
Узлы начинают работу как ведомые. Если они слишком долго не получают ответ от лидера (по тактовым импульсам), они считают лидера мёртвым и запускают выборы (см. диаграмму ниже).
Raft предотвращает конфликты выборов, используя рандомизированные таймауты. Каждый ведомый начинает обратный отсчёт со случайным значением, например, от 150 до 300 миллисекунд. Если до истечения таймера он не получает ответа от лидера, он становится кандидатом и начинает выборы.
Кандидат:
- Увеличивает свой срок полномочий (своего рода счётчик эпох).
- Голосует за себя.
- Отправляет RPC-запросы «RequestVote» всем остальным узлам.
- Другие узлы будут голосовать за кандидата только в том случае, если:
они не голосовали в текущем сроке полномочий и журнал кандидата как минимум не менее актуален, чем их собственный.
- Если кандидат получает голоса большинства, он становится новым лидером и начинает отправлять тактовые импульсы для поддержания своего статуса. Если ни один из кандидатов не побеждает (например, голоса разделились), все ждут и повторяют попытки с новыми таймаутами. Такой рандомизированный подход гарантирует, что один из узлов в итоге опередит остальных.
Raft использует сроки полномочий (term) для отслеживания эпох лидерства. Каждая запись в журнале привязана к сроку полномочий, в котором она была создана. Это позволяет легко обнаруживать и отклонять устаревших или конфликтующих лидеров. Перед голосованием узлы сравнивают журналы. Кандидат с устаревшим журналом будет отклонён, даже если его запрос первым достигнет других узлов. Это гарантирует, что лидером может стать только узел с самой актуальной версией журнала. Затем лидеры реплицируют новые записи журнала на ведомых с помощью RPC-вызовов «AppendEntries». После того, как кворум подтвердил запись, она считается подтверждённой.
Raft решает проблему разделения власти, применяя правила кворума:
- Лидер должен иметь поддержку большинства узлов.
- Два лидера не могут быть активны в одном и том же сроке.
- Ведомый принимает запросы только от лидера текущего срока.
- Если устаревший лидер пытается действовать после разделения сети, он немедленно отклоняется ведомыми с более новым сроком.
Эта чёткая структура позволяет избежать неоднозначности, характерной для таких протоколов, как базовый Paxos, где одновременное выдвижение предложений может замедлять работу узлов или приводить системы в состояние неопределённости.
Окончание следует…
Источник: https://blog.bytebytego.com/p/top-leader-election-algorithms-in
Алгоритмы Выбора Главного Узла в Распределённых БД. Продолжение
1. Алгоритм Забияки
2. Кольцевой Алгоритм
3. Алгоритм Paxos
4. Алгоритм Raft
Raft существует, потому что Paxos, несмотря на свою мощь, сложен в реализации и ещё сложнее в понимании. Raft предоставляет те же гарантии безопасности, что и Paxos, например, отсутствие двух лидеров одновременно, согласование журнала и отслеживание прогресса при наличии кворума. Однако архитектуру проще отслеживать, отлаживать и развёртывать.
Каждый узел Raft находится в одном из трёх состояний:
- Ведомый (Follower) - пассивный узел, слушает лидера и отвечает на запросы.
- Кандидат (Candidate) - пытается стать лидером при отсутствии тактовых импульсов.
- Лидер (Leader) - активный узел, который обрабатывает все клиентские запросы и репликацию.
Узлы начинают работу как ведомые. Если они слишком долго не получают ответ от лидера (по тактовым импульсам), они считают лидера мёртвым и запускают выборы (см. диаграмму ниже).
Raft предотвращает конфликты выборов, используя рандомизированные таймауты. Каждый ведомый начинает обратный отсчёт со случайным значением, например, от 150 до 300 миллисекунд. Если до истечения таймера он не получает ответа от лидера, он становится кандидатом и начинает выборы.
Кандидат:
- Увеличивает свой срок полномочий (своего рода счётчик эпох).
- Голосует за себя.
- Отправляет RPC-запросы «RequestVote» всем остальным узлам.
- Другие узлы будут голосовать за кандидата только в том случае, если:
они не голосовали в текущем сроке полномочий и журнал кандидата как минимум не менее актуален, чем их собственный.
- Если кандидат получает голоса большинства, он становится новым лидером и начинает отправлять тактовые импульсы для поддержания своего статуса. Если ни один из кандидатов не побеждает (например, голоса разделились), все ждут и повторяют попытки с новыми таймаутами. Такой рандомизированный подход гарантирует, что один из узлов в итоге опередит остальных.
Raft использует сроки полномочий (term) для отслеживания эпох лидерства. Каждая запись в журнале привязана к сроку полномочий, в котором она была создана. Это позволяет легко обнаруживать и отклонять устаревших или конфликтующих лидеров. Перед голосованием узлы сравнивают журналы. Кандидат с устаревшим журналом будет отклонён, даже если его запрос первым достигнет других узлов. Это гарантирует, что лидером может стать только узел с самой актуальной версией журнала. Затем лидеры реплицируют новые записи журнала на ведомых с помощью RPC-вызовов «AppendEntries». После того, как кворум подтвердил запись, она считается подтверждённой.
Raft решает проблему разделения власти, применяя правила кворума:
- Лидер должен иметь поддержку большинства узлов.
- Два лидера не могут быть активны в одном и том же сроке.
- Ведомый принимает запросы только от лидера текущего срока.
- Если устаревший лидер пытается действовать после разделения сети, он немедленно отклоняется ведомыми с более новым сроком.
Эта чёткая структура позволяет избежать неоднозначности, характерной для таких протоколов, как базовый Paxos, где одновременное выдвижение предложений может замедлять работу узлов или приводить системы в состояние неопределённости.
Окончание следует…
Источник: https://blog.bytebytego.com/p/top-leader-election-algorithms-in
👍4
День 2383. #ЗаметкиНаПолях
Алгоритмы Выбора Главного Узла в Распределённых БД. Окончание
1. Алгоритм Забияки
2. Кольцевой Алгоритм
3. Алгоритм Paxos
4. Алгоритм Raft
5. Zookeeper и Zab
ZooKeeper не является БД. Он не хранит записи пользователей, индексы запросов и не реплицирует записи журнала. Вместо этого он играет важнейшую роль в распределённой экосистеме: координирует работу. Когда системам требуется выбрать брокера, управлять отказоустойчивостью лидера или синхронизировать конфигурацию, ZooKeeper — незаменимый сервис.
Внутренне ZooKeeper использует протокол Zab (ZooKeeper Atomic Broadcast). Zab обрабатывает как выбор лидера, так и репликацию состояния, гарантируя безопасность, согласованность и отказоустойчивость даже метаданных координации (см. диаграмму ниже).
Zab обеспечивает надёжность выборов лидера в ZooKeeper. Прежде чем новый лидер сможет начать обработку запросов на запись, он должен синхронизироваться с кворумом ведомых, чтобы убедиться, что у него самое последнее зафиксированное состояние.
Каждая транзакция в ZooKeeper помечается zxid (идентификатором транзакции ZooKeeper), который объединяет эпоху лидера и индекс журнала. Это позволяет узлам определять, кто имеет наиболее актуальную картину мира.
Во время выборов лидера в Zab:
- Предпочтение отдаётся кандидату с наиболее актуальным ZXID.
- Новый лидер должен пройти фазу синхронизации состояния с кворумом, прежде чем сможет фиксировать новые транзакции.
- Если синхронизация не удаётся, выборы завершаются неудачей, и повторяются.
Итого
Каждый алгоритм выборов лидера решает одну и ту же фундаментальную задачу: выбор одного надёжного лидера в распределённой системе. Но они подходят к этому с разными допущениями, компромиссами и эксплуатационными характеристиками.
- Алгоритмы Забияки и Кольцевой используют детерминированные правила, основанные на идентификаторах узлов. Paxos, Raft и Zab используют принятие решений на основе кворума.
- Для обеспечения отказоустойчивости алгоритм Забияки предполагает надёжные соединения и точное обнаружение сбоев. Узел, ложно помеченный как мёртвый, может привести к появлению нескольких лидеров или частым перевыборам. Кольцевой алгоритм останавливается, если любой узел в кольце выходит из строя без предупреждения. В отличие от этого, Paxos, Raft и Zab допускают частичные сбои и продолжают принимать безопасные решения.
- С точки зрения производительности, Raft и ZooKeeper/Zab предсказуемо справляются с перевыборами. Алгоритмы Забияки и Кольцевой неэффективны в средах с высокой текучестью. Paxos, особенно в базовой версии, может испытывать трудности при наличии нескольких предлагающих узлов и нечётком лидерстве, если не использовать такие усовершенствования, как Multi-Paxos.
- Алгоритмы Забияки и Кольцевой просты в реализации. Raft требует больше усилий. Paxos довольно сложно настроить правильно. ZooKeeper/Zab скрывает сложность за API, но лежащие в его основе механизмы нетривиальны.
Источник: https://blog.bytebytego.com/p/top-leader-election-algorithms-in
Алгоритмы Выбора Главного Узла в Распределённых БД. Окончание
1. Алгоритм Забияки
2. Кольцевой Алгоритм
3. Алгоритм Paxos
4. Алгоритм Raft
5. Zookeeper и Zab
ZooKeeper не является БД. Он не хранит записи пользователей, индексы запросов и не реплицирует записи журнала. Вместо этого он играет важнейшую роль в распределённой экосистеме: координирует работу. Когда системам требуется выбрать брокера, управлять отказоустойчивостью лидера или синхронизировать конфигурацию, ZooKeeper — незаменимый сервис.
Внутренне ZooKeeper использует протокол Zab (ZooKeeper Atomic Broadcast). Zab обрабатывает как выбор лидера, так и репликацию состояния, гарантируя безопасность, согласованность и отказоустойчивость даже метаданных координации (см. диаграмму ниже).
Zab обеспечивает надёжность выборов лидера в ZooKeeper. Прежде чем новый лидер сможет начать обработку запросов на запись, он должен синхронизироваться с кворумом ведомых, чтобы убедиться, что у него самое последнее зафиксированное состояние.
Каждая транзакция в ZooKeeper помечается zxid (идентификатором транзакции ZooKeeper), который объединяет эпоху лидера и индекс журнала. Это позволяет узлам определять, кто имеет наиболее актуальную картину мира.
Во время выборов лидера в Zab:
- Предпочтение отдаётся кандидату с наиболее актуальным ZXID.
- Новый лидер должен пройти фазу синхронизации состояния с кворумом, прежде чем сможет фиксировать новые транзакции.
- Если синхронизация не удаётся, выборы завершаются неудачей, и повторяются.
Итого
Каждый алгоритм выборов лидера решает одну и ту же фундаментальную задачу: выбор одного надёжного лидера в распределённой системе. Но они подходят к этому с разными допущениями, компромиссами и эксплуатационными характеристиками.
- Алгоритмы Забияки и Кольцевой используют детерминированные правила, основанные на идентификаторах узлов. Paxos, Raft и Zab используют принятие решений на основе кворума.
- Для обеспечения отказоустойчивости алгоритм Забияки предполагает надёжные соединения и точное обнаружение сбоев. Узел, ложно помеченный как мёртвый, может привести к появлению нескольких лидеров или частым перевыборам. Кольцевой алгоритм останавливается, если любой узел в кольце выходит из строя без предупреждения. В отличие от этого, Paxos, Raft и Zab допускают частичные сбои и продолжают принимать безопасные решения.
- С точки зрения производительности, Raft и ZooKeeper/Zab предсказуемо справляются с перевыборами. Алгоритмы Забияки и Кольцевой неэффективны в средах с высокой текучестью. Paxos, особенно в базовой версии, может испытывать трудности при наличии нескольких предлагающих узлов и нечётком лидерстве, если не использовать такие усовершенствования, как Multi-Paxos.
- Алгоритмы Забияки и Кольцевой просты в реализации. Raft требует больше усилий. Paxos довольно сложно настроить правильно. ZooKeeper/Zab скрывает сложность за API, но лежащие в его основе механизмы нетривиальны.
Источник: https://blog.bytebytego.com/p/top-leader-election-algorithms-in
👍5
День 2384. #TipsAndTricks
Как Продолжить Выполнение Процесса После Завершения Задания GitHub Action
После завершения задания GitHub Actions обработчик завершает все запущенные им дочерние процессы. Он идентифицирует эти процессы, проверяя переменную окружения RUNNER_TRACKING_ID. Любой процесс, где эта переменная установлена переменной считается дочерним процессом обработчика и будет остановлен.
Чтобы процесс продолжил работу после завершения задания, запустите его без переменной окружения RUNNER_TRACKING_ID:
Либо:
Примечание: В обработчиках, размещенных на GitHub, это решение не поможет, поскольку вся виртуальная машина удаляется после завершения задания.
Источник: https://www.meziantou.net/how-to-keep-processes-running-after-a-github-action-job-ends.htm
Как Продолжить Выполнение Процесса После Завершения Задания GitHub Action
После завершения задания GitHub Actions обработчик завершает все запущенные им дочерние процессы. Он идентифицирует эти процессы, проверяя переменную окружения RUNNER_TRACKING_ID. Любой процесс, где эта переменная установлена переменной считается дочерним процессом обработчика и будет остановлен.
Чтобы процесс продолжил работу после завершения задания, запустите его без переменной окружения RUNNER_TRACKING_ID:
var psi = new ProcessStartInfo("sample_app");
psi.EnvironmentVariables.Remove("RUNNER_TRACKING_ID");
Process.Start(psi);Либо:
# Вариант 1
$env:RUNNER_TRACKING_ID = $null
# Вариант 2
$psi = New-Object System.Diagnostics.ProcessStartInfo "sample_app"
$psi.EnvironmentVariables.Remove("RUNNER_TRACKING_ID")
[System.Diagnostics.Process]::Start($psi)
Примечание: В обработчиках, размещенных на GitHub, это решение не поможет, поскольку вся виртуальная машина удаляется после завершения задания.
Источник: https://www.meziantou.net/how-to-keep-processes-running-after-a-github-action-job-ends.htm
👍2
День 2385. #TipsAndTricks
5 Современных Возможностей C#, Которые Улучшат Ваш Код
C# стал гораздо более выразительным и мощным языком, чем был ещё несколько лет назад. Вот 5 новинок языка, которые отличают инженеров, просто «знающих C#», от тех, кто использует его на полную.
1. Структуры только для чтения для критически важных для производительности типов-значений
Обычная структура копируется без необходимости, что приводит к скрытым потерям производительности. Отметив её как только для чтения, вы сообщаете компилятору, что её поля никогда не изменятся, что позволяет среде выполнения не создавать защитные копии.
Раньше:
Сейчас:
Для высокопроизводительного кода, такого как графика, игры или API с большим количеством математических вычислений, это небольшое ключевое слово может означать значительную экономию.
2. Ключевое слово scoped для предотвращения использования ref-переменных вне области видимости
C# позволяет использовать ref-переменные для повышения производительности, но распространённой ошибкой является случайное их использование вне области видимости (возвращение ссылки на что-то, что больше не является безопасным). scoped гарантирует во время компиляции, что ссылка не проживёт дольше положенного.
Без scoped:
scoped:
3. Обязательные свойства для принудительной инициализации объекта
Сколько раз вы создавали объект и забывали установить одно из его критически важных свойств? При использовании обязательных свойств компилятор принуждает инициализировать объект.
Раньше:
Сейчас:
4. Возврат типа ref readonly, чтобы избежать защитного копирования
При возврате больших структур возврат по значению часто приводит к ненужному копированию. Возврат типа ref readonly даёт вызывающим функциям ссылку на объект, который они могут читать, но не могут изменять.
Без ref readonly:
С ref readonly:
Это особенно актуально в библиотеках, критичных к производительности, где структуры неизменяемы, но копирование требует больших затрат.
5. Статические абстрактные члены в интерфейсах для обобщённой математики
Это кажется узкоспециализированной функцией, но открывает доступ к настоящей обобщённой математике без рефлексии и упаковки. До появления этой функции нельзя было писать обобщённые алгоритмы, работающие с числовыми типами:
Благодаря статическим абстрактным членам, INumber<T> определяет операторы и константы обобщённо, обеспечивая чистые, независимые от типов математические библиотеки.
Итого
Современный C# — это не просто новый синтаксис; это достижение ясности, безопасности и производительности, которых просто не могли предложить старые версии. Начните внедрять эти функции в свой код, и вы не только начнёте писать лучшее ПО, но и по-новому взглянете на возможности языка.
Источник: https://blog.stackademic.com/5-modern-c-features-that-will-make-your-code-feel-like-magic-d1ef6a374d13
5 Современных Возможностей C#, Которые Улучшат Ваш Код
C# стал гораздо более выразительным и мощным языком, чем был ещё несколько лет назад. Вот 5 новинок языка, которые отличают инженеров, просто «знающих C#», от тех, кто использует его на полную.
1. Структуры только для чтения для критически важных для производительности типов-значений
Обычная структура копируется без необходимости, что приводит к скрытым потерям производительности. Отметив её как только для чтения, вы сообщаете компилятору, что её поля никогда не изменятся, что позволяет среде выполнения не создавать защитные копии.
Раньше:
public struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
}
Сейчас:
public readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
}
Для высокопроизводительного кода, такого как графика, игры или API с большим количеством математических вычислений, это небольшое ключевое слово может означать значительную экономию.
2. Ключевое слово scoped для предотвращения использования ref-переменных вне области видимости
C# позволяет использовать ref-переменные для повышения производительности, но распространённой ошибкой является случайное их использование вне области видимости (возвращение ссылки на что-то, что больше не является безопасным). scoped гарантирует во время компиляции, что ссылка не проживёт дольше положенного.
Без scoped:
ref int Dangerous(ref int number)
{
// Может быть использовано извне и привести к проблемам
return ref number;
}
scoped:
ref int Safe(scoped ref int number)
{
// Компилятор проверяет, что number может
// использоваться только внутри метода
// и выдаёт ошибку компиляции
return ref number;
}
3. Обязательные свойства для принудительной инициализации объекта
Сколько раз вы создавали объект и забывали установить одно из его критически важных свойств? При использовании обязательных свойств компилятор принуждает инициализировать объект.
Раньше:
public class User
{
public string Name { get; set; }
public string Email { get; set; }
}
// Можно забыть инициализировать свойства
var user = new User { Name = "John" };
Сейчас:
public class User
{
public required string Name { get; init; }
public required string Email { get; init; }
}
// Компилятор проверяет полноту инициализации
var user = new User {
Name = "John", Email = "john@mail.com" };
4. Возврат типа ref readonly, чтобы избежать защитного копирования
При возврате больших структур возврат по значению часто приводит к ненужному копированию. Возврат типа ref readonly даёт вызывающим функциям ссылку на объект, который они могут читать, но не могут изменять.
Без ref readonly:
public BigStruct GetData() =>
_bigStruct; // Копирует структуру
С ref readonly:
public ref readonly BigStruct GetData() =>
ref _bigStruct; // Нет копирования
Это особенно актуально в библиотеках, критичных к производительности, где структуры неизменяемы, но копирование требует больших затрат.
5. Статические абстрактные члены в интерфейсах для обобщённой математики
Это кажется узкоспециализированной функцией, но открывает доступ к настоящей обобщённой математике без рефлексии и упаковки. До появления этой функции нельзя было писать обобщённые алгоритмы, работающие с числовыми типами:
T Add<T>(T a, T b) where T : INumber<T>
=> a + b;
Благодаря статическим абстрактным членам, INumber<T> определяет операторы и константы обобщённо, обеспечивая чистые, независимые от типов математические библиотеки.
Итого
Современный C# — это не просто новый синтаксис; это достижение ясности, безопасности и производительности, которых просто не могли предложить старые версии. Начните внедрять эти функции в свой код, и вы не только начнёте писать лучшее ПО, но и по-новому взглянете на возможности языка.
Источник: https://blog.stackademic.com/5-modern-c-features-that-will-make-your-code-feel-like-magic-d1ef6a374d13
👍30
День 2386. #ЗаметкиНаПолях
Неожиданная Несогласованность в Записях
На днях Джон Скит пытался найти ошибку в своём коде, и она оказалась следствием его непонимания принципов работы записей в C#. Как показывает вчерашний опрос, он не единственный, кто ожидал, что они будут работать именно так.
Когда записи появились в C#, одновременно появился и оператор «обратимого изменения» with. Идея заключается в том, что типы записей неизменяемы, но вы можете легко и эффективно создать новый экземпляр с теми же данными, что и существующий экземпляр, но с другими значениями некоторых свойств:
Это не изменяет данные первоначальной записи, т.е. entry.Score останется 5000.
Проблема
В качестве очень простого (и весьма надуманного) примера можно создать запись, которая определяет, является ли значение чётным или нечётным при инициализации:
На первый взгляд, всё нормально:
Но если мы изменим код на использование with:
Джон (и не только он) всегда предполагал, что оператор with вызывает конструктор с новыми значениями. На самом деле это не так. Оператор with выше преобразуется в примерно такой код:
Метод <Clone>$ (по крайней мере, в этом случае) вызывает сгенерированный конструктор копирования (Number(Number)), который копирует как Value, так и резервное поле для Even (т.е. значение Even). Всё это документировано, но пока компилятор не выдаёт никаких предупреждений о возможных несоответствиях, которые это может вызвать.
Конечно, вычисляемое свойство работает правильно. Значение вычисляется каждый раз при обращении к нему:
Что делать?
Пока просто знать об этом и жить дальше. Легко сказать, что можно просто использовать вычисляемые свойства. Проблема, если кто-то, не знающий об этом, добавит/изменит свойство на вычисляемое при инициализации. Либо если запись предоставлена в какой-то внешней библиотеке (не заглядывая в исходный код библиотеки, нет возможности определить какое свойство там использовано). Сложно представить, что это поведение будет изменено в языке. Но Джон попросил Майкрософт хотя бы добавить предупреждение в таких случаях, а также написал собственный анализатор.
Источник: https://codeblog.jonskeet.uk/2025/07/19/unexpected-inconsistency-in-records/
Неожиданная Несогласованность в Записях
На днях Джон Скит пытался найти ошибку в своём коде, и она оказалась следствием его непонимания принципов работы записей в C#. Как показывает вчерашний опрос, он не единственный, кто ожидал, что они будут работать именно так.
Когда записи появились в C#, одновременно появился и оператор «обратимого изменения» with. Идея заключается в том, что типы записей неизменяемы, но вы можете легко и эффективно создать новый экземпляр с теми же данными, что и существующий экземпляр, но с другими значениями некоторых свойств:
public record HighScoreEntry(
string PlayerName, int Score, int Level);
HighScoreEntry entry = new("Jon", 5000, 50);
var updatedEntry =
entry with { Score = 6000, Level = 55 };
Это не изменяет данные первоначальной записи, т.е. entry.Score останется 5000.
Проблема
В качестве очень простого (и весьма надуманного) примера можно создать запись, которая определяет, является ли значение чётным или нечётным при инициализации:
public record Number(int Value)
{
public bool Even { get; } =
(Value & 1) == 0;
}
На первый взгляд, всё нормально:
var n2 = new Number(2);
var n3 = new Number(3);
Console.WriteLine(n2);
// Number { Value = 2, Even = True }
Console.WriteLine(n3);
// Number { Value = 3, Even = False }
Но если мы изменим код на использование with:
var n3 = n2 with { Value = 3 };
Console.WriteLine(n3);
// Number { Value = 3, Even = True }Джон (и не только он) всегда предполагал, что оператор with вызывает конструктор с новыми значениями. На самом деле это не так. Оператор with выше преобразуется в примерно такой код:
var n3 = n2.<Clone>$();
n3.Value = 3;
Метод <Clone>$ (по крайней мере, в этом случае) вызывает сгенерированный конструктор копирования (Number(Number)), который копирует как Value, так и резервное поле для Even (т.е. значение Even). Всё это документировано, но пока компилятор не выдаёт никаких предупреждений о возможных несоответствиях, которые это может вызвать.
Конечно, вычисляемое свойство работает правильно. Значение вычисляется каждый раз при обращении к нему:
public record Number(int Value)
{
public bool Even => (Value & 1) == 0;
}
Что делать?
Пока просто знать об этом и жить дальше. Легко сказать, что можно просто использовать вычисляемые свойства. Проблема, если кто-то, не знающий об этом, добавит/изменит свойство на вычисляемое при инициализации. Либо если запись предоставлена в какой-то внешней библиотеке (не заглядывая в исходный код библиотеки, нет возможности определить какое свойство там использовано). Сложно представить, что это поведение будет изменено в языке. Но Джон попросил Майкрософт хотя бы добавить предупреждение в таких случаях, а также написал собственный анализатор.
Источник: https://codeblog.jonskeet.uk/2025/07/19/unexpected-inconsistency-in-records/
👍31👎2
День 2387. #Оффтоп
Давно не рекомендовал вам видео. А тут вчера у Дудя вышло прекрасное интервью с Андреем Дороничевым. Это своего рода сиквел популярного фильма Дудя про Кремниевую долину. Андрей с тех пор ушёл из гугла и создал стартап, где использует ИИ для решения разных больших задач (в данный момент – лекарство от рака). В интервью не только про него самого, но и 100500 "тупых вопросов" про ИИ (что он уже может и сможет в будущем, под угрозой ли наши профессии, будет ли восстание машин и т.п.), а также про личностный рост и про IT сферу вообще.
Мне очень понравилось, поэтому делюсь. Выделите 2 часа 40 минут в своём графике. Это интересно.
https://youtu.be/1SLvIof4-Zw
Давно не рекомендовал вам видео. А тут вчера у Дудя вышло прекрасное интервью с Андреем Дороничевым. Это своего рода сиквел популярного фильма Дудя про Кремниевую долину. Андрей с тех пор ушёл из гугла и создал стартап, где использует ИИ для решения разных больших задач (в данный момент – лекарство от рака). В интервью не только про него самого, но и 100500 "тупых вопросов" про ИИ (что он уже может и сможет в будущем, под угрозой ли наши профессии, будет ли восстание машин и т.п.), а также про личностный рост и про IT сферу вообще.
Мне очень понравилось, поэтому делюсь. Выделите 2 часа 40 минут в своём графике. Это интересно.
https://youtu.be/1SLvIof4-Zw
YouTube
Чипы Маска, восстание машин и лекарство от рака / вДудь
Более 1700 компаний уже выбрали Astana Hub для выхода на глобальный рынок — присоединяйтесь:
https://astanahub.com/ru/l/kak-otkryt-it-kompanyiu-v-kazakhstane?utm_source=youtube&utm_medium=vdud&utm_campaign=visa
Программы для фаундеров, digital-кочевников…
https://astanahub.com/ru/l/kak-otkryt-it-kompanyiu-v-kazakhstane?utm_source=youtube&utm_medium=vdud&utm_campaign=visa
Программы для фаундеров, digital-кочевников…
👎30👍17
День 2388. #ЗаметкиНаПолях
Разграничиваем Данные в Модульном Монолите. Начало
Модульные монолиты обещают производительность монолита и чёткие границы микросервисов. Каждый модуль самодостаточен: его модель предметной области, поведение и данные находятся внутри границ. Но одно из самых сложных мест для поддержания этих границ — база данных. Ничто не мешает разработчику создать JOIN-оператор между таблицами разных модулей или обойти общедоступный API.
Ранее мы рассматривали 4 уровня изоляции данных и как модули должны предоставлять явные API для доступа к своим данным. Теперь подробно рассмотрим границы в БД.
В модульном монолите каждый модуль владеет своими данными. Если модуль A обращается к таблицам модуля B, это ограничение теряется, и модули становятся тесно связанными. Вместо этого модуль B должен предоставлять общедоступный API и скрывать логику хранения своих данных от клиентов. Помимо чистого кода, соблюдение границ на уровне БД защищает вас от ошибок и упрощает последующее извлечение модуля в отдельный сервис.
Схемы БД действуют как папки: они позволяют организовывать объекты и предоставлять доступ к БД множеству пользователей, но пользователь может получить доступ к любой схеме только при наличии соответствующих привилегий. Это означает, что мы можем намеренно привязать каждый модуль к своей схеме.
Стратегия
- Создайте схему для каждого модуля и выделенную роль БД.
- Предоставьте этой роли привилегии только для её схемы и задайте для неё путь поиска по умолчанию (для PostgreSQL).
- Используйте EF с отдельными DbContext для каждого модуля, установив схему и строку подключения по умолчанию для каждого модуля.
- Для сквозных запросов создайте представление в БД, доступное только для чтения и действующее как общедоступный API.
- Необязательно: используйте политики безопасности на уровне строк для ограничения доступа внутри таблицы.
Эти методы позволят установить чёткие границы, одновременно снижая эксплуатационные издержки.
Схемы, роли и пути поиска
PostgreSQL (и многие другие БД) позволяет создавать несколько схем в одной БД. Модуль может определить свою собственную схему и роль, которой она принадлежит. Роль имеет только привилегии пользователя и уровня таблицы в этой схеме. Например, для модуля заказов:
Команда ALTER ROLE устанавливает путь поиска по умолчанию для роли, чтобы в запросах имена объектов, не имеющие схемы, разрешались в схему модуля. Если вы не хотите полагаться на путь поиска, используйте имена со схемой (orders.table_name).
Безопасность на уровне строк (Row-level security, RLS) позволяет фильтровать строки на основе выражения политики. RLS эффективен для многопользовательских сценариев или конфиденциальных данных, но он увеличивает сложность. Начните со схем и ролей и добавляйте RLS только при необходимости.
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/how-to-keep-your-data-boundaries-intact-in-a-modular-monolith
Разграничиваем Данные в Модульном Монолите. Начало
Модульные монолиты обещают производительность монолита и чёткие границы микросервисов. Каждый модуль самодостаточен: его модель предметной области, поведение и данные находятся внутри границ. Но одно из самых сложных мест для поддержания этих границ — база данных. Ничто не мешает разработчику создать JOIN-оператор между таблицами разных модулей или обойти общедоступный API.
Ранее мы рассматривали 4 уровня изоляции данных и как модули должны предоставлять явные API для доступа к своим данным. Теперь подробно рассмотрим границы в БД.
В модульном монолите каждый модуль владеет своими данными. Если модуль A обращается к таблицам модуля B, это ограничение теряется, и модули становятся тесно связанными. Вместо этого модуль B должен предоставлять общедоступный API и скрывать логику хранения своих данных от клиентов. Помимо чистого кода, соблюдение границ на уровне БД защищает вас от ошибок и упрощает последующее извлечение модуля в отдельный сервис.
Схемы БД действуют как папки: они позволяют организовывать объекты и предоставлять доступ к БД множеству пользователей, но пользователь может получить доступ к любой схеме только при наличии соответствующих привилегий. Это означает, что мы можем намеренно привязать каждый модуль к своей схеме.
Стратегия
- Создайте схему для каждого модуля и выделенную роль БД.
- Предоставьте этой роли привилегии только для её схемы и задайте для неё путь поиска по умолчанию (для PostgreSQL).
- Используйте EF с отдельными DbContext для каждого модуля, установив схему и строку подключения по умолчанию для каждого модуля.
- Для сквозных запросов создайте представление в БД, доступное только для чтения и действующее как общедоступный API.
- Необязательно: используйте политики безопасности на уровне строк для ограничения доступа внутри таблицы.
Эти методы позволят установить чёткие границы, одновременно снижая эксплуатационные издержки.
Схемы, роли и пути поиска
PostgreSQL (и многие другие БД) позволяет создавать несколько схем в одной БД. Модуль может определить свою собственную схему и роль, которой она принадлежит. Роль имеет только привилегии пользователя и уровня таблицы в этой схеме. Например, для модуля заказов:
CREATE ROLE orders_role LOGIN PASSWORD 'orders_secret';
CREATE SCHEMA orders AUTHORIZATION orders_role;
GRANT USAGE ON SCHEMA orders TO orders_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA orders TO orders_role;
ALTER ROLE orders_role SET search_path = orders;
Команда ALTER ROLE устанавливает путь поиска по умолчанию для роли, чтобы в запросах имена объектов, не имеющие схемы, разрешались в схему модуля. Если вы не хотите полагаться на путь поиска, используйте имена со схемой (orders.table_name).
Безопасность на уровне строк (Row-level security, RLS) позволяет фильтровать строки на основе выражения политики. RLS эффективен для многопользовательских сценариев или конфиденциальных данных, но он увеличивает сложность. Начните со схем и ролей и добавляйте RLS только при необходимости.
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/how-to-keep-your-data-boundaries-intact-in-a-modular-monolith
👍14
День 2389. #ЗаметкиНаПолях
Разграничиваем Данные в Модульном Монолите. Окончание
Начало
Настройка схем EF и нескольких DbContext
- Используйте отдельные DbContext на каждый модуль. Установка схемы по умолчанию также влияет на последовательности и миграции.
- Укажите строку подключения для каждого модуля, используя роль модуля. Даже если модули используют одну БД, различные роли гарантируют, что неправильно настроенный контекст не сможет получить доступ к другой схеме.
- Настройте таблицу истории миграций в каждом контексте.
Пошаговая инструкция
Допустим, у нас два модуля (Orders и Shipping), и мы хотим установить границы между ними.
1. Создадим схемы и роли, предоставив каждой роли привилегии только на её схему:
Аналогично для роли Shipping.
2. Добавим строки подключения:
3. Определим контексты для каждого модуля. Для Orders:
4. Регистрируем каждый контекст со своей строкой подключения и указываем таблицу миграций:
5. Управляем миграциями отдельно. При создании миграции указываем контекст:
EF Core сгенерирует классы миграций, которые создадут таблицы в указанной схеме (благодаря HasDefaultSchema). Не забудьте применить миграции в правильном порядке при развёртывании. Вы можете автоматизировать этот процесс, используя средство выполнения миграций, итеративно перебирающее контексты.
Сквозные запросы
Даже в модульной системе иногда требуется экран, охватывающий несколько модулей, например, страница истории заказов, отображающая данные о заказе и доставке. Не поддавайтесь искушению использовать JOIN между схемами. Полезны два подхода:
1. Выделенная модель чтения. Один модуль владеет моделью представления и подписывается на события других. Этот шаблон хорошо работает, когда модули могут быть позже извлечены в микросервисы.
2. Представления в БД. Поскольку модули используют общую БД, мы можем создать в общедоступной схеме представление, доступное только для чтения, которое объединяет соответствующие таблицы. Мы предоставляем право SELECT для представления специальной роли или модулю. Это представление действует как управляемое общедоступное API. Потребители запрашивают представление, но не могут напрямую получить доступ к базовым таблицам. Однако, если впоследствии вы разделите БД, представление придётся заменить вызовом сервиса.
Это позволит создавать отчёты, не нарушая границ данных.
Источник: https://www.milanjovanovic.tech/blog/how-to-keep-your-data-boundaries-intact-in-a-modular-monolith
Разграничиваем Данные в Модульном Монолите. Окончание
Начало
Настройка схем EF и нескольких DbContext
- Используйте отдельные DbContext на каждый модуль. Установка схемы по умолчанию также влияет на последовательности и миграции.
- Укажите строку подключения для каждого модуля, используя роль модуля. Даже если модули используют одну БД, различные роли гарантируют, что неправильно настроенный контекст не сможет получить доступ к другой схеме.
- Настройте таблицу истории миграций в каждом контексте.
Пошаговая инструкция
Допустим, у нас два модуля (Orders и Shipping), и мы хотим установить границы между ними.
1. Создадим схемы и роли, предоставив каждой роли привилегии только на её схему:
-- Схема и роль Orders
CREATE ROLE orders_role LOGIN PASSWORD 'orders_secret';
CREATE SCHEMA orders AUTHORIZATION orders_role;
GRANT USAGE ON SCHEMA orders TO orders_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA orders TO orders_role;
ALTER ROLE orders_role SET search_path = orders;
Аналогично для роли Shipping.
2. Добавим строки подключения:
{
"ConnectionStrings": {
"Orders": "Host=…;Database=…;Username=orders_role;Password=orders_secret",
"Shipping": "…"
}
}3. Определим контексты для каждого модуля. Для Orders:
public class OrdersDbContext : DbContext
{
public DbSet<Order>
Orders { get; set; } = default!;
…
protected override void
OnModelCreating(ModelBuilder mb)
{
// схема по умолчанию
mb.HasDefaultSchema("orders");
// либо явно таблиц
mb.Entity<Order>().ToTable("orders");
…
base.OnModelCreating(mb);
}
}
4. Регистрируем каждый контекст со своей строкой подключения и указываем таблицу миграций:
builder.Services
.AddDbContext<OrdersDbContext>(opts =>
opts.UseNpgsql(
builder.Configuration.GetConnectionString("Orders"),
o => o.MigrationsHistoryTable("__EFMigrationsHistory", "orders")));
5. Управляем миграциями отдельно. При создании миграции указываем контекст:
dotnet ef migrations add InitialOrders --context OrdersDbContext --output-dir Data/Migrations/Orders
EF Core сгенерирует классы миграций, которые создадут таблицы в указанной схеме (благодаря HasDefaultSchema). Не забудьте применить миграции в правильном порядке при развёртывании. Вы можете автоматизировать этот процесс, используя средство выполнения миграций, итеративно перебирающее контексты.
Сквозные запросы
Даже в модульной системе иногда требуется экран, охватывающий несколько модулей, например, страница истории заказов, отображающая данные о заказе и доставке. Не поддавайтесь искушению использовать JOIN между схемами. Полезны два подхода:
1. Выделенная модель чтения. Один модуль владеет моделью представления и подписывается на события других. Этот шаблон хорошо работает, когда модули могут быть позже извлечены в микросервисы.
2. Представления в БД. Поскольку модули используют общую БД, мы можем создать в общедоступной схеме представление, доступное только для чтения, которое объединяет соответствующие таблицы. Мы предоставляем право SELECT для представления специальной роли или модулю. Это представление действует как управляемое общедоступное API. Потребители запрашивают представление, но не могут напрямую получить доступ к базовым таблицам. Однако, если впоследствии вы разделите БД, представление придётся заменить вызовом сервиса.
CREATE VIEW public.order_summary AS
SELECT o.id, o.total, s.status
FROM orders.orders o
JOIN shipping.shipments s ON s.order_id = o.id;
-- даём права чтения роли отчётов
GRANT SELECT ON public.order_summary TO reporting_role;
Это позволит создавать отчёты, не нарушая границ данных.
Источник: https://www.milanjovanovic.tech/blog/how-to-keep-your-data-boundaries-intact-in-a-modular-monolith
👍8
День 2390. #TipsAndTricks
Как не Возвращать Все Свойства в SqlRaw
В Entity Framework SqlRaw есть небольшое, иногда раздражающее ограничение: SQL-запрос должен возвращать данные для всех свойств типа сущности.
Иногда этого не нужно, поэтому давайте посмотрим, как это очень просто обойти.
Представьте, что у нас есть такой объект:
По сути, мы указываем, что ни PropA, ни PropB не являются обязательными и, следовательно, если они отсутствуют в результирующем наборе, должны быть равны NULL. Но SqlRaw ожидает, что все свойства сущности присутствуют в предложении SELECT.
То есть:
Завершится с ошибкой, что EF не может сопоставить запрос с типом, поскольку отсутствует PropB.
Чтобы обойти эту проблему, просто явно возвращайте PropB как NULL:
Вот и всё, EF снова счастлив. Конечно, это упрощённый пример, но суть, надеюсь, вы поняли.
Источник: https://steven-giesel.com/blogPost/c6bea409-9e49-4915-8529-8a8a8574ba80/how-to-not-return-all-properties-in-sqlraw
Как не Возвращать Все Свойства в SqlRaw
В Entity Framework SqlRaw есть небольшое, иногда раздражающее ограничение: SQL-запрос должен возвращать данные для всех свойств типа сущности.
Иногда этого не нужно, поэтому давайте посмотрим, как это очень просто обойти.
Представьте, что у нас есть такой объект:
public sealed record MyEntity
{
public double? PropA { get; init; }
public double? PropB { get; init; }
}
По сути, мы указываем, что ни PropA, ни PropB не являются обязательными и, следовательно, если они отсутствуют в результирующем наборе, должны быть равны NULL. Но SqlRaw ожидает, что все свойства сущности присутствуют в предложении SELECT.
То есть:
dbContext
.Database
.SqlQuery<MyEntity>(
"SELECT PropA FROM MyTable")
.ToListAsync(token);
Завершится с ошибкой, что EF не может сопоставить запрос с типом, поскольку отсутствует PropB.
Чтобы обойти эту проблему, просто явно возвращайте PropB как NULL:
dbContext
.Database
.SqlQuery<MyEntity>(
"SELECT PropA, NULL AS PropB FROM MyTable")
.ToListAsync(token);
Вот и всё, EF снова счастлив. Конечно, это упрощённый пример, но суть, надеюсь, вы поняли.
Источник: https://steven-giesel.com/blogPost/c6bea409-9e49-4915-8529-8a8a8574ba80/how-to-not-return-all-properties-in-sqlraw
👍22
День 2391. #Архитектура
Вам Возможно не Нужен Redis
Redis, пожалуй, самая востребованная технология из всех, один из лидеров того, что вам «нужно» иметь в своём арсенале. Это очень хорошо спроектированная и впечатляющая технология. И при всём этом в ваших проектах он скорее всего не нужен!
Очень часто Redis используется просто потому, что считается отличным решением, и всё. Но при ближайшем рассмотрении реального варианта использования выясняется, что Redis ничего не улучшил и не решил основную проблему, а просто усложнил систему. Виктор Бломквист в своём блоге рассказал о трёх компаниях, в которых он работал. Везде был разный стек и разная нагрузка, но объединяло их необъяснимое стремление обязательно использовать Redis.
1. Tantan
Tantan - второе по величине приложение для знакомств в Китае. В то время, когда туда был добавлен Redis, система имела 50–100 мощных серверов БД с PostgreSQL. Каждый хранил подмножество пользовательских «свайпов», сегментированных по UserId, так что данные для конкретного пользователя хранились только на одном сервере.
Возникла необходимость отслеживать количество свайпов. По сути, одно целочисленное значение для каждого пользователя с двумя важными свойствами: очень частые обновления и считывания, чтобы всегда отображать правильное значение.
Первая мысль - разместить эти данные в Redis. Один мощный сервер Redis должен справиться с нагрузкой, поэтому понадобится всего пара (для резервирования). Однако в процессе внедрения возник вопрос: «Почему бы просто не хранить эти данные на шардах PostgreSQL рядом со свайпами?». Сами данные были бы микроскопическими по сравнению с тем, что уже делали эти серверы. После обсуждения в команде все согласились. Добавление Redis лишь усложнило бы относительно простой стек. После развёртывания дополнительная нагрузка на серверах баз данных была едва заметна.
2. Bannerflow
Bannerflow - компания, занимающаяся рекламой в интернет. Команда разрабатывала новый набор микросервисов для настройки и публикации рекламы в социальных сетях, таких как Facebook. Команда решила добавить экземпляр Redis для кэширования. Обратите внимание, что это было для системы с нагрузкой, составляющей даже меньше 0,1% от Tantan.
После ухода основного разработчика никто в команде уже не мог толком объяснить, зачем был нужен Redis. Глядя на код, количество вызовов или любую другую метрику, причины его добавления были не очевидны. И команда пришла к мнению, что при наличии свободного времени лучше всего будет удалить его.
3. MAJORITY
Финтех компания. Первым применением было кэширование результата внешнего вызова API для поиска геолокации пользователей, чтобы сервис мог обрабатывать запросы местоположения быстрее и дешевле. Кэширование этих данных вполне разумно. По чистой случайности, этот конкретный сервис выполнял два действия для поиска: вызов БД и вызов Redis. Это значительно упростило сравнение и оценку.
У сервиса была собственная БД, которая делила кластер с другими сервисами. Нагрузка на эту БД была настолько низкой, что она даже не отображалась при анализе нагрузки кластера в Azure. Перенос использования Redis в БД привёл бы к увеличению нагрузки примерно в два раза, что значительно в относительных числах, но в абсолютных это ничто, т.к. исходная была практически нулевой!
Конечно, когда в стек добавляется новая технология, всё больше и больше компонентов начинают её использовать. Так и тут. Вскоре возникла необходимость в распределении блокировок между несколькими экземплярами одного микросервиса. Т.к. Redis уже использовался, было естественно использовать его и для этой цели.
Но при более внимательном рассмотрении стало легко увидеть, что для этих блокировок можно было бы использовать механизм блокировок внутри основной БД (Azure SQL). Это бы увеличило нагрузку на БД, но, как и в предыдущем примере, это не было высокопроизводительным вариантом использования.
Источник: https://www.viblo.se/posts/no-need-redis/
Вам Возможно не Нужен Redis
Redis, пожалуй, самая востребованная технология из всех, один из лидеров того, что вам «нужно» иметь в своём арсенале. Это очень хорошо спроектированная и впечатляющая технология. И при всём этом в ваших проектах он скорее всего не нужен!
Очень часто Redis используется просто потому, что считается отличным решением, и всё. Но при ближайшем рассмотрении реального варианта использования выясняется, что Redis ничего не улучшил и не решил основную проблему, а просто усложнил систему. Виктор Бломквист в своём блоге рассказал о трёх компаниях, в которых он работал. Везде был разный стек и разная нагрузка, но объединяло их необъяснимое стремление обязательно использовать Redis.
1. Tantan
Tantan - второе по величине приложение для знакомств в Китае. В то время, когда туда был добавлен Redis, система имела 50–100 мощных серверов БД с PostgreSQL. Каждый хранил подмножество пользовательских «свайпов», сегментированных по UserId, так что данные для конкретного пользователя хранились только на одном сервере.
Возникла необходимость отслеживать количество свайпов. По сути, одно целочисленное значение для каждого пользователя с двумя важными свойствами: очень частые обновления и считывания, чтобы всегда отображать правильное значение.
Первая мысль - разместить эти данные в Redis. Один мощный сервер Redis должен справиться с нагрузкой, поэтому понадобится всего пара (для резервирования). Однако в процессе внедрения возник вопрос: «Почему бы просто не хранить эти данные на шардах PostgreSQL рядом со свайпами?». Сами данные были бы микроскопическими по сравнению с тем, что уже делали эти серверы. После обсуждения в команде все согласились. Добавление Redis лишь усложнило бы относительно простой стек. После развёртывания дополнительная нагрузка на серверах баз данных была едва заметна.
2. Bannerflow
Bannerflow - компания, занимающаяся рекламой в интернет. Команда разрабатывала новый набор микросервисов для настройки и публикации рекламы в социальных сетях, таких как Facebook. Команда решила добавить экземпляр Redis для кэширования. Обратите внимание, что это было для системы с нагрузкой, составляющей даже меньше 0,1% от Tantan.
После ухода основного разработчика никто в команде уже не мог толком объяснить, зачем был нужен Redis. Глядя на код, количество вызовов или любую другую метрику, причины его добавления были не очевидны. И команда пришла к мнению, что при наличии свободного времени лучше всего будет удалить его.
3. MAJORITY
Финтех компания. Первым применением было кэширование результата внешнего вызова API для поиска геолокации пользователей, чтобы сервис мог обрабатывать запросы местоположения быстрее и дешевле. Кэширование этих данных вполне разумно. По чистой случайности, этот конкретный сервис выполнял два действия для поиска: вызов БД и вызов Redis. Это значительно упростило сравнение и оценку.
У сервиса была собственная БД, которая делила кластер с другими сервисами. Нагрузка на эту БД была настолько низкой, что она даже не отображалась при анализе нагрузки кластера в Azure. Перенос использования Redis в БД привёл бы к увеличению нагрузки примерно в два раза, что значительно в относительных числах, но в абсолютных это ничто, т.к. исходная была практически нулевой!
Конечно, когда в стек добавляется новая технология, всё больше и больше компонентов начинают её использовать. Так и тут. Вскоре возникла необходимость в распределении блокировок между несколькими экземплярами одного микросервиса. Т.к. Redis уже использовался, было естественно использовать его и для этой цели.
Но при более внимательном рассмотрении стало легко увидеть, что для этих блокировок можно было бы использовать механизм блокировок внутри основной БД (Azure SQL). Это бы увеличило нагрузку на БД, но, как и в предыдущем примере, это не было высокопроизводительным вариантом использования.
Источник: https://www.viblo.se/posts/no-need-redis/
👍10
День 2392. #ЗаметкиНаПолях
PDF из HTML в .NET (бесплатно). Начало
Создание качественных PDF-отчётов в .NET не обязательно должно быть мучением. Тем, кто хоть немного работает с HTML, проще будет использовать преобразование из HTML в PDF.
Но многие популярные библиотеки требуют коммерческой лицензии. В этой серии рассмотрим бесплатный подход с использованием:
- Handlebars.NET для шаблонов,
- PuppeteerSharp для рендеринга.
Это даёт вам полный контроль над макетом, стилем и содержимым:
- Богатая стилизация с помощью CSS,
- Легкий просмотр/отладка в браузере,
- Поддержка диаграмм/изображений через JS/CSS,
- Полный контроль над макетом (медиазапросы, разрывы страниц и т. д.).
Минусы:
- Требуется установка браузера (например, Chromium),
- Медленнее, чем нативные библиотеки PDF,
- Немного сложнее в настройке.
1. Настройка
Создадим проект минимальных API и установим NuGet-пакеты:
2. Создание шаблона
Это простой HTML-документ с несколькими заместителями для Handlebars. Вы заметите их по синтаксису
Вызовы функций, вроде
Это позволяет, например, форматировать даты, валюты и т.п. при необходимости. Вспомогательные функции регистрируются перед компиляцией шаблона (его мы рассмотрим в продолжении), достаточно одного раза при запуске приложения.
Кроме того, в Handlebars есть встроенные управляющие конструкции, например:
-
-
Продолжение следует…
Источник: https://www.milanjovanovic.tech/blog/pdf-reporting-in-dotnet-with-html-templates-and-puppeteersharp
PDF из HTML в .NET (бесплатно). Начало
Создание качественных PDF-отчётов в .NET не обязательно должно быть мучением. Тем, кто хоть немного работает с HTML, проще будет использовать преобразование из HTML в PDF.
Но многие популярные библиотеки требуют коммерческой лицензии. В этой серии рассмотрим бесплатный подход с использованием:
- Handlebars.NET для шаблонов,
- PuppeteerSharp для рендеринга.
Это даёт вам полный контроль над макетом, стилем и содержимым:
- Богатая стилизация с помощью CSS,
- Легкий просмотр/отладка в браузере,
- Поддержка диаграмм/изображений через JS/CSS,
- Полный контроль над макетом (медиазапросы, разрывы страниц и т. д.).
Минусы:
- Требуется установка браузера (например, Chromium),
- Медленнее, чем нативные библиотеки PDF,
- Немного сложнее в настройке.
1. Настройка
Создадим проект минимальных API и установим NuGet-пакеты:
Install-Package Handlebars.Net
Install-Package PuppeteerSharp
2. Создание шаблона
Это простой HTML-документ с несколькими заместителями для Handlebars. Вы заметите их по синтаксису
{{variable}}. Они будут заменены реальными данными при рендеринге шаблона.<!-- Templates/InvoiceTemplate.html -->
<html lang="en">
<head>
<style>
body {
font-family: Arial;
}
</style>
</head>
<body>
<h1>Invoice #{{Number}}</h1>
<p>Date: {{formatDate IssuedDate}}</p>
<h2>From:</h2>
<p>{{SellerAddress.CompanyName}}</p>
<p>{{SellerAddress.Email}}</p>
<h2>To:</h2>
<p>{{CustomerAddress.CompanyName}}</p>
<p>{{CustomerAddress.Email}}</p>
<h2>Items:</h2>
<table>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
{{#each LineItems}}
<tr>
<td>{{Name}}</td>
<td>{{formatCurrency Price}}</td>
</tr>
{{/each}}
</table>
<p><strong>Total: {{formatCurrency Total}}</strong></p>
</body>
</html>
Вызовы функций, вроде
{{formatDate IssuedDate}} – это пользовательские вспомогательные функции, которые можно определить в Handlebars. Они регистрируются так:Handlebars.RegisterHelper("formatDate", (context, arguments) =>
{
if (arguments[0] is DateOnly date)
return date.ToString("dd/MM/yyyy");
return arguments[0]?.ToString() ?? "";
});Это позволяет, например, форматировать даты, валюты и т.п. при необходимости. Вспомогательные функции регистрируются перед компиляцией шаблона (его мы рассмотрим в продолжении), достаточно одного раза при запуске приложения.
Кроме того, в Handlebars есть встроенные управляющие конструкции, например:
-
{{#if …}} ... {{/if}} для условий, -
{{#each …}} ... {{/each}} для перебора коллекций.Продолжение следует…
Источник: https://www.milanjovanovic.tech/blog/pdf-reporting-in-dotnet-with-html-templates-and-puppeteersharp
👍9
День 2393. #ЗаметкиНаПолях
PDF из HTML в .NET (бесплатно). Продолжение
Начало. Создание шаблона
3. Рендеринг шаблона
Мы используем Handlebars для компиляции шаблона с данными, а затем PuppeteerSharp для рендеринга в PDF.
Сначала прочитаем файл шаблона, скомпилируем его с помощью Handlebars и заполним данными:
Это даст нам финальный HTML-код, в котором все заместители заменены реальными данными.
Примечание: не обязательно использовать Handlebars. Можно использовать любой шаблонизатор, например, Razor или Scriban.
4. Преобразование в PDF
Мы запустим браузер без UI, установим получившийся HTML код как его контент и сгенерируем PDF-файл:
В начале нам нужно скачать движок Chromium для рендеринга HTML страницы. PuppeteerSharp требует загрузки исполняемых файлов браузера Chromium во время выполнения. Это можно сделать, вызвав BrowserFetcher.DownloadAsync(). Это займёт какое-то время в первый раз, далее PuppeteerSharp будет использовать скачанные бинарные файлы.
В итоге в переменной pdf мы получим массив байтов, содержащий данные PDF-файла. Вы можете сохранить его в файл или вернуть из конечной точки API. Например, так:
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/pdf-reporting-in-dotnet-with-html-templates-and-puppeteersharp
PDF из HTML в .NET (бесплатно). Продолжение
Начало. Создание шаблона
3. Рендеринг шаблона
Мы используем Handlebars для компиляции шаблона с данными, а затем PuppeteerSharp для рендеринга в PDF.
Сначала прочитаем файл шаблона, скомпилируем его с помощью Handlebars и заполним данными:
var tmplt = File.ReadAllText(
"Templates/InvoiceTemplate.html");
var data = new {
IssuedDate = new DateOnly(2025, 08, 19),
Number = 12345,
…
LineItems = new[] {
new { Name = "Software License", Price = 99d },
new { Name = "Support Plan", Price = 49d }
}
};
// ... регистрируем вспомогательные функции Handlebars до вызова компиляции ...
var compiledTmplt = Handlebars.Compile(tmplt);
var html = compiledTmplt(data);
Это даст нам финальный HTML-код, в котором все заместители заменены реальными данными.
Примечание: не обязательно использовать Handlebars. Можно использовать любой шаблонизатор, например, Razor или Scriban.
4. Преобразование в PDF
Мы запустим браузер без UI, установим получившийся HTML код как его контент и сгенерируем PDF-файл:
// Скачиваем бинарные файлы браузера
var browserFetcher = new BrowserFetcher();
await browserFetcher.DownloadAsync();
// Запускаем браузер и создаём страницу
using var browser = await Puppeteer.LaunchAsync(
new LaunchOptions { Headless = true });
using var page = await browser.NewPageAsync();
// Устанавливаем контент страницы
await page.SetContentAsync(html);
// Не обязательно: ждём загрузки шрифтов
await page.EvaluateExpressionHandleAsync(
"document.fonts.ready");
// Настраиваем PDF
byte[] pdf = await page.PdfDataAsync(
new PdfOptions {
Format = PaperFormat.A4,
PrintBackground = true,
MarginOptions = new MarginOptions
{
Top = "50px",
Right = "20px",
Bottom = "50px",
Left = "20px"
}
});
В начале нам нужно скачать движок Chromium для рендеринга HTML страницы. PuppeteerSharp требует загрузки исполняемых файлов браузера Chromium во время выполнения. Это можно сделать, вызвав BrowserFetcher.DownloadAsync(). Это займёт какое-то время в первый раз, далее PuppeteerSharp будет использовать скачанные бинарные файлы.
В итоге в переменной pdf мы получим массив байтов, содержащий данные PDF-файла. Вы можете сохранить его в файл или вернуть из конечной точки API. Например, так:
return Results.File(pdf, "application/pdf", "invoice.pdf");
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/pdf-reporting-in-dotnet-with-html-templates-and-puppeteersharp
👍6
День 2394. #ЗаметкиНаПолях
PDF из HTML в .NET (бесплатно). Окончание
Создание шаблона
Рендеринг в PDF
5. Улучшения: изображения, верхний/нижний колонтитул, стили
Можно добавлять изображения в шаблон с помощью тега <img>. Простой подход — использовать изображение в кодировке base64 непосредственно в HTML-коде. Обратите внимание, что мы передаём данные изображения через переменную LogoBase64:
Мы также можем отображать динамические верхние и нижние колонтитулы, используя встроенную поддержку PuppeteerSharp. Можно определить их в объекте PdfOptions при создании PDF-файла:
PuppeteerSharp использует CSS-классы, такие как title, date, pageNumber и totalPages для внедрения динамических значений. В некоторых других библиотеках это может отличаться, поэтому сверьтесь с документацией.
Наконец, вы можете использовать CSS для расширенной стилизации. CSS может быть встроен в HTML-код или оформлен в отдельный CSS-файл. При необходимости вы также можете ссылаться на внешние таблицы стилей, используя тег <link>.
6. Вопросы производительности
Первое, что следует рассмотреть, — это использование headless-браузера, например, Chromium. Он может работать медленнее, чем нативные PDF-библиотеки, особенно для больших документов или высокого уровня параллелизма. Кроме того, это увеличивает нагрузку на процессор, поскольку необходимо загружать и запускать исполняемые файлы браузера. Однозначно стоит рассмотреть возможность вынесения этого процесса из основного приложения. Вы можете использовать фоновый сервис или отдельный микросервис для генерации PDF-файлов. Облачная функция может быть хорошим решением при масштабировании. Но всё зависит от вашего конкретного сценария использования.
Итого
HTML + PuppeteerSharp — один из самых практичных подходов к созданию PDF-отчётов в .NET. Он позволяет:
- Создавать безупречные макеты с использованием знакомых веб-технологий,
- Вставлять динамические данные с помощью Handlebars,
- Выводить высококачественные PDF-файлы с полной стилизацией, таблицами и изображениями.
И всё это без использования коммерческих библиотек. По сравнению с платными библиотеками, вроде QuestPdf или IronPdf, вы пожертвуете производительностью и придётся выполнить некоторую первоначальную настройку. Но для большинства внутренних инструментов, отчётов или генерации счетов для клиентов это того стоит.
Источник: https://www.milanjovanovic.tech/blog/pdf-reporting-in-dotnet-with-html-templates-and-puppeteersharp
PDF из HTML в .NET (бесплатно). Окончание
Создание шаблона
Рендеринг в PDF
5. Улучшения: изображения, верхний/нижний колонтитул, стили
Можно добавлять изображения в шаблон с помощью тега <img>. Простой подход — использовать изображение в кодировке base64 непосредственно в HTML-коде. Обратите внимание, что мы передаём данные изображения через переменную LogoBase64:
<img
src="data:image/png;base64,{{LogoBase64}}"
alt="Logo"
style="height:50px; max-width:200px; object-fit:contain;"
/>
Мы также можем отображать динамические верхние и нижние колонтитулы, используя встроенную поддержку PuppeteerSharp. Можно определить их в объекте PdfOptions при создании PDF-файла:
var pdfOptions = new PdfOptions
{
HeaderTemplate =
@"""
<div style='font-size: 14px; text-align: center; padding: 10px;'>
<span style='margin-right: 20px;'><span class='title'></span></span>
<span><span class='date'></span></span>
</div>
""",
FooterTemplate =
@"""
<div style='font-size: 14px; text-align: center; padding: 10px;'>
<span style='margin-right: 20px;'>Документ создан <span class='date'></span></span>
<span>Страница <span class='pageNumber'></span> из <span class='totalPages'></span></span>
</div>
""",
DisplayHeaderFooter = true
};
PuppeteerSharp использует CSS-классы, такие как title, date, pageNumber и totalPages для внедрения динамических значений. В некоторых других библиотеках это может отличаться, поэтому сверьтесь с документацией.
Наконец, вы можете использовать CSS для расширенной стилизации. CSS может быть встроен в HTML-код или оформлен в отдельный CSS-файл. При необходимости вы также можете ссылаться на внешние таблицы стилей, используя тег <link>.
6. Вопросы производительности
Первое, что следует рассмотреть, — это использование headless-браузера, например, Chromium. Он может работать медленнее, чем нативные PDF-библиотеки, особенно для больших документов или высокого уровня параллелизма. Кроме того, это увеличивает нагрузку на процессор, поскольку необходимо загружать и запускать исполняемые файлы браузера. Однозначно стоит рассмотреть возможность вынесения этого процесса из основного приложения. Вы можете использовать фоновый сервис или отдельный микросервис для генерации PDF-файлов. Облачная функция может быть хорошим решением при масштабировании. Но всё зависит от вашего конкретного сценария использования.
Итого
HTML + PuppeteerSharp — один из самых практичных подходов к созданию PDF-отчётов в .NET. Он позволяет:
- Создавать безупречные макеты с использованием знакомых веб-технологий,
- Вставлять динамические данные с помощью Handlebars,
- Выводить высококачественные PDF-файлы с полной стилизацией, таблицами и изображениями.
И всё это без использования коммерческих библиотек. По сравнению с платными библиотеками, вроде QuestPdf или IronPdf, вы пожертвуете производительностью и придётся выполнить некоторую первоначальную настройку. Но для большинства внутренних инструментов, отчётов или генерации счетов для клиентов это того стоит.
Источник: https://www.milanjovanovic.tech/blog/pdf-reporting-in-dotnet-with-html-templates-and-puppeteersharp
👍7
День 2395. #SystemDesign101
Как Работает SSO?
Технология единого входа (Single Sign-On SSO) — технология, позволяющая пользователю аутентифицироваться один раз, чтобы получить доступ ко множеству связанных сервисов или приложений, без необходимости повторно вводить свои учётные данные для каждого из них.
Рассмотрим типичный процесс входа в SSO.
Шаг 1: Пользователь запрашивает защищённый ресурс в приложении, например, Gmail, которое является провайдером сервиса (Service Provider, SP).
Шаг 2: Сервер Gmail обнаруживает, что пользователь не вошёл в систему, и перенаправляет браузер к провайдеру идентификации (Identity Provider, IdP) компании с запросом на аутентификацию.
Шаг 3: Браузер перенаправляет пользователя к IdP.
Шаг 4: IdP отображает страницу входа, где пользователь вводит свои учётные данные.
Шаг 5: IdP создаёт защищённый токен и возвращает его браузеру. IdP также создаёт сессию для будущего доступа. Браузер перенаправляет токен в Gmail.
Шаг 6: Gmail проверяет токен, чтобы убедиться, что он получен от провайдера идентификации.
Шаг 7: Gmail возвращает защищённые ресурсы в браузер в зависимости от того, к чему пользователю разрешён доступ.
На этом процесс входа с помощью SSO завершается. Теперь посмотрим, что произойдёт, когда пользователь перейдёт в другое приложение с интегрированной системой единого входа, например, в Slack.
Шаги 8-9: Пользователь заходит в Slack, и сервер Slack обнаруживает, что он не вошёл в систему. Slack перенаправляет браузер к провайдеру идентификации с новым запросом на аутентификацию.
Шаг 10: Браузер перенаправляет пользователя к провайдеру идентификации.
Шаги 11-13: Поскольку пользователь уже вошёл в систему провайдера идентификации, процесс входа пропускается и сразу создаётся новый токен для Slack. Новый токен отправляется в браузер, который перенаправляет его в Slack.
Шаг 14–15: Slack проверяет токен и предоставляет пользователю соответствующий доступ.
Источник: https://blog.bytebytego.com/
Как Работает SSO?
Технология единого входа (Single Sign-On SSO) — технология, позволяющая пользователю аутентифицироваться один раз, чтобы получить доступ ко множеству связанных сервисов или приложений, без необходимости повторно вводить свои учётные данные для каждого из них.
Рассмотрим типичный процесс входа в SSO.
Шаг 1: Пользователь запрашивает защищённый ресурс в приложении, например, Gmail, которое является провайдером сервиса (Service Provider, SP).
Шаг 2: Сервер Gmail обнаруживает, что пользователь не вошёл в систему, и перенаправляет браузер к провайдеру идентификации (Identity Provider, IdP) компании с запросом на аутентификацию.
Шаг 3: Браузер перенаправляет пользователя к IdP.
Шаг 4: IdP отображает страницу входа, где пользователь вводит свои учётные данные.
Шаг 5: IdP создаёт защищённый токен и возвращает его браузеру. IdP также создаёт сессию для будущего доступа. Браузер перенаправляет токен в Gmail.
Шаг 6: Gmail проверяет токен, чтобы убедиться, что он получен от провайдера идентификации.
Шаг 7: Gmail возвращает защищённые ресурсы в браузер в зависимости от того, к чему пользователю разрешён доступ.
На этом процесс входа с помощью SSO завершается. Теперь посмотрим, что произойдёт, когда пользователь перейдёт в другое приложение с интегрированной системой единого входа, например, в Slack.
Шаги 8-9: Пользователь заходит в Slack, и сервер Slack обнаруживает, что он не вошёл в систему. Slack перенаправляет браузер к провайдеру идентификации с новым запросом на аутентификацию.
Шаг 10: Браузер перенаправляет пользователя к провайдеру идентификации.
Шаги 11-13: Поскольку пользователь уже вошёл в систему провайдера идентификации, процесс входа пропускается и сразу создаётся новый токен для Slack. Новый токен отправляется в браузер, который перенаправляет его в Slack.
Шаг 14–15: Slack проверяет токен и предоставляет пользователю соответствующий доступ.
Источник: https://blog.bytebytego.com/
👍20
День 2396. #ЧтоНовенького #NET10
Ещё Два Метода-Расширения LINQ в .NET 10
В предварительной версии 6 .NET 10 появились ещё два метода LINQ: InfiniteSequence и Sequence.
1. InfiniteSequence
InfiniteSequence — это простой генератор, позволяющий получить бесконечную последовательность из начальной точки, задавая размер шага:
В результате будут выведены значения 0, 5, 10, 15, …, 45.
Замечание: будьте осторожны и не вызывайте ToList/Count и подобные функции без Take, иначе вы получите исключение «Недостаточно памяти».
2. Sequence
Это почти то же самое, что InfiniteSequence, с той разницей, что можно определить включительно верхнюю границу:
Результат будет таким же, как в предыдущем примере.
Источник: https://steven-giesel.com/blogPost/5d88d808-03ac-431a-82fa-756b59b38a7d/two-more-linq-extensions-in-dotnet-10
Ещё Два Метода-Расширения LINQ в .NET 10
В предварительной версии 6 .NET 10 появились ещё два метода LINQ: InfiniteSequence и Sequence.
1. InfiniteSequence
InfiniteSequence — это простой генератор, позволяющий получить бесконечную последовательность из начальной точки, задавая размер шага:
var steps =
Enumerable.InfiniteSequence(0, 5)
.Take(10)
.ToList();
foreach (var step in steps)
Console.WriteLine(step);
В результате будут выведены значения 0, 5, 10, 15, …, 45.
Замечание: будьте осторожны и не вызывайте ToList/Count и подобные функции без Take, иначе вы получите исключение «Недостаточно памяти».
2. Sequence
Это почти то же самое, что InfiniteSequence, с той разницей, что можно определить включительно верхнюю границу:
var steps =
Enumerable.Sequence(0, 45, 5)
.ToList();
foreach (var step in steps)
Console.WriteLine(step);
Результат будет таким же, как в предыдущем примере.
Источник: https://steven-giesel.com/blogPost/5d88d808-03ac-431a-82fa-756b59b38a7d/two-more-linq-extensions-in-dotnet-10
👍28
День 2397. #ЗаметкиНаПолях
Сокращаем Шаблонный Код и Поддерживаем Согласованность Проекта. Начало
TL/DR: Если вы работаете в небольшой компании и ведёте всего один или два проекта, эта публикация может быть для вас неактуальна.
В крупных могут быть сотни или тысячи репозиториев. По мере роста организации поддерживать согласованность и единообразие всех проектов становится сложной задачей. Легко использовать шаблон для запуска нового проекта, но как поддерживать согласованность всех проектов с течением времени? Как гарантировать, что все проекты обновляют свои стандарты кодирования, CI/CD, безопасность, наблюдаемость и другие аспекты в соответствии с передовыми практиками? Каждая компания сталкивается с уникальными проблемами, но есть некоторые идеи и стратегии, которые помогут повысить согласованность между проектами.
В первую очередь необходимо по максимуму удалить шаблонный код и конфигурации из каждого проекта и заменить их неявными значениями по умолчанию или ссылками, которые можно обновлять с помощью таких инструментов, как Dependabot или Renovate. Такой подход обеспечивает базовую конфигурацию, которая может обновляться автоматически. Также важно предусмотреть точки расширения для настроек, специфичных для проекта.
Преимущества
- Сосредоточенность на продукте: разработчики могут сосредоточиться на разработке функций, а не на настройке и поддержке общего кода.
- Более эффективное организационное воздействие: каждый дополнительный проект, ссылающийся на общую библиотеку, увеличивает её ценность.
- Ускоренная поставка: повторное использование существующих компонентов ускоряет переход от идеи к производству.
- Единообразие: способствует внедрению единых практик и стандартов во всех проектах.
- Сокращение затрат на обслуживание: меньше дублирующегося кода означает меньше задач по поддержке и тестированию.
- Расширенное сотрудничество: способствует обмену знаниями и командной работе.
- Меньше шаблонного кода: кодовые базы легче читать и поддерживать, поскольку не нужно повторять одну и ту же конфигурацию в нескольких проектах. Кроме того, благодаря сокращению шаблонного кода становится понятнее, что относится к конкретному проекту.
- Обновляемость: предоставление новых функций или исправление ошибок без необходимости внесения изменений в каждый проект.
Большую часть времени разработчики думают только о создании пакетов для кода. Однако использовать по ссылке можно не только библиотеки кода, но также файлы конфигурации, стандарты кодирования, конвейеры CI/CD и многое другое.
Прежде чем начать этот путь, будьте готовы к сопротивлению со стороны разработчиков и менеджеров по продукту, которые могут не сразу оценить ценность этих усилий. Выгоды редко появляются сразу, но со временем они накапливаются. Вам нужно будет пропагандировать преимущества обмена ссылками. Начните с малого, с нескольких проектов, и продемонстрируйте преимущества. Как только у вас будет несколько историй успеха, будет легче убедить других присоединиться!
Вам также необходимо понимать, что речь идёт не об универсальных библиотеках, которые может использовать каждый. Речь идёт о создании общих компонентов, специфичных для вашей компании и проектов. Это, безусловно, субъективный подход, поскольку вы хотите предоставить разработчикам правильные настройки по умолчанию, чтобы помочь им добиться успеха.
Далее мы рассмотрим несколько видов кода и конфигураций, которыми можно делиться по ссылке. Некоторые из них специфичны для .NET, но вообще эти принципы применимы к любому технологическому стеку.
Продолжение следует…
Источник: https://www.meziantou.net/reduce-boilerplate-and-maintain-project-consistency.htm
Сокращаем Шаблонный Код и Поддерживаем Согласованность Проекта. Начало
TL/DR: Если вы работаете в небольшой компании и ведёте всего один или два проекта, эта публикация может быть для вас неактуальна.
В крупных могут быть сотни или тысячи репозиториев. По мере роста организации поддерживать согласованность и единообразие всех проектов становится сложной задачей. Легко использовать шаблон для запуска нового проекта, но как поддерживать согласованность всех проектов с течением времени? Как гарантировать, что все проекты обновляют свои стандарты кодирования, CI/CD, безопасность, наблюдаемость и другие аспекты в соответствии с передовыми практиками? Каждая компания сталкивается с уникальными проблемами, но есть некоторые идеи и стратегии, которые помогут повысить согласованность между проектами.
В первую очередь необходимо по максимуму удалить шаблонный код и конфигурации из каждого проекта и заменить их неявными значениями по умолчанию или ссылками, которые можно обновлять с помощью таких инструментов, как Dependabot или Renovate. Такой подход обеспечивает базовую конфигурацию, которая может обновляться автоматически. Также важно предусмотреть точки расширения для настроек, специфичных для проекта.
Преимущества
- Сосредоточенность на продукте: разработчики могут сосредоточиться на разработке функций, а не на настройке и поддержке общего кода.
- Более эффективное организационное воздействие: каждый дополнительный проект, ссылающийся на общую библиотеку, увеличивает её ценность.
- Ускоренная поставка: повторное использование существующих компонентов ускоряет переход от идеи к производству.
- Единообразие: способствует внедрению единых практик и стандартов во всех проектах.
- Сокращение затрат на обслуживание: меньше дублирующегося кода означает меньше задач по поддержке и тестированию.
- Расширенное сотрудничество: способствует обмену знаниями и командной работе.
- Меньше шаблонного кода: кодовые базы легче читать и поддерживать, поскольку не нужно повторять одну и ту же конфигурацию в нескольких проектах. Кроме того, благодаря сокращению шаблонного кода становится понятнее, что относится к конкретному проекту.
- Обновляемость: предоставление новых функций или исправление ошибок без необходимости внесения изменений в каждый проект.
Большую часть времени разработчики думают только о создании пакетов для кода. Однако использовать по ссылке можно не только библиотеки кода, но также файлы конфигурации, стандарты кодирования, конвейеры CI/CD и многое другое.
Прежде чем начать этот путь, будьте готовы к сопротивлению со стороны разработчиков и менеджеров по продукту, которые могут не сразу оценить ценность этих усилий. Выгоды редко появляются сразу, но со временем они накапливаются. Вам нужно будет пропагандировать преимущества обмена ссылками. Начните с малого, с нескольких проектов, и продемонстрируйте преимущества. Как только у вас будет несколько историй успеха, будет легче убедить других присоединиться!
Вам также необходимо понимать, что речь идёт не об универсальных библиотеках, которые может использовать каждый. Речь идёт о создании общих компонентов, специфичных для вашей компании и проектов. Это, безусловно, субъективный подход, поскольку вы хотите предоставить разработчикам правильные настройки по умолчанию, чтобы помочь им добиться успеха.
Далее мы рассмотрим несколько видов кода и конфигураций, которыми можно делиться по ссылке. Некоторые из них специфичны для .NET, но вообще эти принципы применимы к любому технологическому стеку.
Продолжение следует…
Источник: https://www.meziantou.net/reduce-boilerplate-and-maintain-project-consistency.htm
👍9👎1