[{"data":1,"prerenderedAt":1154},["ShallowReactive",2],{"post-\u002Fblog\u002Fcqrs":3,"surround-\u002Fblog\u002Fcqrs":1143},{"id":4,"title":5,"body":6,"category":1128,"cover":1129,"date":1130,"description":1131,"draft":1132,"extension":1133,"meta":1134,"navigation":435,"path":1135,"seo":1136,"stem":1137,"tags":1138,"updated":1141,"__hash__":1142},"blog\u002Fblog\u002Fcqrs.md","CQRS на практике: разделение команд и запросов без лишней сложности",{"type":7,"value":8,"toc":1091},"minimark",[9,14,22,47,58,69,73,121,133,137,142,149,159,162,166,169,175,178,182,185,189,243,247,291,294,328,332,335,341,346,370,374,378,381,466,469,473,554,557,561,595,599,602,606,609,639,645,657,661,665,672,729,732,739,742,809,812,816,827,831,835,838,842,849,853,867,871,874,878,881,885,905,908,922,926,990,994,1057,1061,1064,1070,1084,1087],[10,11,13],"h2",{"id":12},"что-такое-cqrs","Что такое CQRS",[15,16,17,21],"p",{},[18,19,20],"strong",{},"CQRS (Command Query Responsibility Segregation)"," — архитектурный паттерн, который разделяет операции над системой на два принципиально разных типа:",[23,24,25,37],"ul",{},[26,27,28,31,32,36],"li",{},[18,29,30],{},"Command"," (команда) — действие, которое ",[33,34,35],"em",{},"изменяет"," состояние системы.",[26,38,39,42,43,46],{},[18,40,41],{},"Query"," (запрос) — операция, которая ",[33,44,45],{},"читает"," данные, не производя изменений.",[15,48,49,50,53,54,57],{},"Идея не нова: ещё в 1988 году Бертран Мейер в книге ",[33,51,52],{},"Object-Oriented Software Construction"," сформулировал принцип ",[18,55,56],{},"CQS"," (Command Query Separation) на уровне отдельных методов.\nCQRS поднимает ту же идею на уровень архитектуры: команды и запросы разделяются не просто по методам, а по классам, модулям, а иногда и по целым сервисам.",[15,59,60,61,64,65,68],{},"Термин популяризировали ",[18,62,63],{},"Грег Янг"," (Greg Young) и ",[18,66,67],{},"Уди Дахан"," (Udi Dahan) в конце 2000-х, когда стало ясно, что в сложных доменах модели чтения и записи живут по разным правилам и пытаются ужиться в одной сущности себе в убыток.",[10,70,72],{"id":71},"главный-вопрос-это-команда-или-запрос","Главный вопрос: это команда или запрос?",[74,75,76,92],"table",{},[77,78,79],"thead",{},[80,81,82,86,89],"tr",{},[83,84,85],"th",{},"Вопрос об операции",[83,87,88],{},"Тип",[83,90,91],{},"Пример",[93,94,95,109],"tbody",{},[80,96,97,101,106],{},[98,99,100],"td",{},"Меняет ли она данные в системе?",[98,102,103],{},[104,105,30],"code",{},[98,107,108],{},"Создание заказа, списание средств",[80,110,111,114,118],{},[98,112,113],{},"Только возвращает данные?",[98,115,116],{},[104,117,41],{},[98,119,120],{},"Получение списка заказов, расчёт итога корзины",[15,122,123,126,127,129,130,132],{},[18,124,125],{},"Критерий простой:"," если после операции данные в хранилище (БД, кэш, очередь) изменились — это ",[104,128,30],{},".\nЕсли данные остались прежними — ",[104,131,41],{},".",[10,134,136],{"id":135},"два-уровня-cqrs","Два уровня CQRS",[138,139,141],"h3",{"id":140},"лёгкий-cqrs-в-рамках-одного-сервиса","Лёгкий CQRS (в рамках одного сервиса)",[15,143,144,145,148],{},"Команды и запросы живут в одном приложении, работают с одной базой данных, но ",[18,146,147],{},"разделены по папкам, классам и семантике",".\nЭто самый распространённый вариант: он даёт 80 % пользы при 20 % усилий.",[150,151,157],"pre",{"className":152,"code":154,"language":155,"meta":156},[153],"language-text","Один сервис\n├── Commands → пишут в БД\n└── Queries → читают из БД\n","text","",[104,158,154],{"__ignoreMap":156},[15,160,161],{},"Именно о нём пойдёт речь ниже.",[138,163,165],{"id":164},"полный-cqrs-раздельные-модели","Полный CQRS (раздельные модели)",[15,167,168],{},"Модель записи и модель чтения физически разделены: разные базы, разные схемы, синхронизация через события.\nПрименяется в высоконагруженных системах, где нагрузка на чтение и запись отличается на порядки.",[150,170,173],{"className":171,"code":172,"language":155,"meta":156},[153],"Command Side (PostgreSQL) ──события──▶ Query Side (Elasticsearch \u002F Redis)\n",[104,174,172],{"__ignoreMap":156},[15,176,177],{},"Это мощный, но дорогой инструмент. Если у вас нет миллионов запросов в секунду, скорее всего, он избыточен.",[10,179,181],{"id":180},"примеры-команды-и-запросы","Примеры: команды и запросы",[15,183,184],{},"Чтобы не привязываться к одному домену, возьмём несколько типичных.",[138,186,188],{"id":187},"команды-изменяют-состояние","Команды (изменяют состояние)",[74,190,191,201],{},[77,192,193],{},[80,194,195,198],{},[83,196,197],{},"Команда",[83,199,200],{},"Что делает",[93,202,203,213,223,233],{},[80,204,205,210],{},[98,206,207],{},[104,208,209],{},"PlaceOrderCommand",[98,211,212],{},"Создаёт заказ и списывает товары со склада",[80,214,215,220],{},[98,216,217],{},[104,218,219],{},"CancelSubscriptionCommand",[98,221,222],{},"Меняет статус подписки на «отменена»",[80,224,225,230],{},[98,226,227],{},[104,228,229],{},"ResetUserPreferencesCommand",[98,231,232],{},"Удаляет пользовательские настройки из кэша и БД",[80,234,235,240],{},[98,236,237],{},[104,238,239],{},"PublishArticleCommand",[98,241,242],{},"Переводит статью из черновика в опубликованную",[138,244,246],{"id":245},"запросы-читают-данные","Запросы (читают данные)",[74,248,249,259],{},[77,250,251],{},[80,252,253,256],{},[83,254,255],{},"Запрос",[83,257,258],{},"Что возвращает",[93,260,261,271,281],{},[80,262,263,268],{},[98,264,265],{},[104,266,267],{},"GetOrderHistoryQuery",[98,269,270],{},"Список заказов пользователя с пагинацией",[80,272,273,278],{},[98,274,275],{},[104,276,277],{},"GetDashboardStatsQuery",[98,279,280],{},"Агрегированные метрики для дашборда",[80,282,283,288],{},[98,284,285],{},[104,286,287],{},"GetArticleByIdQuery",[98,289,290],{},"Текст и метаданные статьи",[15,292,293],{},"Обратите внимание на именование:",[23,295,296,316],{},[26,297,298,299,302,303,306,307,306,310,306,313,132],{},"Команды отражают ",[18,300,301],{},"действие",": ",[104,304,305],{},"Place",", ",[104,308,309],{},"Cancel",[104,311,312],{},"Reset",[104,314,315],{},"Publish",[26,317,318,319,302,322,306,325,132],{},"Запросы начинаются с ",[18,320,321],{},"Get",[104,323,324],{},"GetOrderHistory",[104,326,327],{},"GetDashboardStats",[10,329,331],{"id":330},"структура-кода","Структура кода",[15,333,334],{},"При лёгком CQRS логично организовать папки по типу операции и по доменной области:",[150,336,339],{"className":337,"code":338,"language":155,"meta":156},[153],"Application\u002F\n├── Order\u002F\n│ ├── Command\u002F\n│ │ └── PlaceOrderCommand.php\n│ ├── CommandHandler\u002F\n│ │ └── PlaceOrderHandler.php\n│ ├── Query\u002F\n│ │ └── GetOrderHistoryQuery.php\n│ ├── QueryHandler\u002F\n│ │ └── GetOrderHistoryHandler.php\n│ └── DTO\u002F\n│   └── OrderSummary.php\n│\n├── Analytics\u002F\n│ ├── Query\u002F\n│ │ └── GetDashboardStatsQuery.php\n│ ├── QueryHandler\u002F\n│ │ ├── GetDashboardStatsHandler.php\n│ │ └── CachedGetDashboardStatsHandler.php\n│ └── DTO\u002F\n│   └── DashboardStats.php\n",[104,340,338],{"__ignoreMap":156},[15,342,343],{},[18,344,345],{},"Соглашения:",[23,347,348,358,367],{},[26,349,350,351,354,355,132],{},"Команды лежат в ",[104,352,353],{},"Command\u002F",", их обработчики — в ",[104,356,357],{},"CommandHandler\u002F",[26,359,360,361,354,364,132],{},"Запросы лежат в ",[104,362,363],{},"Query\u002F",[104,365,366],{},"QueryHandler\u002F",[26,368,369],{},"Каждый класс — одна операция. Никаких «универсальных» обработчиков.",[10,371,373],{"id":372},"почему-это-важно","Почему это важно",[138,375,377],{"id":376},"_1-явная-семантика","1. Явная семантика",[15,379,380],{},"По имени класса мгновенно понятно, что происходит:",[150,382,386],{"className":383,"code":384,"language":385,"meta":156,"style":156},"language-php shiki shiki-themes github-light github-dark","\u002F\u002F Очевидно: создаёт заказ, меняет состояние\n$this->handler->handle(new PlaceOrderCommand($cartId, $userId));\n\n\u002F\u002F Очевидно: только читает историю\n$this->handler->handle(new GetOrderHistoryQuery($userId, $page));\n","php",[104,387,388,397,430,437,443],{"__ignoreMap":156},[389,390,393],"span",{"class":391,"line":392},"line",1,[389,394,396],{"class":395},"sJ8bj","\u002F\u002F Очевидно: создаёт заказ, меняет состояние\n",[389,398,400,404,408,412,414,418,421,424,427],{"class":391,"line":399},2,[389,401,403],{"class":402},"sj4cs","$this",[389,405,407],{"class":406},"szBVR","->",[389,409,411],{"class":410},"sVt8B","handler",[389,413,407],{"class":406},[389,415,417],{"class":416},"sScJk","handle",[389,419,420],{"class":410},"(",[389,422,423],{"class":406},"new",[389,425,426],{"class":402}," PlaceOrderCommand",[389,428,429],{"class":410},"($cartId, $userId));\n",[389,431,433],{"class":391,"line":432},3,[389,434,436],{"emptyLinePlaceholder":435},true,"\n",[389,438,440],{"class":391,"line":439},4,[389,441,442],{"class":395},"\u002F\u002F Очевидно: только читает историю\n",[389,444,446,448,450,452,454,456,458,460,463],{"class":391,"line":445},5,[389,447,403],{"class":402},[389,449,407],{"class":406},[389,451,411],{"class":410},[389,453,407],{"class":406},[389,455,417],{"class":416},[389,457,420],{"class":410},[389,459,423],{"class":406},[389,461,462],{"class":402}," GetOrderHistoryQuery",[389,464,465],{"class":410},"($userId, $page));\n",[15,467,468],{},"Читая код ревью, вы сразу видите, где система мутирует данные, а где нет.\nЭто снижает когнитивную нагрузку на порядок.",[138,470,472],{"id":471},"_2-разные-стратегии-обработки","2. Разные стратегии обработки",[74,474,475,486],{},[77,476,477],{},[80,478,479,482,484],{},[83,480,481],{},"Аспект",[83,483,30],{},[83,485,41],{},[93,487,488,499,510,521,532,543],{},[80,489,490,493,496],{},[98,491,492],{},"Транзакции БД",[98,494,495],{},"Обязательны",[98,497,498],{},"Не нужны",[80,500,501,504,507],{},[98,502,503],{},"Кэширование",[98,505,506],{},"Нельзя кэшировать",[98,508,509],{},"Можно и нужно",[80,511,512,515,518],{},[98,513,514],{},"Валидация бизнес-правил",[98,516,517],{},"Полная",[98,519,520],{},"Минимальная",[80,522,523,526,529],{},[98,524,525],{},"Масштабирование",[98,527,528],{},"Master-нода БД",[98,530,531],{},"Read-replicas",[80,533,534,537,540],{},[98,535,536],{},"Идемпотентность",[98,538,539],{},"Нужно проектировать",[98,541,542],{},"Естественна",[80,544,545,548,551],{},[98,546,547],{},"Логирование \u002F аудит",[98,549,550],{},"Критично",[98,552,553],{},"Опционально",[15,555,556],{},"Кэширование команды — ошибка, которая приводит к потере данных. Кэширование запроса — оптимизация.\nРазделение исключает путаницу.",[138,558,560],{"id":559},"_3-разные-модели-данных","3. Разные модели данных",[23,562,563,576,589,592],{},[26,564,565,568,569,306,572,575],{},[18,566,567],{},"Команды"," работают с доменными агрегатами (",[104,570,571],{},"Order",[104,573,574],{},"Subscription",") и соблюдают бизнес-инварианты.",[26,577,578,581,582,306,585,588],{},[18,579,580],{},"Запросы"," возвращают специализированные DTO (",[104,583,584],{},"OrderSummary",[104,586,587],{},"DashboardStats","), оптимизированные под конкретный экран или API-эндпоинт.",[26,590,591],{},"Не нужно тянуть весь граф объектов заказа, чтобы показать пользователю список из десяти строк.",[26,593,594],{},"Query-модель плоская и лёгкая.",[138,596,598],{"id":597},"_4-тестируемость","4. Тестируемость",[15,600,601],{},"Команды тестируются через побочный эффект: «после выполнения в БД появилась запись X».\nЗапросы тестируются через возвращаемое значение: «для входных данных Y возвращается DTO Z».\nЭто разные типы тестов, и разделение позволяет не смешивать их.",[10,603,605],{"id":604},"когда-граница-размывается","Когда граница размывается",[15,607,608],{},"Операция может и читать, и писать. Например, обработчик размещения заказа:",[610,611,612,618,624,633],"ol",{},[26,613,614,617],{},[18,615,616],{},"Читает"," состав корзины из Redis.",[26,619,620,623],{},[18,621,622],{},"Проверяет"," наличие товаров на складе.",[26,625,626,629,630,632],{},[18,627,628],{},"Создаёт"," запись ",[104,631,571],{}," в БД.",[26,634,635,638],{},[18,636,637],{},"Уменьшает"," остатки на складе.",[15,640,641,642,644],{},"Это ",[104,643,30],{},", потому что главное — изменение состояния. Чтение здесь вспомогательное.",[646,647,648],"blockquote",{},[15,649,650,653,654,656],{},[18,651,652],{},"Правило:"," определяйте тип по конечному эффекту.\nЕсли данные изменились — ",[104,655,30],{},".\nЧтение внутри команды не делает её запросом.",[10,658,660],{"id":659},"связь-с-другими-паттернами","Связь с другими паттернами",[138,662,664],{"id":663},"mediator-message-bus","Mediator \u002F Message Bus",[15,666,667,668,671],{},"Команды и запросы часто передаются через ",[18,669,670],{},"шину сообщений"," (Mediator). Вызов выглядит единообразно:",[150,673,675],{"className":383,"code":674,"language":385,"meta":156,"style":156},"$this->messageBus->dispatch(new PlaceOrderCommand(...));\n$this->messageBus->dispatch(new GetOrderHistoryQuery(...));\n",[104,676,677,705],{"__ignoreMap":156},[389,678,679,681,683,686,688,691,693,695,697,699,702],{"class":391,"line":392},[389,680,403],{"class":402},[389,682,407],{"class":406},[389,684,685],{"class":410},"messageBus",[389,687,407],{"class":406},[389,689,690],{"class":416},"dispatch",[389,692,420],{"class":410},[389,694,423],{"class":406},[389,696,426],{"class":402},[389,698,420],{"class":410},[389,700,701],{"class":406},"...",[389,703,704],{"class":410},"));\n",[389,706,707,709,711,713,715,717,719,721,723,725,727],{"class":391,"line":399},[389,708,403],{"class":402},[389,710,407],{"class":406},[389,712,685],{"class":410},[389,714,407],{"class":406},[389,716,690],{"class":416},[389,718,420],{"class":410},[389,720,423],{"class":406},[389,722,462],{"class":402},[389,724,420],{"class":410},[389,726,701],{"class":406},[389,728,704],{"class":410},[15,730,731],{},"Шина не знает, что внутри: она просто маршрутизирует объект в нужный обработчик.\nЭто убирает жёсткую связность между контроллером и бизнес-логикой.",[138,733,735,736],{"id":734},"паттерн-__invoke","Паттерн ",[104,737,738],{},"__invoke",[15,740,741],{},"Обработчики удобно делать «вызываемыми» объектами:",[150,743,745],{"className":383,"code":744,"language":385,"meta":156,"style":156},"final class PlaceOrderHandler\n{\n    public function __invoke(PlaceOrderCommand $command): OrderId\n    {\n        \u002F\u002F логика создания заказа\n    }\n}\n",[104,746,747,758,763,787,792,797,803],{"__ignoreMap":156},[389,748,749,752,755],{"class":391,"line":392},[389,750,751],{"class":406},"final",[389,753,754],{"class":406}," class",[389,756,757],{"class":416}," PlaceOrderHandler\n",[389,759,760],{"class":391,"line":399},[389,761,762],{"class":410},"{\n",[389,764,765,768,771,774,776,778,781,784],{"class":391,"line":432},[389,766,767],{"class":406},"    public",[389,769,770],{"class":406}," function",[389,772,773],{"class":402}," __invoke",[389,775,420],{"class":410},[389,777,209],{"class":402},[389,779,780],{"class":410}," $command)",[389,782,783],{"class":406},":",[389,785,786],{"class":402}," OrderId\n",[389,788,789],{"class":391,"line":439},[389,790,791],{"class":410},"    {\n",[389,793,794],{"class":391,"line":445},[389,795,796],{"class":395},"        \u002F\u002F логика создания заказа\n",[389,798,800],{"class":391,"line":799},6,[389,801,802],{"class":410},"    }\n",[389,804,806],{"class":391,"line":805},7,[389,807,808],{"class":410},"}\n",[15,810,811],{},"Это упрощает DI-контейнеру регистрацию и делает вызов лаконичным.",[138,813,815],{"id":814},"repository-и-read-модель","Repository и Read-модель",[15,817,818,819,822,823,826],{},"В лёгком CQRS команды обычно идут через ",[18,820,821],{},"Repository"," (паттерн для работы с агрегатами), а запросы — через ",[18,824,825],{},"Read-модель"," (прямые SQL-запросы, Query Builder, Elasticsearch).\nНе нужно прогонять аналитический запрос через агрегат и коллекцию объектов.",[10,828,830],{"id":829},"частые-заблуждения","Частые заблуждения",[138,832,834],{"id":833},"cqrs-требует-две-разные-базы-данных","«CQRS требует две разные базы данных»",[15,836,837],{},"Нет. Полный CQRS с раздельными хранилищами — продвинутый вариант для высоконагруженных систем.\nЛёгкий CQRS на уровне кода даёт главные преимущества (семантика, кэширование, тестируемость) без усложнения инфраструктуры.",[138,839,841],{"id":840},"каждый-select-к-бд-это-query","«Каждый SELECT к БД — это Query»",[15,843,844,845,848],{},"Нет. Если метод читает данные как часть команды (например, проверка «есть ли товар на складе?» внутри ",[104,846,847],{},"PlaceOrderHandler",") — это часть Command, а не отдельный Query.\nQuery — это самостоятельная операция чтения, инициированная извне.",[138,850,852],{"id":851},"command-не-может-возвращать-данные","«Command не может возвращать данные»",[15,854,855,856,858,859,862,863,866],{},"Может, но минимум. ",[104,857,847],{}," возвращает ",[104,860,861],{},"OrderId"," — этого достаточно для HTTP-ответа ",[104,864,865],{},"201 Created",".\nНо команда не должна возвращать большие структуры данных: для этого есть Query.",[138,868,870],{"id":869},"cqrs-это-про-микросервисы","«CQRS — это про микросервисы»",[15,872,873],{},"Нет. CQRS отлично работает в монолите.\nБолее того, в монолите он часто приносит больше пользы, потому что без него границы между чтением и записью размываются быстрее.",[138,875,877],{"id":876},"нужен-event-sourcing","«Нужен Event Sourcing»",[15,879,880],{},"CQRS и Event Sourcing часто упоминаются вместе, но это независимые паттерны.\nМожно использовать CQRS без Event Sourcing и наоборот.\nСвязка имеет смысл в сложных доменах с аудитом, но не является обязательной.",[10,882,884],{"id":883},"когда-cqrs-не-нужен","Когда CQRS не нужен",[23,886,887,893,899],{},[26,888,889,892],{},[18,890,891],{},"CRUD-приложение без бизнес-логики."," Если у вас пять таблиц и стандартный админ-панель, разделение на команды и запросы добавит boilerplate без реальной пользы.",[26,894,895,898],{},[18,896,897],{},"Прототип \u002F MVP."," На этапе проверки гипотезы скорость важнее архитектуры.",[26,900,901,904],{},[18,902,903],{},"Команда не знакома с паттерном."," CQRS, внедрённый формально, без понимания, превращается в ритуал и усложняет онбординг.",[15,906,907],{},"CQRS оправдан, когда:",[23,909,910,913,916,919],{},[26,911,912],{},"Операции чтения и записи имеют разную нагрузку.",[26,914,915],{},"Бизнес-правила при записи сложны и требуют явного моделирования.",[26,917,918],{},"Разные клиенты (веб, мобильное приложение, API) нуждаются в разных проекциях данных.",[26,920,921],{},"Команда растёт и нужна чёткая конвенция, чтобы не скатиться в «богатые сервисы».",[10,923,925],{"id":924},"антипаттерны","Антипаттерны",[74,927,928,938],{},[77,929,930],{},[80,931,932,935],{},[83,933,934],{},"Антипаттерн",[83,936,937],{},"Почему плохо",[93,939,940,958,966,974,982],{},[80,941,942,948],{},[98,943,944,947],{},[104,945,946],{},"SaveOrderCommand",", которая и создаёт, и обновляет, и удаляет",[98,949,950,951,306,953,306,956],{},"Нарушает единственную ответственность; разбейте на ",[104,952,305],{},[104,954,955],{},"Update",[104,957,309],{},[80,959,960,963],{},[98,961,962],{},"Query с побочным эффектом (логирует, шлёт уведомления)",[98,964,965],{},"Скрытая мутация; кэширование такого запроса сломает логику",[80,967,968,971],{},[98,969,970],{},"Один «God Handler» на все команды",[98,972,973],{},"Убивает всю пользу от разделения",[80,975,976,979],{},[98,977,978],{},"Возврат полного агрегата из Query",[98,980,981],{},"Тянет лишние данные, связывает read-модель с доменной",[80,983,984,987],{},[98,985,986],{},"Команда без валидации инвариантов",[98,988,989],{},"CQRS не отменяет проверку бизнес-правил",[10,991,993],{"id":992},"чек-лист-для-внедрения","Чек-лист для внедрения",[610,995,996,1011,1027,1033,1039,1045,1051],{},[26,997,998,1001,1002,306,1004,306,1007,1010],{},[18,999,1000],{},"Определите доменные области"," (",[104,1003,571],{},[104,1005,1006],{},"Analytics",[104,1008,1009],{},"Identity","…).",[26,1012,1013,302,1016,306,1018,306,1020,306,1022,306,1024,132],{},[18,1014,1015],{},"Разнесите папки",[104,1017,353],{},[104,1019,357],{},[104,1021,363],{},[104,1023,366],{},[104,1025,1026],{},"DTO\u002F",[26,1028,1029,1032],{},[18,1030,1031],{},"Договоритесь об именовании",": глагол действия для команд, Get для запросов.",[26,1034,1035,1038],{},[18,1036,1037],{},"Настройте шину \u002F Mediator",", чтобы контроллеры не знали о конкретных обработчиках.",[26,1040,1041,1044],{},[18,1042,1043],{},"Запретите кэширование на уровне команд",", разрешите на уровне запросов.",[26,1046,1047,1050],{},[18,1048,1049],{},"Пишите тесты по типу",": для команд — проверка побочного эффекта, для запросов — проверка возвращаемых данных.",[26,1052,1053,1056],{},[18,1054,1055],{},"Проведите ревью",": убедитесь, что ни один Query не мутирует состояние.",[10,1058,1060],{"id":1059},"итоги","Итоги",[15,1062,1063],{},"CQRS — не обязательный атрибут «серьёзной архитектуры».",[15,1065,641,1066,1069],{},[18,1067,1068],{},"инструмент упорядочивания",", который:",[23,1071,1072,1075,1078,1081],{},[26,1073,1074],{},"делает намерения кода явными,",[26,1076,1077],{},"развязывает руки для кэширования и масштабирования чтения,",[26,1079,1080],{},"упрощает тестирование и код-ревью,",[26,1082,1083],{},"задаёт понятную конвенцию для растущей команды.",[15,1085,1086],{},"Начните с лёгкого CQRS: разделите папки, дайте классам говорящие имена, уберите SELECT-запросы из команд.\nЭто займёт несколько часов \\ дней, а окупаться будет годами.",[1088,1089,1090],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":156,"searchDepth":432,"depth":432,"links":1092},[1093,1094,1095,1099,1103,1104,1110,1111,1117,1124,1125,1126,1127],{"id":12,"depth":399,"text":13},{"id":71,"depth":399,"text":72},{"id":135,"depth":399,"text":136,"children":1096},[1097,1098],{"id":140,"depth":432,"text":141},{"id":164,"depth":432,"text":165},{"id":180,"depth":399,"text":181,"children":1100},[1101,1102],{"id":187,"depth":432,"text":188},{"id":245,"depth":432,"text":246},{"id":330,"depth":399,"text":331},{"id":372,"depth":399,"text":373,"children":1105},[1106,1107,1108,1109],{"id":376,"depth":432,"text":377},{"id":471,"depth":432,"text":472},{"id":559,"depth":432,"text":560},{"id":597,"depth":432,"text":598},{"id":604,"depth":399,"text":605},{"id":659,"depth":399,"text":660,"children":1112},[1113,1114,1116],{"id":663,"depth":432,"text":664},{"id":734,"depth":432,"text":1115},"Паттерн __invoke",{"id":814,"depth":432,"text":815},{"id":829,"depth":399,"text":830,"children":1118},[1119,1120,1121,1122,1123],{"id":833,"depth":432,"text":834},{"id":840,"depth":432,"text":841},{"id":851,"depth":432,"text":852},{"id":869,"depth":432,"text":870},{"id":876,"depth":432,"text":877},{"id":883,"depth":399,"text":884},{"id":924,"depth":399,"text":925},{"id":992,"depth":399,"text":993},{"id":1059,"depth":399,"text":1060},"Backend","\u002Fcovers\u002Fcqrs.webp","2026-08-07","Архитектурный паттерн, который разделяет операции над системой.",false,"md",{},"\u002Fblog\u002Fcqrs",{"title":5,"description":1131},"blog\u002Fcqrs",[1139,1140,385],"architecture","pattern",null,"bFZJtolTlzzCJQMiL6kROb2nQs36aQ-wPSTMdMbDZCo",[1144,1149],{"title":1145,"path":1146,"stem":1147,"description":1148,"children":-1},"Универсальное руководство по Clean Architecture","\u002Fblog\u002Fclean-architecture","blog\u002Fclean-architecture","Главная цель Чистой архитектуры: создание системы, которая независима от фреймворков, баз данных, UI и внешних сервисов. Бизнес-логика изолирована и является ядром системы.",{"title":1150,"path":1151,"stem":1152,"description":1153,"children":-1},"LLM: Как это работает, где взять и как приручить","\u002Fblog\u002Fllm-itself","blog\u002Fllm-itself","Практическое руководство по большим языковым моделям: токены, температура, выбор модели, локальный и облачный запуск, форматы промптов и работа с агентами.",1792405819914]