Бизнес блог #1
Выжимаю книги до самой сути.
? Реклама - @jaMasha
? Хотите свою книгу? Мы напишем её за вас и сделаем книгу бестселлером. Подробности в боте @Summary_library_bot
? Оставьте след в истории с помощью книги
https://expert-book.pro
Фильмы и сериалы со всей планеты. Мы знаем, что посмотреть, где посмотреть и на что сходить в кино.
Last updated 2 days, 10 hours ago
Все материалы размещены по партнёрской програме ivi.ru | All materials are posted on the partner program ivi.ru
По всем вопросам: @kuzr103
Купить рекламу: https://telega.in/c/k1noxa103
Основной канал: https://t.me/kino_hd2
Last updated 2 weeks, 1 day ago
⚡Взаимно-рекурсивные внешние ключи
Самый частый юзкейс:
- Есть главная сущность
- Есть дочерние сущности
- Главная сущность ссылается на конкретную дочернюю
Пример: у вопроса может быть несколько ответов, но лишь один корректный:
```
create table questions (
id bigserial not null primary key,
correct_answer_id bigint not null,
question varchar not null
);
create table answers (
id bigserial not null primary key,
question_id bigint not null,
answer varchar not null
);
alter table questions
add foreign key (correct_answer_id) references answers (id);
alter table answers
add foreign key (question_id) references questions (id);
```
Как тут могут помочь CTE:
Исходя из модели данных, при создании нового вопроса нам сразу же нужно создать для него правильный ответ. Если это делать последовательно, то мы получим конфликт, поскольку при создании одной сущности вторая должна быть уже создана, и наоборот
Но можно воспользовать тем, что CTE проверяет констрейнты лишь после полного своего выполнения и сделать вставку за один запрос:
with question\_id as (select nextval('questions\_id\_seq')),
answer\_id as (
insert
into answers (answer, question\_id)
values ('Some answer', (select * from question\_id)) returning id)
insert
into questions (id, correct\_answer\_id, question)
values ((select * from question\_id), (select * from answer\_id), 'Some question’);
Как вы считаете, стоит ли усложнять модель данных в БД взаимно-рекурсивными ключами, или подобные инварианты должны поддерживаться бизнес-логикой?
⚡Каскадное удаление за один запрос
Нет, речь не про on delete cascade
Во многих случаях мы не хотим использовать внешние ключи с каскадным удалением, как минимум потому что они существенно повышают цену ошибки - когда случайно удаляется одна сущность и каскадно пол базы
Но как тогда быть, если у нас достаточно глубокая иерархия сущностей, например
a <- b <- c
и хочется по a_id удалить все связанные b и c?
```
create table a (
id bigserial not null primary key
);
create table b (
id bigserial not null primary key,
a_id bigint not null references a (id)
);
create table c (
id bigserial not null primary key,
b_id bigint not null references b (id)
);
```
Наивное решение:
- поселектить из b по a_id
- удалить записи из c по полученным b_id
- удалить записи из b
- удалить записи из a
В итоге имеем 4 раудтрипа до базы и сложную логику, которая с ростом иерархии будет выглядить еще более громоздкой
Решение через CTE:
with a\_deleted as (
delete from a where id = <a\_id> returning *
), b\_deleted as (
delete from b where a\_id in (select id from a\_deleted) returning *
) delete from c where b\_id in (select id from b\_deleted)
Почему это работает? CTE трактуется как единый стейтмент, соответственно все проверки констрейнтов будут осуществляться только после его полного выполнения
Это нам позволяет удалить все связанные с таблицей a сущности за один раундтрип до базы, и при необходимости легко масштабировать этот подход, просто добавляя новые строки в CTE, если иерархия вырастет
Ставьте ? на этот пост, если нужен рассказ про еще один интересный вариант применения CTE в контексте внешних ключей
⚡Школы unit-тестирования
Лондонская:
- Все зависимости (изменяемые) заменяются на моки
- Под юнитом подразумевается один класс
- Акцент на проверку взаимодействия компонентов
Пример:
@Test
fun `test user creation`() {
// given
val userSaver = mock<UserSaver>()
val userService = UserService(userSaver)
// when
userService.createUser("username")
// then
verify(userSaver).save(any<User>())
}
Классическая:
- За редким исключением только внепроцессные зависимости (БД, брокер) заменяются на моки
- Под юнитом может подразумеваться набор классов
- Акцент на проверку результатов вызова методов и состояния
Пример:
@Test
fun `test user creation`() {
// given
val userService = UserService(UserSaver())
// when
userService.createUser("username")
// then
assertEquals(User(“username”), userService.getUser(“username”))
}
Лондонская школа позволяет разрабатывать в стиле TDD, начиная с высокоуровневых тестов, поскольку нам не требуются реальные зависимости. И проверяют какой-то аспект поведения класса (что был вызов UserSaver::save при вызове UserService::createUser). В классической школе зачастую проверяется корректная работа конкретного сценария (что UserService::createUser корректно сохранил пользователя).
А какую школу используете вы и почему?
⚡Testcontainers
❓Проблема:
В рамках интеграционного тестирования хочется не мокать, а честно поднимать зависимости, такие как БД, брокер сообщений и т.д.
✅ Решение:
Testcontainers - фреймворк, позволяющий запускать контейнеры с различными зависимостями прямо из кода.
Пример на Java:
```
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(
"postgres:15-alpine"
);
@BeforeAll
static void beforeAll() {
postgres.start();
}
@AfterAll
static void afterAll() {
postgres.stop();
}
```
На данный момент поддержано более 50 заранее сконфигурированных контейнеров, включая Postgres, Kafka, Elasticsearch, MySQL.
И сама реализация фреймворка существует для .NET, Go, Java, Node.js, также есть кастомные пользовательские реализации для многих других языков.
Ставьте ? на этот пост, если нужен рассказ про возможности Temporal
temporal.io
Open Source Durable Execution
Build invincible apps with Temporal's open-source durable execution platform to guarantee successful execution, even in the presence of failures.
⚡Asynchronous Request-Reply pattern
❓Проблема:
На бэкенде есть асинхронное апи запуска какой-то долгой операции, а клиент хочет получить результат этой операции. Делать апи синхронным - не вариант с точки зрения архитектуры.
✅ Решение:
Использовать поллинг на клиенте:
- Клиент идет в апи запуска операции
- Бэкенд отдает operationId
- Клиент раз в какое-то время идет в эндпоинт проверки статуса операции по operationId
- Когда операция завершится, бэкенд отдает uri, по которому можно найти результат операции (либо клиент сам знает, куда сходить)
Это один из самых простых вариантов решения проблемы на чистом http в случае если, например, в проект не хочется затаскивать вебсокеты.
⚡Reverse Proxy
Тип прокси-сервера, который стоит перед группой серверов, и обеспечивает, что все запросы, адресованные этим серверам, проходят через него.
Возможные применения:
- Балансировка нагрузки: прокси будет равномерно распределять нагрузку на стоящие за ним серверы
- Rate-limiting: например, ограничения общего числа запросов в секунду (1k RPS) и ограничение числа запросов от одного ip адреса (20 RPS)
- Кеширование контента: например, чтобы напрямую клиенту отдавать какой-то статический контент, не совершая запрос к серверу
- Производить TLS шифрование/дешифрование, снимая нагрузку с целевых серверов
Без reverse-proxy пришлось бы дублировать всю эту логику на каждом сервере, а также раскрывать внутреннюю структуру сети, чтобы клиенты могли делать запросы напрямую целевым серверам.
Бизнес блог #1
Выжимаю книги до самой сути.
? Реклама - @jaMasha
? Хотите свою книгу? Мы напишем её за вас и сделаем книгу бестселлером. Подробности в боте @Summary_library_bot
? Оставьте след в истории с помощью книги
https://expert-book.pro
Фильмы и сериалы со всей планеты. Мы знаем, что посмотреть, где посмотреть и на что сходить в кино.
Last updated 2 days, 10 hours ago
Все материалы размещены по партнёрской програме ivi.ru | All materials are posted on the partner program ivi.ru
По всем вопросам: @kuzr103
Купить рекламу: https://telega.in/c/k1noxa103
Основной канал: https://t.me/kino_hd2
Last updated 2 weeks, 1 day ago