{
  "name": "Тимур Турдыев",
  "role": "Backend / Full-stack разработчик",
  "location": "Москва, Россия",
  "since": 2014,
  "before_dev": {
    "ecommerce": "свой интернет-магазин",
    "seo": "техническая оптимизация, скорость, индексация, аналитика воронок"
  },
  "stack": [
    "Laravel",
    "Go",
    "Vue",
    "PostgreSQL",
    "Docker"
  ],
  "building": {
    "name": "Mercurio",
    "desc": "Своя платформа для интернет-магазинов: сырьё прайсов поставщиков хранится как есть, человек правит только решения, каталог собирается из них детерминированно. Приём прайсов профилями без кода под поставщика, поиск и фильтры на Manticore, обмен с 1С. У каждого клиента своя витрина, своя база и свой сервер, общее ядро - пакетом.",
    "url": "https://timcode.ru/cases/mercurio"
  },
  "open_source": [
    {
      "name": "mercurioplatform/tables",
      "desc": "list/table-движок для Laravel-админок",
      "registry": "packagist",
      "url": "https://github.com/mercurioplatform/tables"
    },
    {
      "name": "timurturdyev/simple-settings",
      "desc": "key-value настройки для Laravel",
      "registry": "packagist",
      "url": "https://github.com/TimurTurdyev/Simple-Settings"
    },
    {
      "name": "simple-db-settings",
      "desc": "тот же менеджер настроек для Python",
      "registry": "pypi",
      "url": "https://github.com/TimurTurdyev/simple-settings-py"
    },
    {
      "name": "timurturdyev/simple-cart",
      "desc": "корзина, закладки и сравнение",
      "registry": "packagist",
      "url": "https://github.com/TimurTurdyev/simple-cart"
    },
    {
      "name": "timurturdyev/simple-seo",
      "desc": "meta, Open Graph, Twitter/X и JSON-LD",
      "registry": "packagist",
      "url": "https://github.com/TimurTurdyev/simple-seo"
    }
  ],
  "contact": {
    "telegram": "https://t.me/borodatimur",
    "email": "borodatimur@gmail.com",
    "github": "https://github.com/TimurTurdyev"
  },
  "endpoints": {
    "curl timcode.ru": "Эта страница (terminal view)",
    "curl timcode.ru/json": "Данные в JSON",
    "curl timcode.ru/resume.json": "Резюме по схеме JSON Resume v1.0.0",
    "curl timcode.ru/llms.txt": "Профиль в markdown (конвенция llmstxt.org)",
    "curl timcode.ru/llms-full.txt": "То же плюс полные тексты кейсов и статей",
    "curl timcode.ru/rss.xml": "Лента статей и кейсов"
  },
  "articles": [
    {
      "title": "Очередь перед чужим API",
      "date": "2026-10-11",
      "url": "https://timcode.ru/articles/queue-before-foreign-api",
      "summary": "Почему между вебхуком и внешней CRM всегда стоит очередь, и какие три вещи делают её надёжной."
    }
  ],
  "cases": [
    {
      "title": "Майятрица: калькулятор, админка и сайт, три кодовые базы за два года",
      "date": "2026-01-01",
      "url": "https://timcode.ru/cases/mayatriza",
      "summary": "Долгий клиент: эзотерический сайт с расчётом «майянской матрицы» по дате рождения. С 2024 года написал с нуля три кодовые базы: сайт на Laravel 11, отдельную админку на MoonShine и нынешний сайт на Laravel 13 с новым дизайном, который прорабатывал вместе с заказчицей, и своей админкой на Inertia, React и shadcn. Расчёты сверены с документами заказчицы, клипы перекодирует ffmpeg."
    },
    {
      "title": "Магазин упаковки: задачи вокруг старого OpenCart, не ломая его",
      "date": "2026-01-01",
      "url": "https://timcode.ru/cases/lpack",
      "summary": "Оптово-розничный магазин упаковки в Петербурге с учётом в 1С. За год вокруг боевого OpenCart 2.3 появились экран показателей отдела продаж, видеораздел с фоновой конвертацией, портал аттестации сотрудников, отчёт по трафику и демо новой витрины на Mercurio. Магазин при этом не останавливался."
    },
    {
      "title": "Магазин офисной мебели, 2022-2026",
      "date": "2026-01-01",
      "url": "https://timcode.ru/cases/office-furniture-shop",
      "summary": "Веду prvoffice.ru с нуля четвёртый год: своя админка, своя витрина, три редизайна, три этапа поиска и массовое редактирование каталога."
    },
    {
      "title": "Учёт производства с нуля: платы, сканер и один маршрут в коде",
      "date": "2026-01-01",
      "url": "https://timcode.ru/cases/factory",
      "summary": "Написал систему учёта плат для производства Waviot: сначала прототип на готовой админке, потом своя платформа. С июня 2026 года цех ведёт в ней каждую плату, и теперь в неё переезжает всё производство с легаси 2016 года."
    },
    {
      "title": "Mercurio: своя платформа для интернет-магазинов",
      "date": "2025-01-01",
      "url": "https://timcode.ru/cases/mercurio",
      "summary": "С 2025 года строю Mercurio: платформу, где сырьё поставщиков неизменяемо, человек правит только решения, а каталог собирается детерминированно. Два клиента, обе витрины пока на демо-стендах."
    },
    {
      "title": "На один сервис меньше: перенос HES в сервер авторизации",
      "date": "2024-01-01",
      "url": "https://timcode.ru/cases/hes-to-auth",
      "summary": "Два самописных PHP-сервиса Waviot делили один интерфейс: экраны жили в auth, их бэкенд в HES. Перенёс HES внутрь auth так, что фронт не заметил, и закрыл один сервис с отдельным деплоем."
    },
    {
      "title": "Кабинет и телеметрия IoT-платформы: с Zend Framework 2 на Laminas и PHP 8",
      "date": "2022-01-01",
      "url": "https://timcode.ru/cases/zend-to-laminas",
      "summary": "Перевёл кабинет клиентов и бэкенд телеметрии Waviot с Zend Framework 2.3 и PHP 5.6 на Laminas и PHP 8 без остановки платформы, а потом закрепил результат: PSR-12, миграции БД, Sentry."
    },
    {
      "title": "Экран отдела продаж: сделки из Мегаплана, звонки из Mango Office",
      "date": "2020-01-01",
      "url": "https://timcode.ru/cases/prime-wood-sales-screen",
      "summary": "Для отдела продаж Prime Wood сделал экран на офисный монитор: план месяца, оплаты за день, необработанные заявки и звонки по менеджерам. PHP без фреймворка и крон, потому что этого хватало. Работу с API Mango Office потом вынес в Laravel-пакет."
    },
    {
      "title": "Магазин мебели: с самописной CMS на OpenCart за два месяца",
      "date": "2019-01-01",
      "url": "https://timcode.ru/cases/prime-wood-opencart",
      "summary": "В одиночку перенёс prime-wood.ru с самописной MyCMS 2013 года на OpenCart, релиз перед Новым годом. Покупатели смены движка не заметили, магазин работает на этой платформе седьмой год."
    }
  ],
  "handbook": "https://timcode.ru/handbook/",
  "handbook_topics": [
    {
      "title": "Чистая архитектура (Clean Architecture)",
      "url": "https://timcode.ru/handbook/architecture/clean-architecture/",
      "summary": "Делает каждый сценарий приложения отдельным объектом с явным входом и выходом, а все зависимости в коде направляет внутрь, к бизнес-правилам.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Разделение команд и запросов (CQRS)",
      "url": "https://timcode.ru/handbook/architecture/cqrs/",
      "summary": "Команды меняют модель записи и ничего не возвращают, запросы читают отдельную модель, собранную под экран, и не трогают агрегат.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Тактический DDD (Domain-Driven Design)",
      "url": "https://timcode.ru/handbook/architecture/ddd/",
      "summary": "Правила предметной области живут в самих объектах домена: агрегат не даёт привести себя в неверное состояние, значения проверяются при создании, а о случившемся сообщают события.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Хранение событий (Event Sourcing)",
      "url": "https://timcode.ru/handbook/architecture/event-sourcing/",
      "summary": "Вместо текущего состояния заказа хранится журнал всего, что с ним случилось, а состояние каждый раз получается проигрыванием этого журнала.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Гексагональная архитектура (Hexagonal Architecture)",
      "url": "https://timcode.ru/handbook/architecture/hexagonal/",
      "summary": "Ставит предметную область в центр: ядро объявляет порты, а база, платёжная система, HTTP и почта подключаются к ним адаптерами снаружи.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Слоистая архитектура (Layered Architecture)",
      "url": "https://timcode.ru/handbook/architecture/layered/",
      "summary": "Делит приложение на слои представления, бизнес-логики и данных, где каждый слой вызывает только тот, что под ним.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Ошибки как значения в Go (Errors and wrapping)",
      "url": "https://timcode.ru/handbook/languages/go/01-errors-and-wrapping/",
      "summary": "Ошибка в Go - обычное значение, которое можно обернуть контекстом, проверить по смыслу и сложить с другими: так импорт прайса сообщает обо всех плохих строках сразу, а вызывающий код решает, что делать, без разбора текста.",
      "languages": [
        "go"
      ]
    },
    {
      "title": "Readonly и перечисления в PHP (Readonly classes and enums)",
      "url": "https://timcode.ru/handbook/languages/php/01-readonly-and-enums/",
      "summary": "Статус заказа становится типом, а деньги - неизменяемым значением: опечатку в статусе и тихую правку суммы ловит язык, а не код-ревью.",
      "languages": [
        "php"
      ]
    },
    {
      "title": "Protocol и ABC в Python (Protocols vs abstract base classes)",
      "url": "https://timcode.ru/handbook/languages/python/01-protocols-vs-abc/",
      "summary": "Protocol описывает, что нужно вызывающему коду, и принимает любой объект подходящей формы; ABC описывает, что обязаны уметь наследники, и делится с ними общим кодом. Хранилищам файлов нужны оба, но в разных местах.",
      "languages": [
        "python"
      ]
    },
    {
      "title": "Размеченные объединения в TypeScript (Discriminated unions)",
      "url": "https://timcode.ru/handbook/languages/ts/01-discriminated-unions/",
      "summary": "Ответ платёжного шлюза описывается объединением вариантов с общим полем kind: компилятор сам сужает тип в каждой ветке, ловит забытый вариант через never, а ошибки разбора возвращаются как значение Result, а не летят исключением.",
      "languages": [
        "ts"
      ]
    },
    {
      "title": "Шаг 1: одна функция на все способы доставки",
      "url": "https://timcode.ru/handbook/lessons/001-refactor-to-strategy/step-1/",
      "summary": "Исходный код: switch по способу доставки, внутри if по стране, цены зашиты прямо в ветках.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Шаг 2: способы доставки за общим интерфейсом",
      "url": "https://timcode.ru/handbook/lessons/001-refactor-to-strategy/step-2/",
      "summary": "Каждая ветка switch становится своим классом с методом cost, а калькулятор только выбирает, кому отдать посылку.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Шаг 3: реестр тарифов и правила из конфига",
      "url": "https://timcode.ru/handbook/lessons/001-refactor-to-strategy/step-3/",
      "summary": "Тарифы регистрируются по коду, а какой тариф у какого способа в какой стране - решает список правил из конфига.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Шаг 1: SQL прямо в контроллере",
      "url": "https://timcode.ru/handbook/lessons/002-extract-repository/step-1/",
      "summary": "Исходный код: контроллер заказов сам пишет SQL, разбирает ряды и решает, можно ли отменить заказ.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Шаг 2: шлюз таблицы",
      "url": "https://timcode.ru/handbook/lessons/002-extract-repository/step-2/",
      "summary": "Все SQL-строки для orders уезжают в класс OrdersTable, контроллер зовёт его методы, но всё ещё работает с рядами.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Шаг 3: репозиторий в домене",
      "url": "https://timcode.ru/handbook/lessons/002-extract-repository/step-3/",
      "summary": "Заказ становится сущностью Order с правилом отмены, доступ к заказам - интерфейсом OrderRepository, а SQL уходит в одну из его реализаций.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Шаг 1: касса зовёт всех сама",
      "url": "https://timcode.ru/handbook/lessons/003-events-instead-of-calls/step-1/",
      "summary": "Исходный код: Checkout сохраняет заказ и по очереди вызывает склад, аналитику и почту.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Шаг 2: доменное событие и синхронный диспетчер",
      "url": "https://timcode.ru/handbook/lessons/003-events-instead-of-calls/step-2/",
      "summary": "Checkout публикует OrderPlaced, а склад, аналитика и почта подписываются на него сами.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Шаг 3: независимые подписчики",
      "url": "https://timcode.ru/handbook/lessons/003-events-instead-of-calls/step-3/",
      "summary": "Диспетчер ловит ошибку каждого слушателя, отдаёт её в отчёт и зовёт следующих: упавшая аналитика больше не мешает письму.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Цепочка обязанностей (Chain of Responsibility)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/chain-of-responsibility/",
      "summary": "Передаёт запрос по цепочке обработчиков: каждый либо обрабатывает его сам, либо отдаёт следующему, и отправитель не знает, кто в итоге ответит.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: приём вебхуков АТС (PBX webhooks)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/chain-of-responsibility/practice-webhook/",
      "summary": "Облачная АТС шлёт события о звонках и повторяет их, пока не получит 2xx: подпись, дедупликация и фильтр тестовых номеров стоят звеньями цепочки, а заканчивается она маршрутизатором, который по типу события выбирает, что записать в CRM.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: middleware входящего запроса (Request middleware)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/chain-of-responsibility/practice/",
      "summary": "Лимит запросов, аутентификация и валидация как звенья перед обработчиком: каждое может ответить само и остановить цепочку, а от порядка звеньев зависит, кого и от чего они защищают.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Команда (Command)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/command/",
      "summary": "Превращает запрос в объект: его можно сохранить, передать, выполнить позже и отменить, а тот, кто его запускает, не знает, что именно он делает.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: команды прибору учёта по радио (Device commands)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/command/practice-device/",
      "summary": "Команда прибору - объект с параметрами и ожидаемым ответом: её ставят в очередь, отправляют по одной, ждут подтверждения, повторяют по таймауту и отменяют, пока она не ушла в эфир.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: редактор с undo/redo (Undo history)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/command/practice/",
      "summary": "История правок документа из команд: отмена и повтор с курсором, обрезка ветки повтора, склейка набранных подряд символов и правило, что неудачная правка в историю не попадает.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Интерпретатор (Interpreter)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/interpreter/",
      "summary": "Правила маленького языка становятся деревом объектов, и каждый узел сам вычисляет свою часть выражения.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: фильтры товаров (Product filter language)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/interpreter/practice/",
      "summary": "Маркетолог пишет условие подборки строкой, а код превращает её в дерево выражений и проверяет на каждом товаре; опечатка ловится при разборе, с позицией в строке.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Итератор (Iterator)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/iterator/",
      "summary": "Даёт обойти коллекцию по одному элементу, не раскрывая, как она устроена внутри.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: постраничный обход API заказов (Paginated iterator)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/iterator/practice/",
      "summary": "Итератор прячет пагинацию внешнего API: клиент видит один длинный поток заказов, а следующая страница запрашивается, только когда до неё дошли.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Посредник (Mediator)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/mediator/",
      "summary": "Убирает прямые связи между объектами: каждый говорит только с посредником, а правила их взаимодействия собраны в одном месте.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: форма оформления заказа (Checkout form)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/mediator/practice/",
      "summary": "Поля формы зависят друг от друга: доставка прячет и показывает адрес, страна меняет список способов, промокод пересчитывает итог. Посредник CheckoutForm собирает эти правила в одном месте и не даёт уведомлениям ходить по кругу.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Снимок (Memento)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/memento/",
      "summary": "Объект сохраняет своё состояние в непрозрачный снимок и потом восстанавливается из него, не раскрывая внутренностей тому, кто снимки хранит.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: черновики анкеты (Form drafts)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/memento/practice/",
      "summary": "Длинная заявка сохраняется сама: снимки по таймеру с лимитом, именованные точки для отката и черновик, который переживает перезагрузку страницы и узнаёт чужой формат.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Наблюдатель (Observer)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/observer/",
      "summary": "Объект сообщает об изменениях всем, кто подписался, и не знает, кто они: подписчиков можно добавлять и убирать, не трогая источник.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: поток показаний приборов учёта (Meter readings stream)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/observer/practice-meters/",
      "summary": "Счётчики воды присылают показания, а тревога, контроль связи и суточный расход подписаны на поток: подписчики хранят состояние между событиями, а пропажу связи видно только по тику часов.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: доменные события заказа (Domain event dispatcher)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/observer/practice/",
      "summary": "Заказ создан или отменён, а почта, склад и аналитика узнают об этом через диспетчер событий: подписчики падают, отписываются на ходу и публикуют новые события, и рассылка должна это пережить.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Состояние (State)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/state/",
      "summary": "Объект меняет поведение вместе со своим состоянием: каждое состояние — отдельный объект, и вместо if (state === ...) в каждом методе контекст просто передаёт вызов текущему состоянию.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: плата на производстве (Production line)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/state/practice-production/",
      "summary": "Печатная плата идёт по этапам монтаж -> контроль -> прошивка -> упаковка, а сканеры на участках присылают одни и те же четыре действия: что значит \"передать дальше\" или \"вернуть на доработку\", решает этап, на котором плата сейчас.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: жизненный цикл заказа (Order lifecycle)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/state/practice/",
      "summary": "Заказ проходит путь от «новый» до «доставлен», «отменён» или «возврат»: каждый статус решает, какие события допустимы, что сделать с деньгами на переходе и как объяснить отказ.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Стратегия (Strategy)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/strategy/",
      "summary": "Выносит семейство взаимозаменяемых алгоритмов в отдельные объекты, чтобы менять их без правки кода, который их использует.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: цены из прайса поставщика (Supplier pricing)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/strategy/practice-pricing/",
      "summary": "Наценку выбирает категория товара, а описана она в конфиге: тип и параметры. Общие правила магазина применяются после любой стратегии, и стратегия не может их нарушить.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: расчёт доставки (Shipping rates)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/strategy/practice/",
      "summary": "Способ доставки выбирается правилами из конфига, а не if в коде: страна, вес и сумма заказа решают, какая стратегия посчитает цену.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Шаблонный метод (Template Method)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/template-method/",
      "summary": "Порядок шагов алгоритма фиксирует базовый класс, а наследники подставляют только сами шаги и, если нужно, меняют поведение крючков.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: экспорт платёжного реестра (Payment registry export)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/template-method/practice/",
      "summary": "Реестр платежей уходит в разные банки в разных форматах, но проверка платежей, итоговая строка и контрольная сумма одни на всех, и ни один банк не может их обойти.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Посетитель (Visitor)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/visitor/",
      "summary": "Новые операции над набором классов без правки самих классов: каждая операция живёт в своём объекте, а узел только говорит, какой он.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: НДС и чек по заказу (Order visitors)",
      "url": "https://timcode.ru/handbook/patterns/behavioral/visitor/practice/",
      "summary": "Заказ из товаров, скидок, доставки и упаковки, а над ним растёт список операций: НДС, фискальный чек, проверка. Операции живут в посетителях с состоянием, узлы заказа остаются данными.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Абстрактная фабрика (Abstract Factory)",
      "url": "https://timcode.ru/handbook/patterns/creational/abstract-factory/",
      "summary": "Создаёт семейство связанных объектов целиком, чтобы клиент не мог смешать продукты разных семейств.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: платёжные провайдеры (Payment providers)",
      "url": "https://timcode.ru/handbook/patterns/creational/abstract-factory/practice/",
      "summary": "Платёжный провайдер — это семейство: приём платежа, возвраты и разбор вебхуков. Возврат, ушедший не тому провайдеру, который принял платёж, — реальный инцидент, и абстрактная фабрика делает его невозможным по построению.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Строитель (Builder)",
      "url": "https://timcode.ru/handbook/patterns/creational/builder/",
      "summary": "Собирает сложный объект по шагам: обязательные части, необязательные в любом порядке, одна проверка в конце.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: запрос отчёта (Report query)",
      "url": "https://timcode.ru/handbook/patterns/creational/builder/practice/",
      "summary": "Строитель SQL-запроса для отчётов: значения только в параметрах, пагинация с границами, и неизменяемость, из-за которой базовый запрос можно делить между отчётами без сюрпризов.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Фабричный метод (Factory Method)",
      "url": "https://timcode.ru/handbook/patterns/creational/factory-method/",
      "summary": "Общий алгоритм живёт в базовом классе, а решение «какой именно объект создать» отдаётся наследникам.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: разбор пакетов приборов учёта (Meter telemetry)",
      "url": "https://timcode.ru/handbook/patterns/creational/factory-method/practice-telemetry/",
      "summary": "Проверка кадра одна для всех приборов, а парсер полезной нагрузки выбирает тип прибора из заголовка: это параметризованный фабричный метод, и новый тип прибора добавляется наследником, без правки конвейера.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: импорт прайс-листов (Price list import)",
      "url": "https://timcode.ru/handbook/patterns/creational/factory-method/practice/",
      "summary": "Конвейер импорта один для всех поставщиков, а парсер зависит от формата файла: его создаёт наследник, и ровно это делает конвейер проверяемым без файлов.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Прототип (Prototype)",
      "url": "https://timcode.ru/handbook/patterns/creational/prototype/",
      "summary": "Новый объект создаётся копией уже настроенного, а глубину копии решает сам объект.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: КП из шаблона (Quote from template)",
      "url": "https://timcode.ru/handbook/patterns/creational/prototype/practice/",
      "summary": "Копия коммерческого предложения для нового клиента: сбросить идентичность, продублировать строки, оставить общими товары каталога. Глубина копии — бизнес-решение, а не техническая деталь.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Одиночка (Singleton)",
      "url": "https://timcode.ru/handbook/patterns/creational/singleton/",
      "summary": "Гарантирует один экземпляр на процесс и даёт к нему глобальную точку доступа. Первое полезно, второе почти всегда вредно.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: тарифы, загруженные один раз (Loaded once)",
      "url": "https://timcode.ru/handbook/patterns/creational/singleton/practice/",
      "summary": "Единственность без глобальной точки доступа: справочник тарифов загружается один раз на процесс, потокобезопасно, а клиенты получают его через конструктор и подменяют в тестах.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Преобразователь данных (Data Mapper)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/data-mapper/",
      "summary": "Отдельный объект переносит данные между доменной моделью и хранилищем, так что модель не знает ни таблиц, ни колонок, ни того, что её вообще сохраняют.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: заказ в двух таблицах (Order mapper)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/data-mapper/practice/",
      "summary": "Маппер собирает заказ с деньгами, почтой и статусом из двух таблиц, раскладывает его обратно, удаляет снятые строки заказа и не пускает в модель битые данные из базы.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Доменное событие (Domain Event)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/domain-event/",
      "summary": "Агрегат записывает, что с ним случилось, в виде неизменяемых фактов в прошедшем времени, а реагируют на эти факты другие части системы: агрегат не знает ни о почте, ни о биллинге.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: публикация событий после сохранения (Publishing domain events)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/domain-event/practice/",
      "summary": "Подписка меняет тариф, биллинг и почта узнают об этом из событий, но только если изменение действительно записано в базу: событие о том, чего нет в базе, хуже, чем никакого.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Нулевой объект (Null Object)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/null-object/",
      "summary": "Вместо null подставляет объект с тем же интерфейсом и нейтральным поведением, чтобы код не проверял на null перед каждым вызовом.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: гость, лояльность и аналитика (Shop profile)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/null-object/practice/",
      "summary": "Гость вместо null-пользователя, отсутствующая программа лояльности и выключенная аналитика как нулевые объекты, которые выбираются в одном месте, и ненайденный заказ, который нулевым объектом быть не должен.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Outbox (Transactional Outbox)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/outbox/",
      "summary": "Событие о бизнес-изменении записывается в ту же базу и в той же транзакции, что и само изменение, а в брокер его отправляет отдельный процесс: данные и событие не расходятся, даже если брокер лежит.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: уведомления клиентам в Telegram (Telegram notifications)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/outbox/practice-telegram/",
      "summary": "Уведомление о заказе пишется в outbox одной транзакцией с заказом, а фоновый отправитель шлёт его через Bot API: пачками, с паузой на лимит, с пометкой мёртвых чатов и без дублей благодаря ключу идемпотентности.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: надёжный отправитель outbox (Outbox relay)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/outbox/practice/",
      "summary": "Брокер то отказывает, то теряет подтверждения, а события одного заказа должны прийти по порядку и ровно один раз в смысле результата: отправитель повторяет, получатель отбрасывает дубли.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Репозиторий (Repository)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/repository/",
      "summary": "Даёт предметной области коллекцию сущностей с методами на её языке и прячет за ней, где и как они хранятся.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: контрагенты из учётной системы (Repository over HTTP API)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/repository/practice-api/",
      "summary": "Контрагенты живут не в нашей базе, а в учётной системе вроде 1С за HTTP API, и домен всё равно видит обычный репозиторий: страницы превращаются в обход, коды ответа в доменные ошибки, поля API в модель.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: один контракт на два хранилища (Repository contract tests)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/repository/practice/",
      "summary": "Репозиторий в памяти и репозиторий в JSON-файле проходят один и тот же набор тестов: копии вместо общих объектов, поиск по критерию и конфликт версий.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Сервисный слой (Service Layer)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/service-layer/",
      "summary": "Собирает сценарии приложения в один слой: контроллер, консольная команда и очередь вызывают один метод сервиса, а не повторяют шаги каждый у себя.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: регистрация на мероприятие (Event registration)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/service-layer/practice/",
      "summary": "Регистрация на лекцию с ограниченным числом мест: сервис принимает проверенную команду, держит транзакцию вокруг сценария, отдаёт DTO вместо сущности и шлёт письмо только после записи.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Спецификация (Specification)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/specification/",
      "summary": "Бизнес-правило становится отдельным объектом с одним вопросом «подходит ли кандидат», и одно и то же правило проверяет объект, отбирает список и собирается с другими через «и», «или», «не».",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: доступ к заказу в админке (Access rules)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/specification/practice-access/",
      "summary": "Правило \"кто может редактировать заказ\" собирается из маленьких спецификаций над парой пользователь и заказ, и при отказе называет условие, которое не выполнено.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: аудитория рассылки (Mailing audience)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/specification/practice/",
      "summary": "Одно правило отбора клиентов проверяет клиента в памяти и переводится в SQL WHERE с параметрами, так что приложение и база отбирают одних и тех же людей.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Единица работы (Unit of Work)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/unit-of-work/",
      "summary": "Собирает все изменения бизнес-операции (новые, изменённые и удалённые объекты) и записывает их в хранилище одним проходом в конце, а не по одному в момент каждой правки.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: перевод и заказ в одной транзакции (Unit of Work)",
      "url": "https://timcode.ru/handbook/patterns/enterprise/unit-of-work/practice/",
      "summary": "Оформление заказа списывает деньги клиента, зачисляет их магазину и уменьшает остатки склада. Единица работы держит все эти объекты, сама находит изменённые и записывает их в одной транзакции: всё или ничего.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Адаптер (Adapter)",
      "url": "https://timcode.ru/handbook/patterns/structural/adapter/",
      "summary": "Даёт старому или чужому классу тот интерфейс, которого ждёт клиент, не меняя ни класс, ни клиента.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: SMS через старый шлюз (SMS gateway adapter)",
      "url": "https://timcode.ru/handbook/patterns/structural/adapter/practice/",
      "summary": "Старый SMS-шлюз подключается к интерфейсу уведомлений: адаптер переводит номера телефонов в формат шлюза, а числовые коды ответа в причины, по которым приложение решает, что делать дальше.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Мост (Bridge)",
      "url": "https://timcode.ru/handbook/patterns/structural/bridge/",
      "summary": "Разделяет иерархию на две — абстракцию и реализацию, — чтобы каждая росла сама по себе, а не перемножалась с другой.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: отчёты в разных форматах (Report renderers)",
      "url": "https://timcode.ru/handbook/patterns/structural/bridge/practice/",
      "summary": "Два отчёта и три формата вывода без шести классов: отчёт решает, что показать, рендерер — как это записать, и новый формат не трогает отчёты.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Компоновщик (Composite)",
      "url": "https://timcode.ru/handbook/patterns/structural/composite/",
      "summary": "Дерево из простых и составных объектов с одним интерфейсом: клиент спрашивает узел, не выясняя, лист это или контейнер.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: цена корзины с наборами (Bundle pricing)",
      "url": "https://timcode.ru/handbook/patterns/structural/composite/practice/",
      "summary": "Наборы товаров со своей скидкой, вложенные друг в друга: цена считается по дереву, а дерево приходит из каталога, где редактор может ошибиться.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Декоратор (Decorator)",
      "url": "https://timcode.ru/handbook/patterns/structural/decorator/",
      "summary": "Добавляет объекту поведение обёрткой с тем же интерфейсом: обёртки складываются в любом количестве и порядке, а исходный класс не меняется.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: слои над медленным отчётом (Report source layers)",
      "url": "https://timcode.ru/handbook/patterns/structural/decorator/practice-reports/",
      "summary": "Кэш, замер времени, журнал и лимит строк над одним медленным запросом к базе: набор и порядок слоёв задаёт конфиг, и от порядка зависит, какие данные получит соседний экран.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: клиент курсов валют (API client layers)",
      "url": "https://timcode.ru/handbook/patterns/structural/decorator/practice/",
      "summary": "Ретраи, кэш и журнал как слои поверх одного клиента внешнего API: каждый слой простой, а поведение целого определяет порядок, в котором их сложили.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Фасад (Facade)",
      "url": "https://timcode.ru/handbook/patterns/structural/facade/",
      "summary": "Даёт один простой вход в набор подсистем: клиент вызывает одну операцию, а порядок шагов и связи между частями знает фасад.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: оформление заказа (Place order)",
      "url": "https://timcode.ru/handbook/patterns/structural/facade/practice/",
      "summary": "Оформление заказа — это резерв на складе, списание оплаты и уведомление. Фасад PlaceOrder прячет не только порядок шагов, но и то, что делать, когда шаг не удался.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Приспособленец (Flyweight)",
      "url": "https://timcode.ru/handbook/patterns/structural/flyweight/",
      "summary": "Много мелких объектов делят одно неизменяемое общее состояние, а уникальное хранят у себя или получают параметрами.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: стили ячеек таблицы (Cell styles)",
      "url": "https://timcode.ru/handbook/patterns/structural/flyweight/practice/",
      "summary": "Десять тысяч ячеек выгрузки делят три стиля: стиль неизменяемый и приходит из пула по ключу, а правка стиля одной ячейки не задевает соседей, потому что создаёт новый стиль, а не меняет общий.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Заместитель (Proxy)",
      "url": "https://timcode.ru/handbook/patterns/structural/proxy/",
      "summary": "Подставляет вместо объекта другой с тем же интерфейсом, который решает, когда и пускать ли вызов дальше.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Практика: кэш репозитория товаров (Caching proxy)",
      "url": "https://timcode.ru/handbook/patterns/structural/proxy/practice/",
      "summary": "Кэширующий заместитель перед медленным репозиторием: ответы из памяти с TTL, сброс при записи и честная граница — запись мимо заместителя кэш не видит.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Композиция вместо наследования (Composition over Inheritance)",
      "url": "https://timcode.ru/handbook/principles/composition-over-inheritance/",
      "summary": "Поведение собирается из небольших частей, а не наследуется: новый набор каналов уведомлений - это новый список, а не новый класс.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Не повторяйся, не усложняй, не делай впрок (DRY, KISS, YAGNI)",
      "url": "https://timcode.ru/handbook/principles/dry-kiss-yagni/",
      "summary": "Три коротких правила против лишнего кода: одно знание - в одном месте, простое решение вместо хитрого, и ничего \"на будущее\".",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Закон Деметры (Law of Demeter)",
      "url": "https://timcode.ru/handbook/principles/law-of-demeter/",
      "summary": "Объект общается только с ближайшими соседями: спрашивает заказ, куда его везти, а не лезет через заказ в покупателя и его адрес.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Принцип инверсии зависимостей (Dependency Inversion Principle)",
      "url": "https://timcode.ru/handbook/principles/solid/dependency-inversion/",
      "summary": "Модули верхнего уровня зависят от абстракций, а не от деталей: сервис уведомлений знает порт Mailer, а SMTP - деталь, которая подключается снаружи.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Принцип разделения интерфейсов (Interface Segregation Principle)",
      "url": "https://timcode.ru/handbook/principles/solid/interface-segregation/",
      "summary": "Клиент не должен зависеть от методов, которыми не пользуется: печать и сканирование - отдельные узкие интерфейсы, а не одно \"устройство\".",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Принцип подстановки Лисков (Liskov Substitution Principle)",
      "url": "https://timcode.ru/handbook/principles/solid/liskov-substitution/",
      "summary": "Подтип должен работать везде, где ждут базовый тип: хранилище только для чтения не наследует запись, которую не умеет выполнять.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Принцип открытости/закрытости (Open/Closed Principle)",
      "url": "https://timcode.ru/handbook/principles/solid/open-closed/",
      "summary": "Модуль открыт для расширения и закрыт для изменения: новый способ оплаты подключается к кассе без правки её кода.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    },
    {
      "title": "Принцип единственной ответственности (Single Responsibility Principle)",
      "url": "https://timcode.ru/handbook/principles/solid/single-responsibility/",
      "summary": "У модуля одна причина для изменения: подсчёт продаж и форматирование отчёта меняются по разным причинам, поэтому живут порознь.",
      "languages": [
        "php",
        "go",
        "python",
        "ts"
      ]
    }
  ]
}
