惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

AI
AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
D
Docker
Last Week in AI
Last Week in AI
Apple Machine Learning Research
Apple Machine Learning Research
WordPress大学
WordPress大学
F
Full Disclosure
博客园 - 【当耐特】
博客园 - 司徒正美
V
Visual Studio Blog
F
Fortinet All Blogs
T
Tor Project blog
T
Threatpost
Blog — PlanetScale
Blog — PlanetScale
月光博客
月光博客
C
Cyber Attacks, Cyber Crime and Cyber Security
阮一峰的网络日志
阮一峰的网络日志
GbyAI
GbyAI
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Tenable Blog
M
MIT News - Artificial intelligence
L
Lohrmann on Cybersecurity
P
Palo Alto Networks Blog
I
Intezer
Stack Overflow Blog
Stack Overflow Blog
The Register - Security
The Register - Security
The Last Watchdog
The Last Watchdog
S
Securelist
T
Tailwind CSS Blog
V
Vulnerabilities – Threatpost
U
Unit 42
博客园 - 叶小钗
P
Proofpoint News Feed
C
Cybersecurity and Infrastructure Security Agency CISA
H
Help Net Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
酷 壳 – CoolShell
酷 壳 – CoolShell
Hacker News - Newest:
Hacker News - Newest: "LLM"
NISL@THU
NISL@THU
J
Java Code Geeks
H
Hackread – Cybersecurity News, Data Breaches, AI and More
SecWiki News
SecWiki News
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Project Zero
Project Zero
T
The Exploit Database - CXSecurity.com
TaoSecurity Blog
TaoSecurity Blog
A
Arctic Wolf
Martin Fowler
Martin Fowler
T
Threat Research - Cisco Blogs
N
News | PayPal Newsroom

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
gerpo: repository pattern для Go через указатели, без struct tags и кодогенерации
Insei · 2026-04-27 · via Все публикации подряд на Хабре

Я пять лет писал на .NET, и там у меня сложилась привычка держать доменную модель отдельно от инфраструктуры хранения. Repository pattern — не как догма из книги Фаулера, а как рабочий способ не тащить DbContext, маппинги и названия колонок в сущности. Домен остаётся доменом. Когда я перешёл на Go, меня сразу царапнули struct tags. Большинство библиотек работы с БД ожидает примерно такое:

type User struct {
    ID    uuid.UUID `db:"id"`
    Email string    `db:"email"`
    Age   int       `db:"age"`
}

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


Готового решения под такую постановку я не нашёл. Поэтому написал своё — gerpo. Это проект из моих потребностей и вкусов. Я не претендую, что он лучше существующих инструментов, я просто его сделал для себя и решил им поделится, 20 апреля 2026 года вышла версия 1.0.0, и я решил, что пора рассказать про неё публично. Я разрабатывал её довольно долго, с 2024 года - редко и потихоньку, но нашел силы довести до v1.0.0.

Парочка примеров, как это выглядит

Конфигурация репозитория под модель User.

type User struct {
    ID        uuid.UUID
    Email     *string
    Age       int
    CreatedAt time.Time
}

repo, err := gerpo.New[User]().
    Adapter(pgx5.NewPoolAdapter(pool)).
    Table("users").
    Columns(func(m *User, c *gerpo.ColumnBuilder[User]) {
        c.Field(&m.ID).OmitOnUpdate()
        c.Field(&m.Email)
        c.Field(&m.Age)
        c.Field(&m.CreatedAt).OmitOnUpdate()
    }).
    Build()

Никаких тегов в структуре. Никакого go generate. Модель остаётся чистой: четыре поля, четыре типа, ничего лишнего. Всё знание про то, как User кладётся в таблицу users, лежит в одном Columns(…)-блоке.

Запрос на выборку:

adults, _ := repo.GetList(ctx, func(m *User, h query.GetListHelper[User]) {
    h.Where().Field(&m.Age).GTE(18)
    h.OrderBy().Field(&m.CreatedAt).DESC()
    h.Page(1).Size(20)
})

Снова те же &m.Age, &m.CreatedAt. Это не переменные и не лямбда-выражения из C#; это буквальные указатели на поля структуры, полученные через &. Вся магия в том, что gerpo умеет сопоставить такой указатель с колонкой, которую вы сконфигурировали при сборке репозитория.
P.S. Ну вы понимаете откуда и почему такой стиль и способ - корни видны :)

Центральная идея — field pointers

