Event Sourcing меняет привычный подход к хранению данных: вместо перезаписи текущего состояния система сохраняет последовательность событий, из которых это состояние можно восстановить.В этом посте:
• Разберём, почему в Event Sourcing состояние становится производным;
• Отделим команды от событий и поймём их разные роли;
• Реализуем восстановление состояния через последовательный replay событий;
• Разберём, почему события становятся источником истины, а не просто журналом изменений.
Такой подход сохраняет не только результат изменений, но и их причинную историю, позволяя строить новые представления данных и воспроизводить состояние системы на основе событий.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍6❤3
Получаем только изменённые поля объекта!
При изменении состояния приложения можно сравнить предыдущую и текущую версию объекта, чтобы определить только обновлённые значения. Такой
Для получения изменённых полей можно преобразовать объект в массив пар через
В результате получится объект, содержащий только изменённые поля:
Такой подход применяется при обновлении состояния во frontend-приложениях, работе с формами и подготовке частичных изменений для API через
Вместо отправки всей структуры можно передать только те значения, которые действительно были изменены.
Проверка через количество ключей позволяет определить, были ли изменения вообще. Если объект
Важно учитывать, что данный способ выполняет только поверхностное сравнение (
Вложенные объекты сравниваются по ссылке, поэтому одинаковая структура внутри разных объектов не делает их равными. Для таких случаев требуется использовать глубокое сравнение (
🔥 Также при реализации реальных обновлений через
📣 JS Ready | #практика
При изменении состояния приложения можно сравнить предыдущую и текущую версию объекта, чтобы определить только обновлённые значения. Такой
diff позволяет отделить изменившиеся поля от тех, которые остались без изменений, и использовать только актуальные данные для дальнейшей обработки.const oldUser = {
name: "Alex",
age: 30,
role: "developer"
};
const newUser = {
name: "Alex",
age: 31,
role: "developer"
};Для получения изменённых полей можно преобразовать объект в массив пар через
Object.entries(), отфильтровать только отличающиеся значения и собрать новый объект обратно с помощью Object.fromEntries().const changes = Object.fromEntries(
Object.entries(newUser)
.filter(([key, value]) =>
!Object.is(value, oldUser[key])
)
);
console.log(changes);
В результате получится объект, содержащий только изменённые поля:
{
age: 31
}Такой подход применяется при обновлении состояния во frontend-приложениях, работе с формами и подготовке частичных изменений для API через
PATCH запросы. Вместо отправки всей структуры можно передать только те значения, которые действительно были изменены.
const hasChanges =
Object.keys(changes).length > 0;
console.log(hasChanges);
Проверка через количество ключей позволяет определить, были ли изменения вообще. Если объект
changes пустой, значит исходное и новое состояние полностью совпадают.Важно учитывать, что данный способ выполняет только поверхностное сравнение (
shallow diff) и проверяет значения первого уровня объекта.const first = {
profile: {
name: "Alex"
}
};
const second = {
profile: {
name: "Alex"
}
};
console.log(first.profile === second.profile);
// falseВложенные объекты сравниваются по ссылке, поэтому одинаковая структура внутри разных объектов не делает их равными. Для таких случаев требуется использовать глубокое сравнение (
deep comparison) или отдельную логику построения diff.PATCH нужно отдельно учитывать удаление полей, так как простой shallow diff определяет только добавленные и изменённые значения.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤7👍4🤝1
Например,
await приостанавливает выполнение текущей async-функции до завершения Promise, а try...catch позволяет удобно обрабатывать возникающие ошибки.На картинке — основной синтаксис
async/await, принцип его работы, сравнение с цепочками .then() и другие полезные моменты.Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9❤8👍7
Склеивайте множество синхронных обновлений в одно!
Иногда функция может вызываться десятки раз за один проход синхронного кода.
Можно накопить изменения и запланировать обработку один раз:
Теперь даже серия вызовов обработается одной пачкой:
🔥 Такой подход объединяет множество быстрых обновлений в одну обработку, уменьшая лишние вычисления и нагрузку. Что крайне полезно для событий и часто обновляемых данных.
📣 JS Ready | #совет
Иногда функция может вызываться десятки раз за один проход синхронного кода.
Можно накопить изменения и запланировать обработку один раз:
const pending = new Set();
function update(id) {
pending.add(id);
scheduleFlush();
}
queueMicrotask() выполнит flush после текущего синхронного кода, но до перехода к следующей macrotask:function flush() {
scheduled = false;
process([...pending]);
pending.clear();
}Теперь даже серия вызовов обработается одной пачкой:
update(1);
update(2);
update(3);
// process([1, 2, 3]) — один раз
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤6👍5
Например,
const [a, b] = arr позволяет получить значения из массива, а const { name, age } = user — извлечь нужные свойства из объекта.На картинке — основные способы деструктуризации массивов и объектов, которые пригодятся в повседневной разработке.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍8❤3🤝2
Почему Array.sort() может создать баг далеко от места сортировки!
С
Особенно неприятно это становится на границе функций. По названию
В результате функция имеет побочный эффект, который не очевиден из места её вызова. Это уже может затронуть состояние, кэш, мемоизацию или другой код, использующий тот же массив:
Классический способ избежать этого — сначала создать новый массив и сортировать уже его. Такой код работает, но копирование приходится выражать отдельной операцией:
Важно, что копируется именно массив. Объекты внутри него не клонируются, поэтому обе коллекции по-прежнему содержат ссылки на одни и те же объекты:
Поэтому изменение самого объекта будет видно через обе коллекции:
Похожая логика есть у
🔥 Поэтому выбор между
📣 JS Ready | #практика
С
sort() проблема не в самой сортировке, а в скрытой мутации. Вызов выглядит как обычное получение отсортированного результата, хотя одновременно изменяет сам исходный массив:const scores = [30, 10, 20];
const sorted = scores.sort(
(a, b) => a - b
);
console.log(scores);
// [10, 20, 30]
sort() сортирует массив непосредственно и возвращает ту же ссылку. Поэтому изменение увидит не только код, работающий с sorted, но и любой код, который уже хранит ссылку на исходный массив:const scores = [30, 10, 20];
const cached = scores;
scores.sort((a, b) => a - b);
console.log(cached);
// [10, 20, 30]
Особенно неприятно это становится на границе функций. По названию
getSortedUsers() можно ожидать получение нового результата, но реализация незаметно меняет массив вызывающего кода:function getSortedUsers(users) {
return users.sort(
(a, b) => a.age - b.age
);
}В результате функция имеет побочный эффект, который не очевиден из места её вызова. Это уже может затронуть состояние, кэш, мемоизацию или другой код, использующий тот же массив:
const users = getUsers();
const original = users;
getSortedUsers(users);
console.log(original === users);
// true
Классический способ избежать этого — сначала создать новый массив и сортировать уже его. Такой код работает, но копирование приходится выражать отдельной операцией:
const sorted = [...scores].sort(
(a, b) => a - b
);
console.log(scores);
// [30, 10, 20]
toSorted() делает намерение явным: получить новый массив с другим порядком элементов, не изменяя исходный. При этом возвращается уже другая ссылка:const sorted = scores.toSorted(
(a, b) => a - b
);
console.log(scores);
// [30, 10, 20]
console.log(scores === sorted);
// false
Важно, что копируется именно массив. Объекты внутри него не клонируются, поэтому обе коллекции по-прежнему содержат ссылки на одни и те же объекты:
const users = [
{ name: "Alex", age: 30 },
{ name: "Bob", age: 20 },
];
const sorted = users.toSorted(
(a, b) => a.age - b.age
);
console.log(
sorted[0] === users[1]
);
// true
Поэтому изменение самого объекта будет видно через обе коллекции:
sorted[0].age = 25;
console.log(users[1].age);
// 25
Похожая логика есть у
toReversed() и toSpliced(): они позволяют выполнить соответствующую операцию без изменения исходного массива, в отличие от мутирующих reverse() и splice().sort() и toSorted() — это не просто вопрос синтаксиса, это выбор контракта операции: изменить существующий массив или получить новый, сохранив исходный. Чем больше у массива потребителей, тем важнее эта разница.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9👍6🤝4❤1
Метод
filter() используется для отбора элементов массива по заданному условию. Результатом выполнения является новый массив, при этом исходный массив не изменяется.На изображении — синтаксис метода, аргументы callback-функции и примеры фильтрации чисел и объектов.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥5🤝3
Почему JavaScript может изменить большой ID из API
Числовой идентификатор может быть корректно сформирован на сервере, но изменить значение после преобразования в js
Причина заключается в модели представления чисел:
Граница гарантированно точного представления целых чисел определяется константой
Безопасный диапазон составляет от
Для точных операций с большими целыми в js существует
С API возникает дополнительная граница — десериализация JSON. Сам JSON может содержать последовательность цифр
Если значение находится за пределами безопасного целочисленного диапазона, точность теряется уже во время этого преобразования:
После такого парсинга восстановить исходный ID по полученному
Поэтому идентификаторы, над которыми не предполагается арифметика, надёжнее передавать в JSON как строки. Это сохраняет исходную последовательность цифр независимо от числовых ограничений JavaScript-клиента:
Если значение всё же требуется использовать в точных целочисленных вычислениях, строку можно явно преобразовать в
🔥 Числовая природа ID не означает, что его следует представлять типом
📣 JS Ready | #практика
Числовой идентификатор может быть корректно сформирован на сервере, но изменить значение после преобразования в js
Number. Причина заключается в модели представления чисел:
Number соответствует IEEE 754 binary64 и не способен точно представить все целые числа произвольного размера:const id = 9007199254740993;
console.log(id);
// 9007199254740992
Граница гарантированно точного представления целых чисел определяется константой
Number.MAX_SAFE_INTEGER:console.log(Number.MAX_SAFE_INTEGER);
// 9007199254740991
console.log(
Number.isSafeInteger(9007199254740991)
);
// true
Безопасный диапазон составляет от
-(2^53 - 1) до 2^53 - 1. За его пределами проблема заключается не просто в том, что число становится «слишком большим»: соседние математические целые уже не обязаны иметь разные представления в Number. Поэтому два разных исходных значения могут оказаться одним и тем же значением javascript:const a = 9007199254740992;
const b = 9007199254740993;
console.log(a === b);
// true
Для точных операций с большими целыми в js существует
BigInt. Суффикс n создаёт значение непосредственно как BigInt, без промежуточного преобразования в Number:const id = 9007199254740993n;
console.log(id + 1n);
// 9007199254740994n
С API возникает дополнительная граница — десериализация JSON. Сам JSON может содержать последовательность цифр
9007199254740993 как число, но стандартный JSON.parse() преобразует JSON-число в JavaScript Number. Если значение находится за пределами безопасного целочисленного диапазона, точность теряется уже во время этого преобразования:
const data = JSON.parse(
'{"id":9007199254740993}'
);
console.log(data.id);
// 9007199254740992
После такого парсинга восстановить исходный ID по полученному
Number в общем случае невозможно: информация о первоначальном целом уже потеряна.Поэтому идентификаторы, над которыми не предполагается арифметика, надёжнее передавать в JSON как строки. Это сохраняет исходную последовательность цифр независимо от числовых ограничений JavaScript-клиента:
const data = JSON.parse(
'{"id":"9007199254740993"}'
);
console.log(data.id);
// "9007199254740993"
Если значение всё же требуется использовать в точных целочисленных вычислениях, строку можно явно преобразовать в
BigInt уже после получения ответа API:const data = JSON.parse(
'{"id":"9007199254740993"}'
);
const id = BigInt(data.id);
console.log(id + 1n);
// 9007199254740994n
Number. Если идентификатор потенциально превышает Number.MAX_SAFE_INTEGER, его представление становится частью корректности API-контракта: строка сохраняет ID без потерь при передаче, а BigInt подходит там, где действительно требуется точная целочисленная арифметика.Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍8🔥6
Разбираем Web Workers, с помощью которых можно выполнять ресурсоёмкие задачи вне основного UI-потока: запускать фоновые вычисления, передавать данные между main thread и Worker, получать результаты, обрабатывать ошибки и управлять завершением Worker. Используется для тяжёлых вычислений и обработки больших объёмов данных без блокировки интерфейса.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍9🔥5
Выполняйте независимые операции без эффекта домино!
Если несколько операций не зависят друг от друга,
Для независимых задач используйте
Что полезно для пакетной отправки, фоновых задач и загрузки независимых данных: ошибка одной операции не скрывает успешные результаты остальных.
🔥 Если операции независимы, не позволяйте одной ошибке скрывать результат остальных — обрабатывайте каждую отдельно.
📣 JS Ready | #совет
Если несколько операций не зависят друг от друга,
Promise.all() может создать лишнюю связанность: одна ошибка отклоняет общий Promise, хотя остальные задачи продолжат выполняться и могут успешно завершиться.const users = await Promise.all(
ids.map(id => loadUser(id))
);
Для независимых задач используйте
Promise.allSettled() — он дождётся каждой и сохранит результат отдельно:const results = await Promise.allSettled(
ids.map(id => loadUser(id))
);
const successful = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
Что полезно для пакетной отправки, фоновых задач и загрузки независимых данных: ошибка одной операции не скрывает успешные результаты остальных.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍5🤝3❤1
В этой статье:
• Показывается, как с нуля собрать браузерного тамагочи на HTML, CSS и JavaScript — без фреймворков, npm и сборщиков;• Разбираются игровое состояние, жизненный цикл питомца, случайные события, мини-игра, коллекция и сохранение прогресса через localStorage;• Объясняется, как рисовать пиксельных персонажей программно через Canvas и создавать звуки через Web Audio API без готовых изображений и аудиофайлов.🔊 Продолжайте читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥7👍5🤝2
Например,
REST используют для построения API, WebSocket — для двустороннего обмена данными, а gRPC — для взаимодействия между сервисами.На картинке — основные технологии и подходы, с которыми регулярно сталкиваются разработчики: REST, Webhooks, GraphQL, SOAP, WebSocket, gRPC, MQTT, AMQP, SSE, EDI и EDA. Кратко и наглядно показано, чем они отличаются, как работают и для каких задач применяются.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤5🤝5🔥2
HTTP передаёт данные между клиентом и сервером без шифрования, а HTTPS добавляет TLS — он аутентифицирует сервер, согласовывает ключи и защищает дальнейший обмен данными шифрованием.
На картинке наглядно показано, чем отличается обычное HTTP-соединение от HTTPS: от TCP-handshake до установки защищённого соединения и передачи данных.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤5🔥4🤝3