Если коротко, то в тот день я хотел одного: чтобы каждый новый модуль в Картаре встраивался в продукт сам, без того, чтобы я потом руками лез в десять разных мест и дописывал «а вот ещё этот домен покажи тут, и тут, и не забудь вот здесь». Из этого выросла плагинная архитектура на декораторах, реестр контрибьюторов портрета, куда любой новый домен — хоть архетипы Юнга, хоть что угодно ещё — прописывается сам и автоматом подтягивается в портрет пользователя. А из этого же требования родился второй реестр, уже под покупки, чтобы новый платный домен априори проходил через единый фильтр от двойного списания. И вот тут самое смешное: половину того, что мы напроектировали и прогнали через консилиум, я в конце сам же и откатил, потому что начал с «я просто рефакторинг хотел», а закончил почти полноценной системой под то, чего ещё нет. Об этом и расскажу, простыми словами конечно же — что строил, зачем гонял через дебаты, и почему лишний реестр поехал в мусорку.
С чего вообще начался разговор
Картара — это Telegram Mini App с AI-персонажем, и в ней у пользователя есть портрет: натальная карта, нумерология, всякие штуки, которые мы постепенно добавляем отдельными доменами. И каждый раз, когда заходит новый домен, повторялась одна и та же боль. Ты пишешь логику самого домена, а потом начинается марафон по кодовой базе: тут добавь его в выдачу портрета, там не забудь в сервисе, здесь дёрни в UI, а ещё в паре мест, про которые ты вспоминаешь уже на проде, когда у юзера чего-то не показалось. И вот я в какой-то момент сформулировал это прямо: каждый новый домен должен интегрироваться автоматом в портрет, систему надо сделать. Не «давай я ещё раз вручную пробегусь по всем местам», а именно систему, которая принимает новый кусок и сама знает, куда его пристроить.
Это, если разобраться, классическая задача про расширяемость. Вы добавляете в приложение новую фичу, и вопрос даже не в том, чтобы её написать — написать-то Claude Code напишет, — а в том, чтобы она не заставляла вас трогать ещё кучу старого кода. Чем больше таких точек касания, тем выше шанс что-нибудь проебать при следующем добавлении, и когда домены идут пачками, эта цена начинает кусаться по-настоящему.
Плагин-паттерн на декораторах
Решение, к которому мы пришли в обсуждении с Claude Code, — это registry pattern. По-простому: есть единый реестр, и каждый домен сам в него прописывается через декоратор, а портрет потом просто спрашивает у реестра, кто там вообще есть, давайте всех. Никто не дёргает каждый домен поимённо в десяти местах — портрет работает со списком контрибьюторов, и ему всё равно, два их там или восемь. Добавил новый — он сам появился. Это и есть та самая архитектура ai агента, где модуль не надо «подключать», он подключается фактом своего существования.
Параллельно вылезла вторая история, та самая с покупками. У нас был риск двойного списания: это когда между «проверил, что у юзера есть доступ» и «списал деньги» проходит момент, в который запрос может прилететь дважды, и человек платит два раза за одно и то же. Называется это TOCTOU, time-of-check to time-of-use, но суть простая и неприятная. И я сразу сказал, что не хочу затыкать это точечно по эндпоинтам — сегодня нашёл дырку тут, заткнул, завтра новый домен добавил и забыл. Я хотел, чтобы новый домен и новые покупки работали так: просто указывается цена, а дальше оно априори проходит через системный фильтр. Ну тут по идее тоже надо системно решать, как-то же. Из этого и вырос второй реестр, по тому же принципу, что и портретный.
Зачем я погнал это через консилиум
И вот в этот момент во мне сработало то, что я обычно делаю, когда задача пахнет архитектурой. У меня есть свой процесс — консилиум, когда несколько AI-экспертов, часть на Claude, часть на GPT, параллельно разбирают вариант с разных сторон, а потом устраивают дебаты, если подходов несколько — как было, когда Claude и GPT спорили про архитектуру моей UGC-игры. Я про сам консилиум уже подробно писал в Собрал консилиум из 6 AI-экспертов и забыл добавить туда пользователя, так что повторяться не буду, но штука рабочая.
Так вот, Claude Code в какой-то момент попытался поехать сразу в реализацию, мол, и так понятно. А я притормозил и спросил прямо: ты уверен, что без консилиума норм будет, или ты ленишься сейчас? И это не придирка ради придирки. Когда ты проектируешь штуку, которую потом будут переиспользовать все новые домены, ошибка в фундаменте размножается на каждый следующий модуль, и тут как раз тот случай, когда полчаса на дебаты выходят дешевле, чем потом переделывать паттерн, который уже расползся по всему коду.
В консилиуме и развернулся главный спор. Вариантов было три. Первый — один большой объект профиля, который знает про всё сразу. Второй — восемь отдельных реестров, по реестру на каждый тип сущности, максимально гранулярно. И третий — лёгкий агрегатор, который собирает контрибьюторов в одном месте и не плодит сущности. У каждого свои плюсы: один объект проще читать, но он распухает; восемь реестров чисто разделяют ответственность, но это восемь мест, которые надо держать в голове; агрегатор где-то посередине. Вот ровно ради такого выбора консилиум и нужен, потому что в одиночку ты легко влюбляешься в первое решение, которое пришло в голову, и перестаёшь видеть его минусы.
Как я сам же урезал собственный скоуп
А дальше случилось то, ради чего, наверное, и стоит писать этот пост. Я посмотрел на всё, что мы напроектировали, и поймал себя на мысли: подожди, я же изначально просто рефакторинг хотел, а меня уже несёт в сторону пяти толстых контрибьюторов, второго реестра, целой инфраструктуры под то, чего ещё нет. И я выбрал вариант полегче — тонкий слой идентичности поверх уже готового портрета, вместо того чтобы городить толстую систему под каждый домен. Портрет и так уже работал, переписывать его не надо было, надо было аккуратно надстроить.
А второй реестр, тот что под покупки, я в итоге откатил. Просто потому, что держать в коде неиспользуемый реестр, который красиво выглядит на диаграмме, но реально пока ничего не фильтрует, — это и есть тех-долг в чистом виде. Он стоит ноль пользы и ненулевое внимание: каждый, кто потом откроет код, будет гадать, зачем эта штука и почему она пустая. Да-да, всё верно, тех-долг не нужен, откатывай — вот ровно так я себе и сказал. Защиту от двойного списания мы при этом не выкинули, её сделали там, где она реально нужна сейчас, без преждевременного обобщения на восемь будущих доменов, которых пока нет.
И тут важная штука, которую я для себя зафиксировал. Это ведь не противоречит принципу делать системно. Системно — это не настроить максимум абстракций на всякий случай. Системно — это понимать, какая именно проблема перед тобой стоит сегодня, и решать её так, чтобы не пришлось переделывать завтра. А восемь реестров под два реальных домена — это уже не системность, это игра в архитектуру ради архитектуры. Моё личное правило тут жёсткое: надо или делать нормально сразу, или не делать вообще. И вот «отложенный тех-долг ради красоты» как раз это правило и нарушает, потому что это «не сделал нормально, но и не не сделал, оставил висеть».
Что в итоге осталось и что я понял
По факту в продукте остался один реестр на декораторах — тот, что собирает домены в портрет автоматом. Новый домен теперь действительно встраивается сам, без забега по десяти местам, и это ровно то, чего я хотел в самом начале. Лёгкий слой идентичности поверх готового портрета вместо пяти толстых контрибьюторов, и никакого пустого реестра под покупки, который бы делал вид, что он что-то фильтрует.
Что я из этого вынес. Первое: registry pattern на декораторах — отличная штука для расширяемого приложения, когда новые модули идут пачками и ты не хочешь каждый раз дописывать интеграцию руками. Если строите что-то, что будет расти доменами или плагинами, то штука почти обязательная. Второе: консилиум перед архитектурным решением окупается, потому что без него ты не увидишь варианты, в которые сам не влюблён. И третье, самое для меня ценное в этой сессии — умение остановить собственный занос. Я начал с рефакторинга, а чуть не построил систему под будущее, которого ещё нет, и хорошо, что вовремя себя спросил, что я вообще тут делаю.
Кстати, весь код тут писал и переписывал Claude Code под моим управлением — я оркестрировал, спорил с ним, гонял через дебаты и в конце говорил, что откатить. Сам я код руками не пишу и не скрываю этого. И вот именно в таком режиме особенно важно держать руку на скоупе, потому что AI с удовольствием построит вам красивую толстую систему на любой запрос — он не устаёт и ему не жалко. Жалко должно быть вам, потому что поддерживать это потом тоже вам. У меня под такие штуки даже есть свой каркас из фаз, про него я писал в STC + Guardian MCP: как я заставил AI-агента работать по процессу, и он как раз помогает не уехать. Вот и делайте выводы: иногда самая системная вещь, которую можно сделать, — это вовремя нажать отмену.