На этапе Build() gerpo один раз обходит структуру User и запоминает смещение каждого поля через unsafe.Offsetof. Когда потом в запросе приходит &m.Age, gerpo смотрит на смещение и находит ту самую колонку, которую вы привязали к полю в Columns(…). Это не reflection на каждый запрос — на горячем пути идёт только указательная арифметика.Я специально не буду в этой статье копаться в том, как устроен внутренний модуль fmap, как работает unsafe.Offsetof на разных Go-версиях, как обеспечена безопасность при pointer receiver. Это отдельная история, и она потянет на вторую статью. Здесь важно одно: указатель на поле — первоклассный объект в API gerpo. Им идентифицируют колонку в Columns, в Where, в OrderBy, в Only/Exclude. Один и тот же лексический элемент — &m.X — везде означает одно и то же. Нет переключения между "строковым именем колонки" и "именем поля" — это теперь одно целое.

Что это даёт на практике

Компилятор ловит удаление поля. Если я уберу Age из User, все строки с &m.Age перестанут компилироваться. Для struct-tag-библиотеки это ошибка рантайма, вы должны пройти все места sql генерации у удалить эту колонку и там, и если что-то забыто - всё просто перестанет работать где-то после деплоя.

Линтер ловит смену типа. Если я поменяю Age int на Age string, тип указателя &m.Age меняется. Дальше помогает gerpolint — статический анализатор, который я написал специально для этого: он ловит Field(&m.Age).EQ("18"), когда поле int, и Field(&m.Age).Contains("x"), когда Contains применяется к нестроковому полю, и пр. кейсы, выдавая ожидаемые ошибки. Компилятор сам такой вызов пропустит, потому что EQ принимает any. Линтер — нет. Да это известное ограничение Generics в Go. Я решил обойти его тулингом линтера.

Нет тегов, нет кодогенерации. Структура чистая, go generate не нужен. IDE-рефакторинг переименования работает штатно, потому что &m.Age — обычная Go-ссылка на поле.И здесь я обязан честно сказать про компромиссом с которым придется считаться, но и на этот условий тоже найдется фикс:

Переименование поля меняет имя колонки. По умолчанию gerpo берёт snake_case от имени Go-поля. Если вы переименуете Age в YearsOld, колонка в запросе станет years_old. Это поведение convention over configuration: удобное, но ловушка, если вы переименовываете поле и миграция не переименовала колонку в БД. Компилятор здесь молчит.Для стабильности есть явное имя:
c.Field(&m.Age).WithColumnName("age")

Я держусь такого правила: для production-таблиц всегда ставлю .WithColumnName(...). В учебных примерах и в examples/todo-api/ полагаюсь на конвенцию — там переименование структуры и колонки должны идти синхронно, и это упрощает чтение. Выбор — ваш. Но тезис "рефакторинг полностью безопасен" — неправда, есть ложка дегтя в бочке с мёдом.

Важные ограничения

SQL, который gerpo на выходе генерирует, — PostgreSQL-шейп: плейсхолдеры $1, RETURNING, оконные функции, CAST(? AS text) в LIKE. Версия 1.0 поддерживает только PostgreSQL (и PG-совместимые базы — CockroachDB и подобные — "drop-in", без формального тестирования). Multi-dialect — в бэклоге.

Инфраструктурный оверхед над чистым pgx. На реальном PostgreSQL gerpo добавляет порядка 8% latency и около x2 аллокаций. В абсолютных числах — одна микросекунда на операцию, при запросе, который идёт 50–500 µс через сеть, это 0.2–2%. На горячих путях с сотнями тысяч RPS, где каждая аллокация критична, чистый pgx будет предпочтительнее. В умеренных нагрузках разница в шуме.

Что дальше

Первая публичная версия gerpo стабильна, с документацией и runnable-примером. Дальше я планирую несколько статей написать, если будет интересно:

  • Deep dive в fmap. Как на самом деле устроен механизм field pointers, почему unsafe.Offsetof безопасен в том контексте, в котором gerpo его использует, что происходит при embedded-полях и указателях.

  • gerpolint. Статический анализатор для WHERE-фильтров уже доступен как отдельный бинарь и как плагин к golangci-lint v2. Как он устроен, какие правила (GPL001–GPL006) реализует, как подключить в CI.

Ссылки для тех, кто захочет пощупать:

Если у вас есть вопросы, кейсы, где gerpo упадёт, или замечания к API — я читаю issues и благодарен за любой фидбек.