Хз почему мюнусят. Я сейчас конкретно в веб приложениях, когда работаю с сущностями, у которых достаточно простое поведение, использую сущности как хранилище состояний без инвариантов. А сами инварианты, стейт транзишены и регистрацию событий в outbox пишу прямо в command handler. Да, теряется гибкость использования тактических паттернов. Но модули простые, и тащить сложности их реализации в этот код не хочется. Поэтому у меня микс - самое сложное ядро с насыщенным доменом, различными реализациями стратегий, где сложность богатой доменной модели оправдана - использую ее. В случаях, когда это нафиг не надо - использую DbContext напрямую вместе с анемичной моделью. Самое страшное, что может произойти с таким модулем, это смена персистенса на такой, который не удастся подружить с DbContext на уровне конфигурации. И то, я такой модуль за пол дня перепишу на другой движок (Mongo, DynamoDB и тд). Особенно если он покрыт тестами.
Так что с в целом я с комментарием согласен. На количество стейт транзишенов не влияет богатая у вас модель или анемичная. Инварианты может соблюдать там и там.
По поводу ListAll : я обычно делаю какой-то отдельный интерфейс для этого, если прямо очень надо. Так основной класс репозитория не засоряется и надо ещё поискать этот интерфейс под конкретную команду (обычно это какой-то cleanup или архивация). Проблема очевидная, но неявная (не отражено в домене) - сущности должны быть на одном партишене.
Но обычно все-таки я делаю вызов снаружи: есть квери, который возвращает список сущностей (чаще всего с пагинацией) и потом вызываю для каждой сущности команду. Это лишает разработчика иллюзии, что все транзанкционно и если кто-то добавит новую запись в процессе обработки, она обработана не будет.
Если это ок - ну ок. Если нет - явные / неявные локи, очереди и тд.
Вспоминаем ранее пройденное – репозиторий это паттерн доменного слоя и выражает он доменные концепции.
Например, какие? Кто является главным потребителем Repository? Application Layer. Соответственно и абстракция должна быть удобной для данного слоя. Но если пойти еще дальше, то у репозитория может быть только один метод на получение - Get(Identity id). И метод записи уже зависит от того, как реализован UoW. То, как вы выбираете данные, отношения к DDD не имеет, хоть SQL запросы прямо из вьюхи делайте.
Вашим репозиториям не нужен IQueryable (и паттерн Specification тоже)
К счастью, для связки .net + ef core есть в меру кривое почти красивое решение – использовать Expressions
Несмотря на то, что я считаю, что паттерн Specification именно в DDD не нужен, но может быть удобен, если есть четкий слой абстракции для доступа данных для Query стороны. Но в Вашем утверждении есть явное противоречие.
List
Такого метода в репозитории быть не должно, этот вопрос мы подробно разобрали в предыдущем разделе. Вместо этого репозиторий может иметь много аналогичных методов, но с разными параметрами и, главное, разными именами, выражающими доменные концепции.
Опять же, Aggregate это граница транзакционности. Если вы получаете несколько агрегатов через List или аналоги, а потом обновляете их все вместе - вы нарушаете данные границы - теперь у транзакции причин завалиться в N раз больше, где N число задействованных агрегатов. Для такого случая нужно либо использовать Saga, либо делать Aggregate больше, либо пересматривать модель более детально.
Domain Events – мостик к нормальности.
Единственный неочевидный момент в этом паттерне - ...
Это далеко не самый неочевидный момент в этом паттерне. Самый неочевидный момент, это то, что все обработчики доменных событий выпоняются синхронно. Это ок если все ваше приложение живет InMemory. Ваш RAM выступает в качестве базы данных, тразакция это запись в RAM. Но если это веб-приложение или вроде того, этот эффект ловины приведет к тому, что накопится такое количество Aggregate для коммита, что вероятность ошибки транзакции из-за оптимистичной конкурентности (а я надеюсь она у читателя настроена) стремится к бесконечности. По схеме как будто бы не понятно, в какой момент происходит транзакция. Описанный мой флоу такой command handler -> uow -> event handler -> uow -> event handler -> uow -> коммит транзакции. Если флоу такой: command handler -> uow -> транзакция -> event handler ... , то возникает проблема гарантий доставки ивента. Т.е. у комманды A есть сайд эффект B, но если база отвалилась во время выполнения сайд эффекта B - то система зависнет в неконсистентном состоянии. Поэтому я предпочитаю интеграционные события. Доменные у меня используются только для аудит логов и заполнения Outbox.
Дальше пока устал читать и писать, может позже дополню :)
Там уже можно использовать более сложные ключи группировки очередей, чем просто product_id. Например, сделать product_id:warehouse_id; или же product_id:category_id и параллелить еще глубже. Можно сделать руками теггирование через админку и особо нагруженные продукты обрабатывать в отдельной очереди.
В принципе все заказы обрабатываются настолько параллельно, насколько это возможно, при учете того, что главный конкурирующий ресурс здесь это сток продукта. Причем прием заказов максимально конкурентный и это главное. Тут тоже можно оптимизировать, и например смотреть, что если запас товара на скаде большой, а покупатель берет всего пару единиц, можно оптимистично ему вернуть, что заказ успешно принят, потому что вероятнее всего это так. И если уже все-таки не удалось забукать - переводим флаг "оптимистичное бронирование данного товара" в false и ждем в ручном режиме, и звоним покупателю. Такое будет случаться только с какими-нибудь "лабубу", для такого типа товара флаг "оптимистичное бронирование данного товара" всегда должен быть false. Соответственно заказ будет отмечен, как принятый, когда все товары, у которых "оптимистичное бронирование данного товара" = false пройдут полноценное бронирование.
В общем я тут на ходу изобретаю, комбинации оптимистик + пессимистик можно придумать очень разные :)
У вас как будто чутка смешаны понятия знания и понимания. Мне кажется, что знания - это константа, вроде значения числа Пи или заряда электрона. А понимание - это функция расчета Пи и заряда электрона. Т.е. знание отвечает на вопрос "что?", а понимание "почему?". Поэтому понимание ценится больше, потому что из одной формулы можно сделать 10-ки комбинацией получая разнообразные "что?", а вот из "что?" другие "что?" получить невозможно.
Поэтому правильнее было бы сказать "не вспомнил, как хранятся числа, потому что знал", а "подумал, как могут храниться числа и сам понял, как они хранятся (вычислил "что")", и вот тогда ответил. Иначе получается не очень валидный пример.
На Хабре аудитория в принципе не глупая и не однобокая. И все как один говорят, что приведенные вами примеры - это не то, что должно быть в интервью. По крайней мере на общеиндустриальном уровне, может быть если Ваш продукт это линтер или другой анализатор кода - тогда ладно.
Поэтому может быть стоит проявить немного гибкости и прислушаться к обратной связи. Возможно, Вы фильтруете достойных мотивированных работать и учится кандидатов тем, что они сиюминутно не знают некой "базы", которую Вы определили для них как фильтр.
Я всегда видел, будет ли человек полезен в работе и может ли он вписаться в команду. В ходе интервью проверял гибкий ли у него ум, внимательность в моменте, умение вовремя попросить помощи или наоборот злоупотребить этим; или же вообще зарыться, но не сдаться пока не разберёшься и поймёшь сам. Или же человек дико креативный, но надо быть с ним не одной волне и это тоже может быть очень полезно, но потребует оверхеда на интеграцию его в команду. А на синьерских позициях важнее баланс ширины и глубины знаний, чтобы можно было эффективно делегировать задачи с высокой степенью неопределенности.
В общем я к чему это говорю - на моих собеседованиях я могу дать задачу написать функцию, которая отправляет http запрос. А потом понемногу по ходу диалога расспросить про асинхронность, многопоточность, попросить сделать этот код конфигурируемым (base_url / apiKey из конфига, переменных среды, секретов, паттерн стратегия или Dependency Injection), попросить сделать этот код тестируемым, написать тест, далее подняться на дизайн API, обсудить аутентификацию, авторизацию (OpenID, oAuth, apiKey, mTLS), идемпотентность, tracing, rate limiting, retry policy, обработку ошибок и тд и ТП. По факту от этой задачи можно плясать куда угодно и стимулировать кандидата объяснять почему он выбрал этот подход и какие были альтернативы. И даже если человек не работал и не знает, я могу попросить его порассуждать, а как бы он это реализовал, хотя бы наивным способом. Так можно узнать о кандидате очень много, при этом стимулируя его говорить и демонстрировать навыки, лишь подталкивая его вопросами в интересующую вас сторону.
А Ваш пример на мой взгляд проверяет только то, насколько такой код вгоняет кандидата в ступор и способен ли он достать правильные ответы из чертог своей памяти.
Я бы например просто скачал файл opeapi.json и прикрепил к документу в базе знаний или к репозиторию в git.
Хотя тут есть нюанс с индексацией - искать по базе знаний было бы не очень удобно. Тут можно было бы рассмотреть создание какого-то конвертера в тот формат, в котором реализована база знаний.
А вот в documentation as a code github отлично справится с поиском по openapi.json для поиска операции и ее параметров.
Можно в машине элементы кузова не сварить, а на клей посадить, и тоже будет работать... Какое-то время.
Задачи "чтоб заработало" стоит оставлять на работе или в личных одноразовых утилитах.
Здесь на Хабре читатели с большего это инженеры, а инженеры хотят анализа: контекст такой, опции такие и такие, решил сравнить. Вариант 1 оказался самым быстрым с точки зрения разработки MVP, но вариант 2 более гибкий и предоставляет больше кастомизации в долгосрочной перспективе. Вывод: для своего кейса я пошел с вариантом 1.
Тогда инженер захочет задать дополнительные вопросы, поделиться своим опытом, указать на неточности. А так получается в последнее время инженер только что и делает, как указывает на неточности, какой-то байт на комментарии)
Например возьмём такой сценарий - допустим я закупаюсь на летние шашлыки на компанию. Нужна большая корзина на колесиках, приблизительно но 3-4 пакета + угли + бутылки с водой. Пробовали пробиваться на кассе самообслуживания. Какие сложности:
Взвешивание на кассе самообслуживания - то товар не найден, то взвесил, потом положил на весовую платформу, а пишет, что не соответствует. Решение: взвешивание сразу в овощном отделе по номерам позиций. Положил в пакетик, наклеил штрих-код. Поднял, опустил - проверил, что все ок; альтернативно в некоторых местах есть специальные тети на весах, но мы же хотим вообще без человеков.
Маленькие платформы куда выкладывается отсканированный товар. Буквально невозможно все это вместить даже в пакетах. Решение: возможно добавить несколько касс с платформами побольше. Как минимум это даст шанс дождаться , пока именно они освободятся и занять их.
Неудобно закреплять пакеты, нигде. Если просто пробить пакет и пытаться его расправить, он нормально в открытом состоянии находится не будет. Поэтому приходится прибегать к лайфхаку: первым пробиваем что-то тяжёлое и твердое, что хотим положить на дно пакета, например полторушку минералки или пенного. Ставим на платформу, добавляем пакет. Раскрываем пакет. Поднимаем пенное - аппарат пищит. Кладём пенное в пакет, кладём пакет с пенным на платформу, надеемся, что аппарат успокоится. Теперь пакет кое-как зафиксирован. Решение: сходу ничего простого и удачного не придумал.
Отдельный кейс: Касание весовой платформы. Я парень не маленький, а в зимней парке особенно. И бывало такое, что в магазине тепло, я расстегнулся ну и хожу так. Прихожу на кассу, пробиваю, а мне ошибка весовой платформы. Я не понимаю как так, причем какая-то переменная, то пищит то нет. Я подумал, что машина тупая. А в следующий раз заметил, что у меня куртка полы парки когда я кладу на весовую платформу что-то касаются ее совсем чуть-чуть. Решение: сделать какой-то относительно низкий бортик внизу панели, чтобы можно было прислониться к кассе, пусть даже непроизвольно. Но это так, мелочи.
А RPS, CCU, R, A точно важнее авторизации и аутентификации? А как же панель управления, аналитика и другие элементы системы, которые имеют ключевую бизнес ценность? Что да двойные стандарты.
Наклепали (не Вы конкретно) какой-то горе-фреймворк для расчета сферического коня в вакууме, и теперь пытаетесь подогнать реальность под него (Вы в частности). Типа "мы не придумали как это считать и чтобы классно на интервью выглядело, поэтому давайте не считать".
На мой взгляд такой материал не несёт никакой ценности для тех, кто всё-таки решил разобраться в системном дизайне.
Джунам и миддлам не до этого, им бы просто основы языка и среды разработки понять (включая базовые принципы развертки, CI/CD, базы данных, юнит тестирование и ТД).
А синьеров с ролью тех лида Ваш пример собьёт с толку. Как делать рассчет уже написана масса статей, Вы сами приводите ссылки. А дизайн у Вас получился как минимум спорный, и чего-то новое подчерпнуть не сможет даже техлид в небольшом стартапе (5-6 лет опыта), а уж техлид в бигтехе (10+ лет опыта) тем более.
Остаются всякие недосиеньеры, которые каждый год ходят по собесам в надежде заткнуть собой какую-нибудь дырку за побольше денег. Но для них стратегия другая. Думать и понимать не надо, надо заучить, "задрочить", и пока не забыл идти на интервью. Потому что на самой работе все будет совсем не так.
Тогда для кого статья?
Я в комментарии старался подсветить читателю стати, что не надо думать, что читатель какой-то не такой, если его опыт совсем другой, если он не согласен с Вашим решением. Если у него есть свое решение, а его предложения заворачивают "более опытные" на работе или на интервью. Возможно его решение даже технически лучше "старшего" или "экзаменатора", он подошёл более творчески и креативно, но язык у него подвешен не как у старшего или синдром самозванца. И это надо проламывать, в себе настаивать, искать способы донести бизнесу и команде свой вариант технического решения, но и при этом слушать обратную связь и учиться ее фильтровать. Если критика по делу, а не вкусовщина - принять, поправить решение, выучить что-то новое, заполнить пробелы. Если критика в роде "я говорю твое решение говно, значит говно" - тоже сделать выводы, но уже не о себе.
Для новичков: я бы не стал руководствоваться этой статьей при подготовке к System Design. Найдите книжку топ 100 типовых задач на систем дизайн интервью и посмотрите 5 роликов на Ютубе на английском и 5 на русском. Чтобы понимать, в каком стиле может быть коммуникация с инеивртювером. И делать это за недели 2-3 до собесов, чтобы было свежо в голове. Это спринт, а не марафон.
Секрет System Design секции интервью в том, попадется ли вам знакомая задачка и какое будет настроение у интервьювера. Но даже если вам попадется именно задача на сокращатель ссылок, то с таким решением как в статье вас развернет даже интервьювер, у которого вчера жена родила.
Секрет System Design в том, что даже одну и ту же систему мы можем построить по разному.
Соответственно, типичный интервьювер решение задачи интерпретирует как хочет, особенно оценка будет не в вашу пользу, если вы решаете задачу не так, как в методичке, или не так, как решил бы ее сам интервьювер. Гейм Овер.
Нет «идеального» дизайна — есть оптимальный для конкретных требований и усоливий.
Даже для конкретных требований и условий бывают очень разные градации оптимальности. Все зависит от степени детализации требований. Чем дольше собираются требования, тем позже начало разработки, но тем выше точность в выборе решения. В современном Agile мире дизайн не бывает оптимальным никогда, так как требования собираются прямо в процессе разработки. Помимо всего прочего, команду болтает со стороны в сторону: что-то не учел PO, новая аналитика от продакт менеджера показывает, что пользователю нужно B, а изначально разрабатывалось под А. Поэтому дизайн получается просто минимально рабочим. Я думаю не нужно объяснять разницу между "оптимально" и "минимально жизнеспособно". И чем дольше разрабатывается продукт в таком режим, тем менее жизнеспособным он становится - постоянные сбои в одном месте из-за того, что "ветер подул" в другом. И тут такие исходы:
Продукт умирает в зародыше по не техническим причинам (1-12 месяцев).
Продукт умирает от того, что тот способ, которым строили минимально жизнеспособно решение не выдержал конкуренции и в конечном итоге начал отставать от конкурентов по скорости внедрения фич (через 1-3 года). Причем у конкурентов тоже может быть криво косо, но инвестор залил больше денег и теперь не 9 женщин рожает ребенка за месяц, а 19. А может быть и так, что денег залили вам, но у конкурента его 9 женщин рожает быстрее, чем ваши 19. В общем это отчасти лотерея.
Продукт растет, но выживает на грани, скорость внедрения фич критически минимальная, стоимость обслуживания критически максимальная (3-6 лет); но база клиентов покрыта и рефакторинг начался более менее вовремя. Но проблема в том, что его делают те люди, которые делали "чтобы заработало" и делать "оптимально" они не знают как, а если и знают, то уже забыли; в результате рефакторинга получается чуть менее уродливое нечто и так до следующего рефакторинга через 2-3 года.
Продукт выживает на грани, но без рефакторинга. Новые фичи перестают добавляться. Продукт превращается в Легаси и уходит на саппорт (6+ лет до бесконечности).
Продукт изначально строится как клон какого-то другого софта. Тут можно попробовать построить что-то оптимальное, но где вы найдёте людей, которые обладают нужными знаниями и опытом... В итоге получается чуть лучше и чуть дешевле, чем у конкурента и может быть даже получится переманить часть аудитории. Или "импортозаместить". А может быть и нет.
Реальная работа это: придти на новый проект и разобраться, как он устроен. Найти способ добавить новую фичу и не сломать существующие. Перед этим провести валидацию требований, а достаточно ли информации, чтобы сделать хоть что-то. Протянуть эту фичу по всей системе. И тд.
Поэтому системный архитектор сейчас это просто самый опытный человек в компании с точки зрения знания продукта и его технической реализации; и его самая главная задача, это помогать добавлять новые функции на кросс-командном уровне, чтобы сохранить "минимальную жизнеспособность". Откуда ему знать, как делать оптимальные системы, если он сам с ними никогда не работал и у него такой задачи не стоит.
А так, систем дизайн интервью проверяет только одну вещь - как вы готовились к систем дизайн интервью и вашу удачу. Вот и думайте.
Во-первых, ОРМ в виде того же Entity Framework это в первую очередь реализация UnitOfWork. Т.е. работа с транзакционными границами используя язык программирования, а не язык SQL.
Вторая суперсила EF - change tracking. Т.е. при применении изменений рассчитывается diff и далее накатывается только он (если вообще появился какой-то diff). Фича классная, но опциональная.
Третья суперсила - сборка аггрегата за счёт маппинга. Т.е. у вас аггрегат это Todo list, при этом Todo items нормализованы в отдельную таблицу, то чтобы собрать агрегат нужно будет сделать простые джойны. И EF сделает аггрегацию автоматически когда вы получаете аггрегат для выполнения операции над ним. Ну или лениво подгрузит, есть вам не так критичен перфоманс.
А никто никогда и не говорил выполнять сложные аггрегации из БД через EF. Эти фреймворк для менеджмента данных (command side), а не для формирования из представлений (query side), хотя какие-то кастрированные возможности у него конечно есть и можно использовать в примитивных случаях.
По классике: когда в руках молоток, все вокруг кажется гвоздями.
View Model это при байндинги. Байндинги это декларативная реализация паттерна наблюдатель. Можно сказать, что View Model это декларативный аналог Controller + снятие с Model задач эмиттера событий.
Я уже давно вынашиваю идею написать серию статей об MV* семействе паттернов. Но когда смотрю на объем черновиков по этой теме - ужасаюсь от объема материала. Чтобы реально понять, нужно приводить примеры кода, сравнивать различные реализации (MVC в smalltalk, objective-c/swift и например Angular JS это разные вариации MVC, а это мы еще сервера не коснулись) , делать много анализа. Это уже диссертация получится, которую прочитаешь разве только посвятив ей весь обед. А делать такого рода статьи как данная "кладите код сюда и будет хорошо", при этом очередной раз ретранслируя распространненные заблуждения, я себе позволить не могу.
Хоть я и фуллстек с бОльшим уклоном в бэк, но мне есть что рассказать по этой теме, для новичков так точно. Если интересно, ставь +. Если наберем 50 плюсов на комментарии - подготовлю и выпущу свою статью об MVC в течении месяца.
В чистом MVC об ORM и не слышали. Классический MVC паттерн для императивного UI фреймворка из Smalltalk для десктопных приложений и некоторых вариаций embedded систем.
И даже в веб вариации MVC модель это байндинг полей из Body / Query String / Path / Headers, валидация построенной модели (смотри ModelBinders & ModelState & ModelState.IsValid в ASP.NET Core), аутентификация, авторизация. А дальше уже идут вызовы application services, которые предосталяют интерфейсы юз кейсов бизнес логики.
Но MVC будет работать как часы, даже если у вас нет никакой бизнес логики и ваше приложение это просто рендерер статических страниц.
Хз почему мюнусят. Я сейчас конкретно в веб приложениях, когда работаю с сущностями, у которых достаточно простое поведение, использую сущности как хранилище состояний без инвариантов. А сами инварианты, стейт транзишены и регистрацию событий в outbox пишу прямо в command handler. Да, теряется гибкость использования тактических паттернов. Но модули простые, и тащить сложности их реализации в этот код не хочется. Поэтому у меня микс - самое сложное ядро с насыщенным доменом, различными реализациями стратегий, где сложность богатой доменной модели оправдана - использую ее. В случаях, когда это нафиг не надо - использую DbContext напрямую вместе с анемичной моделью. Самое страшное, что может произойти с таким модулем, это смена персистенса на такой, который не удастся подружить с DbContext на уровне конфигурации. И то, я такой модуль за пол дня перепишу на другой движок (Mongo, DynamoDB и тд). Особенно если он покрыт тестами.
Так что с в целом я с комментарием согласен. На количество стейт транзишенов не влияет богатая у вас модель или анемичная. Инварианты может соблюдать там и там.
А что там с db context?
По поводу
ListAll: я обычно делаю какой-то отдельный интерфейс для этого, если прямо очень надо. Так основной класс репозитория не засоряется и надо ещё поискать этот интерфейс под конкретную команду (обычно это какой-то cleanup или архивация). Проблема очевидная, но неявная (не отражено в домене) - сущности должны быть на одном партишене.Но обычно все-таки я делаю вызов снаружи: есть квери, который возвращает список сущностей (чаще всего с пагинацией) и потом вызываю для каждой сущности команду. Это лишает разработчика иллюзии, что все транзанкционно и если кто-то добавит новую запись в процессе обработки, она обработана не будет.
Если это ок - ну ок. Если нет - явные / неявные локи, очереди и тд.
Тогда без гарантий выполнения сайд эффекта. А если с гарантиями, то доменное событие превращается в интеграционное.
Например, какие? Кто является главным потребителем Repository? Application Layer. Соответственно и абстракция должна быть удобной для данного слоя. Но если пойти еще дальше, то у репозитория может быть только один метод на получение - Get(Identity id). И метод записи уже зависит от того, как реализован UoW. То, как вы выбираете данные, отношения к DDD не имеет, хоть SQL запросы прямо из вьюхи делайте.
Несмотря на то, что я считаю, что паттерн Specification именно в DDD не нужен, но может быть удобен, если есть четкий слой абстракции для доступа данных для Query стороны. Но в Вашем утверждении есть явное противоречие.
Опять же, Aggregate это граница транзакционности. Если вы получаете несколько агрегатов через List или аналоги, а потом обновляете их все вместе - вы нарушаете данные границы - теперь у транзакции причин завалиться в N раз больше, где N число задействованных агрегатов. Для такого случая нужно либо использовать Saga, либо делать Aggregate больше, либо пересматривать модель более детально.
Это далеко не самый неочевидный момент в этом паттерне. Самый неочевидный момент, это то, что все обработчики доменных событий выпоняются синхронно. Это ок если все ваше приложение живет InMemory. Ваш RAM выступает в качестве базы данных, тразакция это запись в RAM. Но если это веб-приложение или вроде того, этот эффект ловины приведет к тому, что накопится такое количество Aggregate для коммита, что вероятность ошибки транзакции из-за оптимистичной конкурентности (а я надеюсь она у читателя настроена) стремится к бесконечности. По схеме как будто бы не понятно, в какой момент происходит транзакция. Описанный мой флоу такой command handler -> uow -> event handler -> uow -> event handler -> uow -> коммит транзакции. Если флоу такой: command handler -> uow -> транзакция -> event handler ... , то возникает проблема гарантий доставки ивента. Т.е. у комманды A есть сайд эффект B, но если база отвалилась во время выполнения сайд эффекта B - то система зависнет в неконсистентном состоянии. Поэтому я предпочитаю интеграционные события. Доменные у меня используются только для аудит логов и заполнения Outbox.
Дальше пока устал читать и писать, может позже дополню :)
Там уже можно использовать более сложные ключи группировки очередей, чем просто product_id. Например, сделать product_id:warehouse_id; или же product_id:category_id и параллелить еще глубже. Можно сделать руками теггирование через админку и особо нагруженные продукты обрабатывать в отдельной очереди.
В принципе все заказы обрабатываются настолько параллельно, насколько это возможно, при учете того, что главный конкурирующий ресурс здесь это сток продукта. Причем прием заказов максимально конкурентный и это главное. Тут тоже можно оптимизировать, и например смотреть, что если запас товара на скаде большой, а покупатель берет всего пару единиц, можно оптимистично ему вернуть, что заказ успешно принят, потому что вероятнее всего это так. И если уже все-таки не удалось забукать - переводим флаг "оптимистичное бронирование данного товара" в false и ждем в ручном режиме, и звоним покупателю. Такое будет случаться только с какими-нибудь "лабубу", для такого типа товара флаг "оптимистичное бронирование данного товара" всегда должен быть false. Соответственно заказ будет отмечен, как принятый, когда все товары, у которых "оптимистичное бронирование данного товара" = false пройдут полноценное бронирование.
В общем я тут на ходу изобретаю, комбинации оптимистик + пессимистик можно придумать очень разные :)
Возможно у меня баннерная слепота и я просто уже физически не обращаю внимания на неадекватность)
У вас как будто чутка смешаны понятия знания и понимания. Мне кажется, что знания - это константа, вроде значения числа Пи или заряда электрона. А понимание - это функция расчета Пи и заряда электрона. Т.е. знание отвечает на вопрос "что?", а понимание "почему?". Поэтому понимание ценится больше, потому что из одной формулы можно сделать 10-ки комбинацией получая разнообразные "что?", а вот из "что?" другие "что?" получить невозможно.
Поэтому правильнее было бы сказать "не вспомнил, как хранятся числа, потому что знал", а "подумал, как могут храниться числа и сам понял, как они хранятся (вычислил "что")", и вот тогда ответил. Иначе получается не очень валидный пример.
Ну это так, демагогия.
На Хабре аудитория в принципе не глупая и не однобокая. И все как один говорят, что приведенные вами примеры - это не то, что должно быть в интервью. По крайней мере на общеиндустриальном уровне, может быть если Ваш продукт это линтер или другой анализатор кода - тогда ладно.
Поэтому может быть стоит проявить немного гибкости и прислушаться к обратной связи. Возможно, Вы фильтруете достойных мотивированных работать и учится кандидатов тем, что они сиюминутно не знают некой "базы", которую Вы определили для них как фильтр.
Я всегда видел, будет ли человек полезен в работе и может ли он вписаться в команду. В ходе интервью проверял гибкий ли у него ум, внимательность в моменте, умение вовремя попросить помощи или наоборот злоупотребить этим; или же вообще зарыться, но не сдаться пока не разберёшься и поймёшь сам. Или же человек дико креативный, но надо быть с ним не одной волне и это тоже может быть очень полезно, но потребует оверхеда на интеграцию его в команду. А на синьерских позициях важнее баланс ширины и глубины знаний, чтобы можно было эффективно делегировать задачи с высокой степенью неопределенности.
В общем я к чему это говорю - на моих собеседованиях я могу дать задачу написать функцию, которая отправляет http запрос. А потом понемногу по ходу диалога расспросить про асинхронность, многопоточность, попросить сделать этот код конфигурируемым (base_url / apiKey из конфига, переменных среды, секретов, паттерн стратегия или Dependency Injection), попросить сделать этот код тестируемым, написать тест, далее подняться на дизайн API, обсудить аутентификацию, авторизацию (OpenID, oAuth, apiKey, mTLS), идемпотентность, tracing, rate limiting, retry policy, обработку ошибок и тд и ТП. По факту от этой задачи можно плясать куда угодно и стимулировать кандидата объяснять почему он выбрал этот подход и какие были альтернативы. И даже если человек не работал и не знает, я могу попросить его порассуждать, а как бы он это реализовал, хотя бы наивным способом. Так можно узнать о кандидате очень много, при этом стимулируя его говорить и демонстрировать навыки, лишь подталкивая его вопросами в интересующую вас сторону.
А Ваш пример на мой взгляд проверяет только то, насколько такой код вгоняет кандидата в ступор и способен ли он достать правильные ответы из чертог своей памяти.
Я бы например просто скачал файл opeapi.json и прикрепил к документу в базе знаний или к репозиторию в git.
Хотя тут есть нюанс с индексацией - искать по базе знаний было бы не очень удобно. Тут можно было бы рассмотреть создание какого-то конвертера в тот формат, в котором реализована база знаний.
А вот в documentation as a code github отлично справится с поиском по openapi.json для поиска операции и ее параметров.
Можно в машине элементы кузова не сварить, а на клей посадить, и тоже будет работать... Какое-то время.
Задачи "чтоб заработало" стоит оставлять на работе или в личных одноразовых утилитах.
Здесь на Хабре читатели с большего это инженеры, а инженеры хотят анализа: контекст такой, опции такие и такие, решил сравнить. Вариант 1 оказался самым быстрым с точки зрения разработки MVP, но вариант 2 более гибкий и предоставляет больше кастомизации в долгосрочной перспективе. Вывод: для своего кейса я пошел с вариантом 1.
Тогда инженер захочет задать дополнительные вопросы, поделиться своим опытом, указать на неточности. А так получается в последнее время инженер только что и делает, как указывает на неточности, какой-то байт на комментарии)
Например возьмём такой сценарий - допустим я закупаюсь на летние шашлыки на компанию. Нужна большая корзина на колесиках, приблизительно но 3-4 пакета + угли + бутылки с водой. Пробовали пробиваться на кассе самообслуживания. Какие сложности:
Взвешивание на кассе самообслуживания - то товар не найден, то взвесил, потом положил на весовую платформу, а пишет, что не соответствует. Решение: взвешивание сразу в овощном отделе по номерам позиций. Положил в пакетик, наклеил штрих-код. Поднял, опустил - проверил, что все ок; альтернативно в некоторых местах есть специальные тети на весах, но мы же хотим вообще без человеков.
Маленькие платформы куда выкладывается отсканированный товар. Буквально невозможно все это вместить даже в пакетах. Решение: возможно добавить несколько касс с платформами побольше. Как минимум это даст шанс дождаться , пока именно они освободятся и занять их.
Неудобно закреплять пакеты, нигде. Если просто пробить пакет и пытаться его расправить, он нормально в открытом состоянии находится не будет. Поэтому приходится прибегать к лайфхаку: первым пробиваем что-то тяжёлое и твердое, что хотим положить на дно пакета, например полторушку минералки или пенного. Ставим на платформу, добавляем пакет. Раскрываем пакет. Поднимаем пенное - аппарат пищит. Кладём пенное в пакет, кладём пакет с пенным на платформу, надеемся, что аппарат успокоится. Теперь пакет кое-как зафиксирован. Решение: сходу ничего простого и удачного не придумал.
Отдельный кейс: Касание весовой платформы. Я парень не маленький, а в зимней парке особенно. И бывало такое, что в магазине тепло, я расстегнулся ну и хожу так. Прихожу на кассу, пробиваю, а мне ошибка весовой платформы. Я не понимаю как так, причем какая-то переменная, то пищит то нет. Я подумал, что машина тупая. А в следующий раз заметил, что у меня куртка полы парки когда я кладу на весовую платформу что-то касаются ее совсем чуть-чуть. Решение: сделать какой-то относительно низкий бортик внизу панели, чтобы можно было прислониться к кассе, пусть даже непроизвольно. Но это так, мелочи.
А RPS, CCU, R, A точно важнее авторизации и аутентификации? А как же панель управления, аналитика и другие элементы системы, которые имеют ключевую бизнес ценность? Что да двойные стандарты.
Наклепали (не Вы конкретно) какой-то горе-фреймворк для расчета сферического коня в вакууме, и теперь пытаетесь подогнать реальность под него (Вы в частности). Типа "мы не придумали как это считать и чтобы классно на интервью выглядело, поэтому давайте не считать".
На мой взгляд такой материал не несёт никакой ценности для тех, кто всё-таки решил разобраться в системном дизайне.
Джунам и миддлам не до этого, им бы просто основы языка и среды разработки понять (включая базовые принципы развертки, CI/CD, базы данных, юнит тестирование и ТД).
А синьеров с ролью тех лида Ваш пример собьёт с толку. Как делать рассчет уже написана масса статей, Вы сами приводите ссылки. А дизайн у Вас получился как минимум спорный, и чего-то новое подчерпнуть не сможет даже техлид в небольшом стартапе (5-6 лет опыта), а уж техлид в бигтехе (10+ лет опыта) тем более.
Остаются всякие недосиеньеры, которые каждый год ходят по собесам в надежде заткнуть собой какую-нибудь дырку за побольше денег. Но для них стратегия другая. Думать и понимать не надо, надо заучить, "задрочить", и пока не забыл идти на интервью. Потому что на самой работе все будет совсем не так.
Тогда для кого статья?
Я в комментарии старался подсветить читателю стати, что не надо думать, что читатель какой-то не такой, если его опыт совсем другой, если он не согласен с Вашим решением. Если у него есть свое решение, а его предложения заворачивают "более опытные" на работе или на интервью. Возможно его решение даже технически лучше "старшего" или "экзаменатора", он подошёл более творчески и креативно, но язык у него подвешен не как у старшего или синдром самозванца. И это надо проламывать, в себе настаивать, искать способы донести бизнесу и команде свой вариант технического решения, но и при этом слушать обратную связь и учиться ее фильтровать. Если критика по делу, а не вкусовщина - принять, поправить решение, выучить что-то новое, заполнить пробелы. Если критика в роде "я говорю твое решение говно, значит говно" - тоже сделать выводы, но уже не о себе.
Для новичков: я бы не стал руководствоваться этой статьей при подготовке к System Design. Найдите книжку топ 100 типовых задач на систем дизайн интервью и посмотрите 5 роликов на Ютубе на английском и 5 на русском. Чтобы понимать, в каком стиле может быть коммуникация с инеивртювером. И делать это за недели 2-3 до собесов, чтобы было свежо в голове. Это спринт, а не марафон.
Секрет System Design секции интервью в том, попадется ли вам знакомая задачка и какое будет настроение у интервьювера. Но даже если вам попадется именно задача на сокращатель ссылок, то с таким решением как в статье вас развернет даже интервьювер, у которого вчера жена родила.
Соответственно, типичный интервьювер решение задачи интерпретирует как хочет, особенно оценка будет не в вашу пользу, если вы решаете задачу не так, как в методичке, или не так, как решил бы ее сам интервьювер. Гейм Овер.
Даже для конкретных требований и условий бывают очень разные градации оптимальности. Все зависит от степени детализации требований. Чем дольше собираются требования, тем позже начало разработки, но тем выше точность в выборе решения. В современном Agile мире дизайн не бывает оптимальным никогда, так как требования собираются прямо в процессе разработки. Помимо всего прочего, команду болтает со стороны в сторону: что-то не учел PO, новая аналитика от продакт менеджера показывает, что пользователю нужно B, а изначально разрабатывалось под А. Поэтому дизайн получается просто минимально рабочим. Я думаю не нужно объяснять разницу между "оптимально" и "минимально жизнеспособно". И чем дольше разрабатывается продукт в таком режим, тем менее жизнеспособным он становится - постоянные сбои в одном месте из-за того, что "ветер подул" в другом. И тут такие исходы:
Продукт умирает в зародыше по не техническим причинам (1-12 месяцев).
Продукт умирает от того, что тот способ, которым строили минимально жизнеспособно решение не выдержал конкуренции и в конечном итоге начал отставать от конкурентов по скорости внедрения фич (через 1-3 года). Причем у конкурентов тоже может быть криво косо, но инвестор залил больше денег и теперь не 9 женщин рожает ребенка за месяц, а 19. А может быть и так, что денег залили вам, но у конкурента его 9 женщин рожает быстрее, чем ваши 19. В общем это отчасти лотерея.
Продукт растет, но выживает на грани, скорость внедрения фич критически минимальная, стоимость обслуживания критически максимальная (3-6 лет); но база клиентов покрыта и рефакторинг начался более менее вовремя. Но проблема в том, что его делают те люди, которые делали "чтобы заработало" и делать "оптимально" они не знают как, а если и знают, то уже забыли; в результате рефакторинга получается чуть менее уродливое нечто и так до следующего рефакторинга через 2-3 года.
Продукт выживает на грани, но без рефакторинга. Новые фичи перестают добавляться. Продукт превращается в Легаси и уходит на саппорт (6+ лет до бесконечности).
Продукт изначально строится как клон какого-то другого софта. Тут можно попробовать построить что-то оптимальное, но где вы найдёте людей, которые обладают нужными знаниями и опытом... В итоге получается чуть лучше и чуть дешевле, чем у конкурента и может быть даже получится переманить часть аудитории. Или "импортозаместить". А может быть и нет.
Реальная работа это: придти на новый проект и разобраться, как он устроен. Найти способ добавить новую фичу и не сломать существующие. Перед этим провести валидацию требований, а достаточно ли информации, чтобы сделать хоть что-то. Протянуть эту фичу по всей системе. И тд.
Поэтому системный архитектор сейчас это просто самый опытный человек в компании с точки зрения знания продукта и его технической реализации; и его самая главная задача, это помогать добавлять новые функции на кросс-командном уровне, чтобы сохранить "минимальную жизнеспособность". Откуда ему знать, как делать оптимальные системы, если он сам с ними никогда не работал и у него такой задачи не стоит.
А так, систем дизайн интервью проверяет только одну вещь - как вы готовились к систем дизайн интервью и вашу удачу. Вот и думайте.
Ох вэйт.
Во-первых, ОРМ в виде того же Entity Framework это в первую очередь реализация UnitOfWork. Т.е. работа с транзакционными границами используя язык программирования, а не язык SQL.
Вторая суперсила EF - change tracking. Т.е. при применении изменений рассчитывается diff и далее накатывается только он (если вообще появился какой-то diff). Фича классная, но опциональная.
Третья суперсила - сборка аггрегата за счёт маппинга. Т.е. у вас аггрегат это Todo list, при этом Todo items нормализованы в отдельную таблицу, то чтобы собрать агрегат нужно будет сделать простые джойны. И EF сделает аггрегацию автоматически когда вы получаете аггрегат для выполнения операции над ним. Ну или лениво подгрузит, есть вам не так критичен перфоманс.
А никто никогда и не говорил выполнять сложные аггрегации из БД через EF. Эти фреймворк для менеджмента данных (command side), а не для формирования из представлений (query side), хотя какие-то кастрированные возможности у него конечно есть и можно использовать в примитивных случаях.
По классике: когда в руках молоток, все вокруг кажется гвоздями.
Мне кажется некоторые вопросы достаточно креативны, чтобы обсудить их с кожаным
View Model это при байндинги. Байндинги это декларативная реализация паттерна наблюдатель. Можно сказать, что View Model это декларативный аналог Controller + снятие с Model задач эмиттера событий.
Я уже давно вынашиваю идею написать серию статей об MV* семействе паттернов. Но когда смотрю на объем черновиков по этой теме - ужасаюсь от объема материала. Чтобы реально понять, нужно приводить примеры кода, сравнивать различные реализации (MVC в smalltalk, objective-c/swift и например Angular JS это разные вариации MVC, а это мы еще сервера не коснулись) , делать много анализа. Это уже диссертация получится, которую прочитаешь разве только посвятив ей весь обед. А делать такого рода статьи как данная "кладите код сюда и будет хорошо", при этом очередной раз ретранслируя распространненные заблуждения, я себе позволить не могу.
Хоть я и фуллстек с бОльшим уклоном в бэк, но мне есть что рассказать по этой теме, для новичков так точно. Если интересно, ставь +. Если наберем 50 плюсов на комментарии - подготовлю и выпущу свою статью об MVC в течении месяца.
В чистом MVC об ORM и не слышали. Классический MVC паттерн для императивного UI фреймворка из Smalltalk для десктопных приложений и некоторых вариаций embedded систем.
И даже в веб вариации MVC модель это байндинг полей из Body / Query String / Path / Headers, валидация построенной модели (смотри ModelBinders & ModelState & ModelState.IsValid в ASP.NET Core), аутентификация, авторизация. А дальше уже идут вызовы application services, которые предосталяют интерфейсы юз кейсов бизнес логики.
Но MVC будет работать как часы, даже если у вас нет никакой бизнес логики и ваше приложение это просто рендерер статических страниц.
Approved, можно мержить!
Что можно подсветить, так это гибкость всего данного решения:
Изменилась стратегия хранилища данных - заменили репозиторий;
Необходимо заскейлится - вынесли application services на другую машину и общаетесь с ней по rpc вместо inmemory;
Необходимо добавить поддержку нового протокола типа gRPC или GraphQL: добавляем.
Нужно подключить RESTFul API - подрубаем MVC фреймворк, который скорее всего уже написан под ваш язык программирования.
90% сценариев расширения приложения покрыто.