Содержание урока · 13 разделов
- ent limit offset: что именно делает пара Limit и Offset
- Order: несколько полей и тай-брейкер
- Математика страниц: Count
- Что кладут в пагинационный ответ
- Глубокие страницы: чем платит OFFSET
- Курсорная пагинация: страница как граница
- Курсор по не-id колонке: составной ключ
- Сортировка по числу соседей: обещание девятого урока
- Offset против курсора: сводка выбора
- Что дальше
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Маршрут урока — от наивной страницы к промышленной. Первый сюжет — ent пагинация в механике Limit и Offset: что именно они пишут в билдер и в SQL, и почему страница — это всегда тройка «сортировка, окно, исполнение». Второй — order ent в развёрнутом виде: несколько полей за один вызов, стабильность порядка и тай-брейкер по id, без которого страницы начинают дублировать и терять строки. Третий — подсчёт страниц через Count и форма пагинационного ответа, которую ждёт любой фронтенд. Четвёртый — цена глубоких offset-ов: сколько строк база читает и выбрасывает, и почему данные, изменившиеся между запросами, сдвигают окна. Пятый — курсорная пагинация: страница как граница, курсор по первичному ключу и составной курсор по паре «время, идентификатор». Шестой — обещание девятого урока: упорядочивание по числу соседей-рёбер через Modify. Финиш — таблица выбора между offset и курсором. Данные прежние: анна (26 лет, два поста — «про рёбра» и «черновик»), борис (19), вера (17), группа go-ninja.
ent limit offset: что именно делает пара Limit и Offset#
С девятого урока вы знаете, что Limit(n) и Offset(k) копятся в билдере так же, как предикаты Where, — и исполняются только вместе с All(ctx). Теперь достроим механику страницы целиком. В SQL запрос с пагинацией состоит из трёх обязательных частей: ORDER BY задаёт порядок строк, LIMIT отрезает окно, OFFSET пропускает начало. Уберёте сортировку — получите окно поверх недетерминированного порядка, и на каждой перезагрузке страницы состав может гулять; забудете OFFSET — каждый раз вернётся первая порция. ent тут без волшебства: Limit и Offset — прозрачные методы, которые рендерятся в одноимённые клаузы SQL, и за содержимое окна отвечает ваша связка с Order. Нюанс из девятого урока вспоминаем одной строкой: OFFSET без LIMIT в SQL не живёт, и ent при заданном offset сам подставляет лимит-максимум — это было в генераторе, страница вам его повторять не будет. Отсюда рабочее правило ent limit offset: любую страницу собирайте полной тройкой — Order, Limit, Offset — и никогда не полагайтесь на «естественный» порядок таблицы: его не существует ни в одной СУБД.
Первая страница пользователей
GO// страница 0: первые десять пользователей по убыванию возраста;
// при равном возрасте — по возрастанию id, чтобы порядок был строгим:
users, err := client.User.Query().
Order(ent.Desc(user.FieldAge), ent.Asc(user.FieldID)).
Limit(10).
Offset(0).
All(ctx)
if err != nil {
return err
}
// страница 1 — тот же порядок, окно сдвинуто:
page2, err := client.User.Query().
Order(ent.Desc(user.FieldAge), ent.Asc(user.FieldID)).
Limit(10).
Offset(10).
All(ctx)Оба вызова — один и тот же билдер с разными Offsets: предикатов нет, значит выборка — вся таблица users. Связка с одиннадцатым уроком прямая: With-вызовы и пагинация свободно смешиваются в одной цепочке — загрузить соседей можно и для окна из десяти строк, тариф жадной загрузки при этом не меняется, один запрос на ребро. Notice-момент: Offset(0) можно и не писать — дефолт нулевой, но явный ноль в коде страницы читается лучше.
Order: несколько полей и тай-брейкер#
Сортировка в ent — метод Order на query-билдере, и его аргументы — не строки, а хелперы корневого пакета: ent.Asc и ent.Desc. Каждый принимает вариативный список констант-колонок, поэтому два вида записи эквивалентны: Order(ent.Desc(user.FieldAge), ent.Asc(user.FieldID)) и Order(ent.Desc(user.FieldAge, user.FieldID)) — разве что направление во втором случае общее, а в первом своё на поле. Несколько вызовов Order на одном билдере складываются: последний добавляет поля в конец ORDER BY, а не затирает прежние — в этом отличие от перезаписи With-полей из одиннадцатого урока. Валидация та же, что вы видели у ent.Desc в девятом: выдуманное имя колонки отвергается до базы, потому что константы типа user.FieldName — это ключи ValidColumn, а не свободный текст. Теперь главное — тай-брейкер. ORDER BY age DESC для трёх пользователей вернёт однозначный порядок, а для тысячи — уже нет: при равных возрастах база вольна вернуть строки в любой последовательности, и она действительно меняет её от запроса к запросу. На одной странице это незаметно, на пагинации — беда: строка с возрастом 26 может попасть и в страницу 0, и в страницу 2, а какая-то — не попасть никуда. Лечение простое и обязательное: последним полем сортировки ставьте уникальную колонку — на каноне курса id: Order(ent.Desc(user.FieldAge), ent.Asc(user.FieldID)). Теперь каждая строка занимает строгое место, и окно OFFSET не может вернуть её дважды.
Проверка: как ляжет ORDER BY
Запрос: Order(ent.Desc(user.FieldAge), ent.Asc(user.FieldID)).Limit(10). Что именно гарантирует такая сортировка?
Математика страниц: Count#
Пользовательский интерфейс хочет больше, чем просто окно: «страница 2 из 7», «всего 63». Число страниц добывается тем же запросом, что и данные, минус окно: Count(ctx) из девятого урока — он переиспользует весь WHERE вашего билдера и вычищает ORDER BY, потому что сортировка внутри счётного запроса некоторым базам запрещена. Дальше арифметика: всего страниц — ceil(total / limit), последняя страница — та, где offset + limit >= total. Два практических замечания. Первое: Count — это отдельный поход в базу, и на больших таблицах он не бесплатен; для «показать ещё» с курсором он вообще не нужен, и ниже мы этим воспользуемся. Второе: между запросом Count и запросом страницы данные могли измениться — total честный только на момент своего запроса, и это норма всей клиент-серверной жизни, а не особенность ent. Собственно подсчёт не требует от билдера ничего нового: тот же Query, те же предикаты из десятого урока, другой терминальный метод. Комбинируя Count, Limit и Offset, вы получаете классический «нумерованный» пагинатор, которого достаточно для большинства админок и каталогов.
Нумерованный пагинатор на Count
GO// параметры страницы приходят снаружи:
const (
perPage = 10
page = 2 // третья страница, считая с нуля
)
// 1. сколько всего строк под фильтр (WHERE переиспользуется):
total, err := client.User.Query().
Where(user.AgeGTE(18)).
Count(ctx)
if err != nil {
return err
}
// 2. само окно — те же предикаты, плюс сортировка:
items, err := client.User.Query().
Where(user.AgeGTE(18)).
Order(ent.Desc(user.FieldAge), ent.Asc(user.FieldID)).
Limit(perPage).
Offset(page * perPage).
All(ctx)
pages := (total + perPage - 1) / perPage // ceil без математикиОба запроса несут один и тот же Where — фильтр согласован между счётом и содержимым. Целочисленный ceil (total + perPage - 1) / perPage — идиома без вызовов math. На каноне курса: взрослых двое — анна и борис, вере 17; при perPage = 1 total равен двум и pages тоже двум. Для пустого фильтра Count вернёт 0 — страниц нет, но и ошибки нет: «пусто» — данные, а не сбой.
Что кладут в пагинационный ответ#
- items — срез сущностей страницы; сюда же после запроса «пристёгиваются» рёбра With-вызовами из одиннадцатого урока.
- total — общее число строк под фильтр; добывается Count-ом с тем же WHERE; для курсорной пагинации часто опускается.
- page и perPage — эхо входных параметров; фронтенд сверяет их со своими стейтами, не доверяя молчанию.
- pages — ceil(total / perPage); для курсоров вместо него отдают hasNext.
- next и prev — ссылки или курсоры следующей и предыдущей страницы; курсор обычно кодирует сортировочный ключ последней строки.
Глубокие страницы: чем платит OFFSET#
У нумерованной пагинации есть цена, и растёт она линейно с номером страницы. OFFSET k в SQL означает: прочитать первые k + limit строк по порядку сортировки и выбросить первые k. Не «перейти» — прочитать и выбросить: база обязана пройти начало выборки каждый раз, потому что окно определяется позиционно. OFFSET 1990 при LIMIT 10 — две тысячи прочитанных строк, десять отданных; OFFSET 999900 — почти миллион. Индекс по сортировочному полю превращает это из «прочитать таблицу» в «пройти по индексу», но константа всё равно остаётся: глубина страницы — это время. Вторая беда тоньше — дрейф. Между запросом страницы 0 и страницы 1 кто-то вставил нового пользователя — все окна сместились на одну строку: анна, стоявшая последней на странице 0, появится снова первой на странице 1. Удалили строку — часть строк страницы 1 не попадёт ни в одну страницу. Тай-брейкер из прошлого блока делает порядок строгим, но не отменяет позиционности: OFFSET считает строки, а не значения. Поэтому в нагруженных списках — ленты, фиды, бесконечные прокрутки — нумерацию страниц заменяют курсорами, и это следующий сюжет. Если вы уже читали курс GORM, его семнадцатый урок показывал ту же развилку на другой ORM: синтаксис меняется, дилемма — нет.
Сколько строк прочитает база на глубокой странице
Прочитайте код и выберите, что он выведет:
// страница 199 при perPage = 10 (offset = 1990):
users, err := client.User.Query().
Order(ent.Asc(user.FieldID)).
Limit(10).
Offset(1990).
All(ctx)
// в таблице 300 000 пользователей.
// сколько строк база прочитает по порядку, чтобы отдать 10?Курсорная пагинация: страница как граница#
Курсорная (она же keyset) пагинация меняет саму постановку вопроса. Вместо «дай строки 20–29» клиент просит «дай следующие 10 строк после вот этой» — и «вот эта» кодируется значением сортировочного ключа последней полученной строки. Для сортировки по возрастанию id страница — это WHERE id > lastID в паре с ORDER BY id ASC и LIMIT: предикат IDGT из каталога десятого урока. Вся конструкция опирается на индекс по первичному ключу, поэтому каждый переход между страницами стоит одинаково — глубина страницы перестала существовать. Дрейф тоже исчезает: строки вставляются с растущими id, и новая запись не сдвигает чужие окна — она либо попадёт в конец ленты, либо нет, но уже выданные страницы не перетасуются. Плата за это — два отказа. Первый: нельзя прыгнуть на произвольную страницу, потому что «страница 7» не определена без позиции; курсорные интерфейсы — это «показать ещё» и стрелки, а не нумерованные списки. Второй: без Count вы не знаете общего числа строк — либо считайте отдельным запросом, либо честно скажите дизайнеру, что «всего 63» в бесконечной ленте никому не нужно. Курсор — это просто значение, которое клиент вернёт вам в следующем запросе; сериализуете вы его в base64 или отдаёте голым числом — ваше API-решение, ent ограничивается предикатом.
Курсор по первичному ключу
GO// первая страница: обычное окно без предиката:
page1, err := client.User.Query().
Order(ent.Asc(user.FieldID)).
Limit(10).
All(ctx)
// курсор — id последней строки страницы:
cursor := page1[len(page1)-1].ID
// следующая страница: всё после границы, окно того же размера:
page2, err := client.User.Query().
Where(user.IDGT(cursor)).
Order(ent.Asc(user.FieldID)).
Limit(10).
All(ctx)
// трюк «limit+1»: запросить 11, если вернулось 11 —
// hasNext = true, а одиннадцатую строку не отдаёмIDGT — обычный сгенерированный предикат where.go, тот же класс, что AgeGTE из десятого урока: просто здесь он сравнивает первичный ключ. Запрос «следующей страницы» настолько же дёшев, насколько дёшев lookup по индексу, — и одинаков на любой глубине. Трюк limit+1 решает вопрос hasNext без Count: запросите на одну строку больше, увидели переполнение — есть продолжение. Канон: анна id 1, борис id 2, вера id 3 — курсор 2 вернёт страницу с одной верой при LIMIT 10.
Курсор по не-id колонке: составной ключ#
Сортировать только по id — редкая роскошь: ленты обычно упорядочены по времени. И здесь подстерегает ловушка, которая ломает наивные курсоры: created_at не уникален. Два поста, написанные в одну секунду, — норма; курсор вида «после 12:00:00» с WHERE created_at > t молча выбросит второй пост с тем же временем. Лечение — составной курсор: сортируем по паре (created_at, id) и границей считаем тоже пару. Условие «строка строго после границы» читается как: время меньше по значению ИЛИ время то же, но id больше — в предикатах ent это user.Or(user.CreatedAtLT(cursor.Time), user.And(user.CreatedAtEqual(cursor.Time), user.IDGT(cursor.ID))). Запись многословная, но логика — ровно сравнение кортежей, как в математике: (t1, id1) > (t2, id2) означает t1 > t2 ИЛИ (t1 = t2 И id1 > id2). База такой WHERE исполняет по индексу (created_at, id) — составной индекс из двадцатого урока для этого и рождается. Ошибка «взять только время» проявляет себя не сразу: пока у вас одна строка в секунду, всё работает; под нагрузкой посты начинают теряться. Запомните правило: курсор уникален тогда и только тогда, когда сортировочный ключ уникален; не уникален — достраивайте состав.
Проверка: зачем в курсоре второе поле
Лента отсортирована по created_at DESC. Почему одного предиката CreatedAtLT(t) для следующей страницы мало?
Составной курсор на (created_at, id)
GO// лента постов анны по убыванию времени:
page1, err := client.Post.Query().
Order(ent.Desc(post.FieldCreatedAt), ent.Desc(post.FieldID)).
Limit(5).
All(ctx)
// курсор — кортеж последней строки:
last := page1[len(page1)-1]
// следующая страница: строго «после» кортежа:
page2, err := client.Post.Query().
Where(post.Or(
post.CreatedAtLT(last.CreatedAt),
post.And(
post.CreatedAtEqual(last.CreatedAt),
post.IDGT(last.ID),
),
)).
Order(ent.Desc(post.FieldCreatedAt), ent.Desc(post.FieldID)).
Limit(5).
All(ctx)Сортировка и курсор зеркалят друг друга: DESC по обоим полям — и предикат «строго после» по тем же полям. Направления сортировки и знаки сравнения обязаны совпадать: поменяете порядок на ASC, а курсор оставите — страницы поедут в другую сторону, и границы перестанут значить то, что вы думали. Число запросов на страницу не изменилось: один SELECT на данные, плюс отдельный COUNT, если UI настаивает на «всего».
Сортировка по числу соседей: обещание девятого урока#
Девятый урок оставил вам обещание: покажем, как упорядочить пользователей по числу их постов. Готового API у ent для этого нет — Order принимает только колонки, а «число соседей» колонкой не является. Два рабочих пути. Первый — денормализация: поле posts_count в схеме, которое поддерживают хуки или групповые обновления; сортировка превращается в обычный Order(ent.Desc(user.FieldPostsCount)) и достаётся бесплатно при любом объёме, но требует дисциплины — счётчик надо не забывать двигать при создании и удалении постов, за этим к семнадцатому уроку. Второй путь — Modify из feature-флага sql/modifier (флаги включались в седьмом уроке): он даёт доступ к финальному SELECT-у через sql.Selector, где можно докрутить JOIN, GROUP BY и выражение сортировки. Это уже почти ручной SQL — с типобезопасностью билдера на корневом запросе, но с полной свободой внутри модификатора. Такой запрос стоит держать под присмотром: план с GROUP BY по большой таблице дороже любого курсора, и для «топа авторов» честнее посчитать денормализованно. Показываю механику — решение о применении принимайте по своему объёму данных.
Modify: топ авторов по числу постов
GO// требует флага sql/modifier (седьмой урок):
users, err := client.User.Query().
Modify(func(s *sql.Selector) {
s.LeftJoin(sql.Table("posts")).
On(s.C(user.FieldID), post.FieldAuthorID).
GroupBy(s.C(user.FieldID)).
OrderBy(sql.Desc(sql.Count(post.FieldID)))
}).
All(ctx)
// SQL: SELECT users.* FROM users
// LEFT JOIN posts ON users.id = posts.author_id
// GROUP BY users.id
// ORDER BY COUNT(posts.id) DESCModify получает финальный селектор — тот самый, что обычно рендерится из билдера. LeftJoin с On по колонке ребра собирает пары «пользователь — его посты», GroupBy сворачивает их в пользователя, OrderBy по COUNT расставляет по активности. У веры ноль постов — LEFT JOIN гарантирует, что она останется в списке с нулём, в отличие от INNER JOIN. GROUP BY по первичному ключу легален в PostgreSQL (функциональная зависимость) и MySQL; всё же лишнее в SELECT ent сам вычищать не станет — проверяйте план запроса на реальном объёме.
Offset против курсора: сводка выбора#
| Критерий | Limit + Offset | Курсор (keyset) |
|---|---|---|
| Произвольный переход | да: страница N вычисляется offset = N * perPage | нет: только «следующая» и «предыдущая» |
| Стоимость глубокой страницы | растёт с offset: база читает и выбрасывает offset + limit строк | константная: предикат по индексу, глубина не важна |
| Дрейф при вставках и удалениях | окна сдвигаются: строки дублируются и теряются между страницами | новые строки не сдвигают уже выданные страницы |
| Общее число строк | из коробки через Count с тем же WHERE | только отдельным Count или никак |
| Требования к порядку | обязателен тай-брейкер по уникальной колонке | курсор уникален ровно настолько, насколько уникален ключ |
| Типовые применения | админки, каталоги, отчёты с номерами страниц | ленты, фиды, бесконечные прокрутки, API для мобильных клиентов |
Проверка: выбрать стратегию
Мобильный клиент бесконечно скроллит ленту постов, вставки идут каждую секунду, номера страниц в дизайне нет. Что выбрать?
Соберите курсорную страницу
Впишите сортировку по убыванию id, предикат курсора, размер окна и подсчёт страниц.
Путь одной курсорной страницы
Соберите жизненный цикл курсорной страницы ленты: что происходит и в каком порядке от запроса клиента до готового ответа.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Runner-ов в курсе ent нет, и песочницы не будет и здесь: пагинации нужны настоящий драйвер и база — правило всего курса. План проверки: возьмите базу из домашек восьмого урока, включите лог SQL драйвера и исполните каждую страницу из урока. Считайте прочитанные строки на глубоком OFFSET — сравните план запроса страницы 0 и страницы 199; продублируйте дрейф: вставьте пользователя между двумя запросами страниц и найдите его дважды; соберите составной курсор по (created_at, id) и убедитесь, что два поста с одним временем не теряются. Каждый шаг должен быть виден в логе драйвера — ent ничего не делает за кадром.
Что дальше#
Вы умеете резать выборку на страницы обоими способами: нумерованные окна с честной математикой Count и keyset-курсоры, которые не боятся ни глубины, ни вставок; сортировка теперь в вашем распоряжении целиком — от мультиполей с тай-брейкером до сортировки по числу соседей. Но всё, что вы вернули клиенту, он пока может только читать: строки было нечем менять. Двенадцатый урок закрыл чтение, тринадцатый — 13-update — откроет запись: UpdateOneID с Set-мутаторами, массовое обновление по предикатам и возврат обновлённой сущности, а в придачу и upsert-развилка, чьи азы лежат в восьмом уроке. Домашнее задание этого урока — перевести свой список пользователей с «всё на одной странице» на обе стратегии и почувствовать разницу в логе драйвера. Если понадобится вспомнить каталог предикатов для курсоров — он в десятом уроке, а механика жадной загрузки соседей на странице — в одиннадцатом. Полный план из 25 уроков — на странице курса.
Карточки: пагинация и сортировка
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Пагинация в ent — это тройка Order, Limit, Offset поверх любого билдера: предикаты десятого урока фильтруют, окно отрезает LIMIT, порядок задаёт ORDER BY. Ent limit offset рендерится в одноимённые SQL-клаузы без магии; OFFSET без LIMIT ent достраивает лимитом-максимумом.
- Сортировка order ent: ent.Asc и ent.Desc принимают вариативные списки колонок, несколько вызовов Order складываются в конец ORDER BY. Валидация колонок происходит до базы. Тай-брейкер обязателен: без уникального поля в конце строки дублируются и теряются между страницами.
- Подсчёт страниц — Count(ctx) с тем же WHERE: предикаты переиспользуются, ORDER BY вычищается. pages = ceil(total / perPage). Count — отдельный запрос, честный только на свой момент времени.
- Глубокие offset-ы читают и выбрасывают offset + limit строк — стоимость растёт линейно с номером страницы; вставки и удаления сдвигают позиционные окна (дрейф). Курсорная пагинация убирает обе беды ценой отказа от произвольного перехода и total.
- Курсор — значение сортировочного ключа последней строки: Where(user.IDGT(cursor)) + Order + Limit. hasNext добывается трюком limit+1. Курсор уникален ровно настолько, насколько уникален ключ сортировки; для created_at нужен составной курсор Or(CreatedAtLT, And(CreatedAtEqual, IDGT)).
- Сортировка по числу соседей-рёбер готового API не имеет: либо денормализованный счётчик, поддерживаемый хуками, либо Modify из флага sql/modifier с LeftJoin, GroupBy и OrderBy(sql.Desc(sql.Count(...))).
- Выбор стратегии: нумерованные страницы — для админок и каталогов, курсоры — для лент и бесконечных прокруток. В GORM та же дилемма разобрана в его семнадцатом уроке: синтаксис меняется, механика — нет.
- Пагинация свободно комбинируется с жадной загрузкой из одиннадцатого урока: With-вызовы на странице из десяти строк стоят столько же запросов, сколько на всей таблице, — один на ребро.
Домашнее задание
Работайте на базе из восьмого урока — анна, борис, вера, их посты и группа go-ninja — и включите лог SQL драйвера. Первое: соберите нумерованный пагинатор по возрасту с тай-брейкером по id, perPage = 1, и проверьте математику страниц: adults — двое, значит pages = 2; пройдите обе страницы и убедитесь, что ни одна строка не повторилась. Второе: умышленно уберите тай-брейкер и поищите недетерминизм — на трёх пользователях его может не быть, тогда отсортируйте по role (у анны админ, у бориса и веры юзер) и посмотрите в лог, что именно база вернула при равных значениях. Третье: смоделируйте дрейф — прочитайте страницу 0 нумерованной пагинации, вставьте пользователя с возрастом больше всех, прочитайте страницу 1 и найдите дубль; затем повторите то же на курсоре по id и убедитесь, что уже выданные страницы не изменились. Четвёртое: соберите составной курсор по постам (created_at, id) — создайте два поста с одним временем и проверьте, что оба доезжают по разные стороны границы; сверьте с логом, что сортировка и предикат зеркальны. Пятое: посчитайте соотношение прочитанных и отданных строк на OFFSET 2 при трёх пользователях, затем доведите таблицу до хотя бы сотни строк CreateBulk-ом из восьмого урока и сравните план запроса страницы 0 и страницы 9. Вопрос на подумать: почему курсор по неуникальному полю «молча» теряет строки, и какие ещё пары полей на нашей схеме годятся в составной курсор?
Личные заметкиПусто
Вопросы и ответы по уроку
Можно ли в ent сделать пагинацию без сортировки?
Технически — да: Limit и Offset работают и без Order. Фактически — нет: без ORDER BY порядок строк не гарантирован ничем, и страницы будут дублировать и терять строки. Единственная безопасная сортировка без явного поля — по первичному ключу, потому что он уникален и индексирован; всё остальное требует тай-брейкера.
Как совместить курсорную пагинацию с фильтрами и жадной загрузкой?
Свободно: предикат курсора — обычный предикат, он складывается с Where из десятого урока через And, а With-вызовы одиннадцатого урока работают на любой выборке, включая окно страницы. Тариф не меняется: один запрос на данные плюс один на каждое запрошенное ребро. Следите только за тем, чтобы фильтры не менялись между страницами — иначе курсор отсчитывает строки в другой выборке.
Нужно ли всегда отдавать total и pages?
Нет, и часто это правильно. Count на больших таблицах — отдельный полноценный запрос, а в лентах «всего 63 214 постов» не имеет операционной ценности: данные изменились раньше, чем вы дочитали число. Нумерованные интерфейсы (админка, отчёт) без total не живут; курсорные интерфейсы обходятся hasNext через трюк limit+1. Решение принимает UX, а не ORM.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
go-kit: микросервисный тулкит
Курс go-kit: endpoint, транспорт, логирование, метрики, трейсинг, circuit breaker, service discovery. 25 уроков с итоговым микросервисом.
- Понимать архитектуру endpoint → transport → service
- Оборачивать сервисы middleware: логи, метрики, трейсинг
- Подключать HTTP и gRPC транспорты
- Ставить rate limiting и circuit breaker