Здравствуйте, Vladek, Вы писали:
AVK>>А как быть с SRP? V>Думать о SRP когда какой-то код уже написан.
А пока не написан, то фиг ли думать, трясти надо?
V> Красивое и стройное решение сразу не напишешь, надо быть готовым что код будет так или иначе писаться несколько раз.
Вопрос в другом — зачем писать заведомо кривое при любых раскладах решение? И, кстати, чем активнее ты кладешь болт на SRP, тем труднее потом это все будет переписывать.
V>>>Я использую фабрики или оператор new, чтобы создать объект, которого раньше не существовало, и репозитории, чтобы достать объект, который уже где-то есть. AVK>>Вопрос был в том зачем это делать, если нужно внести изменения. V>Чтобы модель предметной области проявила своё поведение.
Чего?
V> Другими словами, чтобы отработала бизнес-логика.
Зачем для отрабатывания бизнес-логики читать ненужные данные?
V> Если вся суть работы приложения заключается в изменение таблиц БД V>, то это просто накопление данных и тогда действительно модель предметной области не нужна. если бизнес-логика есть, я оформляю её в виде модели предметной области.
К чему эта абстракция? Вопрос был вполне конкретным — зачем что то читать, если задача стоит записать?
AVK>>Считывать данные из БД чтобы их поменять? Нет, это совсем не логично и не просто. V>Пробуждаем модель предметной области
Ты перешел к описанию решения. А я спрашивал про задачу.
AVK>>Подожди. Вот есть у тебя предикат, по которому, к примеру, надо изменить определенное поле. Т.е. нужны, в итоге, примерно такие запросы: AVK>>
AVK>>UPDATE Log SET DeletedOn = GETDATE() WHERE CreatedOn < DATEADD(-30, D, GETDATE())
AVK>>...
AVK>>UPDATE Tasks SET CaptureCount = CaptureCount + 3 WHERE Type = 4
AVK>>
AVK>>Как это выразить в твоей модели? V>Что мы здесь моделируем, какая бизнес-задача?
Да вроде все тривиально. В первом случае пометить записи в логе старше 30 дней удаленными. Во втором — увеличить количество захватов задач на единицу для типа 4. Никакой специфики БД, все в терминах предметной области.
Так будет решение, или опять витание в абстракциях без ответа на конкретный вопрос?
V> Ты рассуждаешь об изменении бд — ну так и отобрази это в коде — выполни нужные запросы к серверу бд и дело с концом.
Я спрашиваю как предполагается это все делать у тебя.
AVK>>Списываем баланс с одного счета, начисляем на другой. Последовательность действий: считать операции по исходному счету, просуммировать, получить баланс, вычислить корректирующую сумму, записать сумму с минусом на старый счет, с плюсом на новый. Все, разумеется, в одной транзакции. Вычисление баланса, разумеется, нужно более чем в одном месте. AVK>>Достаточно конкретный пример или еще что то уточнить нужно? V>Уточнять можно бесконечно.
Но ответа все равно не будет?
V>>>Стереотипный код писать я тоже не люблю. AVK>>Зачем тогда советуешь? Особенно это касается рассказов про CQRS, чтоб Фаулеру икалось. V>Советуй другое.
Т.е. ответа опять нет.
AVK>>Зато появляется стадо контрактов, которое таки влияет на остальной проект. А, учитывая что все нормальные ORM умеют работать с POCO, получается куча развлечений по написанию кода без какого либо смыслового наполнения. Зато красивого и простого, да. V>Я не использую POCO объекты для ORM в качестве модели предметной области. Раньше я так делал и это был говно-код
Почму?
V>который очень быстро начинал вонять по мере развития проекта.
Конкретнее.
V>Модель предметной области для реализации бизнес-логики и простые структуры данных для отображения пользователю — вот такой код мне удобнее всего (и получается короче).
Приведи пример. Т.е. был такой говнокод на РОСО, потом ты его переписал и он стал вот таким вот прекрасным.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Max Mustermann, Вы писали:
MM>Какая прохладная история. Я-то думал, что "пишу всё на хранимках с 91-го — хорошо себя чуствую" уже перевелись, а подиж-ты, жива старая гвардия!
А новая гвардия что делает? Пишет мегарешение, которое кушает 5 килобаксов в месяц за облако, выполняя при этом малую долю работы похожей системы, тратящей 1.5К в месяц? При том что и у второй системы не все идеально и с перфомансом, и с лишним бойлерплейтом.
Если что — это как раз реальный пример применения модных технологий и CQRS.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, Vladek, Вы писали:
AVK>>>А как быть с SRP? V>>Думать о SRP когда какой-то код уже написан.
AVK>А пока не написан, то фиг ли думать, трясти надо?
V>> Красивое и стройное решение сразу не напишешь, надо быть готовым что код будет так или иначе писаться несколько раз.
AVK>Вопрос в другом — зачем писать заведомо кривое при любых раскладах решение? И, кстати, чем активнее ты кладешь болт на SRP, тем труднее потом это все будет переписывать.
V>>>>Я использую фабрики или оператор new, чтобы создать объект, которого раньше не существовало, и репозитории, чтобы достать объект, который уже где-то есть. AVK>>>Вопрос был в том зачем это делать, если нужно внести изменения. V>>Чтобы модель предметной области проявила своё поведение.
AVK>Чего?
V>> Другими словами, чтобы отработала бизнес-логика.
AVK>Зачем для отрабатывания бизнес-логики читать ненужные данные?
Затем, что сколь-нибудь нетривиальная программа имеет своё внутреннее состояние, накопленное за время жизни программы или загруженное откуда-нибудь. Вот это я и понимаю под пробуждением модели предметной области — привести программу в состояние, адекватное выполнению команды на изменение. Что может привести к чтению данных из бд перед записью данных обратно в бд.
Допустим, мы только что запустили программу, которая хранит свои данные в бд — первым делом она что-то оттуда прочитает.
V>> Если вся суть работы приложения заключается в изменение таблиц БД V>>, то это просто накопление данных и тогда действительно модель предметной области не нужна. если бизнес-логика есть, я оформляю её в виде модели предметной области.
AVK>К чему эта абстракция? Вопрос был вполне конкретным — зачем что то читать, если задача стоит записать?
Максимально конкретно — пришёл запрос от пользователя что-то записать в бд. Надо проверить права пользователя, вдруг у него прав нет что-то менять. Права пользователя прописаны в той же бд. Будем слепо выполнять запрос пользователя или проверим его права сначала? Проверим, то есть загрузим профиль пользователя из бд, совершим операцию чтения, и уже решим выполнять запись или нет. Окей, у нас профиль пользователя в памяти висит, не надо ничего читать из бд. Выполняем проверку на права и решаем выполнять запись или нет.
Я всегда предполагаю, что в памяти у нас ничего нет, состояние программы перед выполнением операции надо полностью реконструировать. В самом первом посте этой бесполезной ветки я ответил на вопрос как я это делаю. Я отвечаю на вопрос откуда берётся профиль пользователя в памяти, как это оформляется в коде. Как часто программист будет читать профиль пользователя из бд — решает он сам, руководствуясь здравым смыслом.
Вот зачем что-то читать, перед тем как записать. Я ответил как создаются корневые объекты модели предметной области. Простые конструкторы, фабрики и репозитории. Ещё раз, создаются они обычно если их до этого в памяти не было. Как? Конструкторы, фабрики, репозитории. Когда — решает программист.
AVK>>>Считывать данные из БД чтобы их поменять? Нет, это совсем не логично и не просто. V>>Пробуждаем модель предметной области
AVK>Ты перешел к описанию решения. А я спрашивал про задачу.
AVK>>>Подожди. Вот есть у тебя предикат, по которому, к примеру, надо изменить определенное поле. Т.е. нужны, в итоге, примерно такие запросы: AVK>>>
AVK>>>UPDATE Log SET DeletedOn = GETDATE() WHERE CreatedOn < DATEADD(-30, D, GETDATE())
AVK>>>...
AVK>>>UPDATE Tasks SET CaptureCount = CaptureCount + 3 WHERE Type = 4
AVK>>>
AVK>>>Как это выразить в твоей модели? V>>Что мы здесь моделируем, какая бизнес-задача?
AVK>Да вроде все тривиально. В первом случае пометить записи в логе старше 30 дней удаленными. Во втором — увеличить количество захватов задач на единицу для типа 4. Никакой специфики БД, все в терминах предметной области. AVK>Так будет решение, или опять витание в абстракциях без ответа на конкретный вопрос?
V>> Ты рассуждаешь об изменении бд — ну так и отобрази это в коде — выполни нужные запросы к серверу бд и дело с концом.
AVK>Я спрашиваю как предполагается это все делать у тебя.
Создаём объект лога через фабрику или репозиторий. Вызываем у него метод Purge, который выполнит этот запрос UPDATE. Лог обновляет сам себя.
Модель предметной области должна отвечать на вопрос — зачем вызывать метод Purge. Тривиальны конечные операции. Условия по которым они должны быть выполнены тут никакие не указаны. Что тут моделировать? Помечаем старые записи в логе — зачем и почему? Модель предметной области отвечает на этот вопрос. Наступило какое-то событие, время подошло — вот уже можно что-то моделировать.
Чёрный ящик выполнил запрос UPDATE Log... Что должно быть в чёрном ящике?
С Tasks то же самое.
AVK>>>Списываем баланс с одного счета, начисляем на другой. Последовательность действий: считать операции по исходному счету, просуммировать, получить баланс, вычислить корректирующую сумму, записать сумму с минусом на старый счет, с плюсом на новый. Все, разумеется, в одной транзакции. Вычисление баланса, разумеется, нужно более чем в одном месте. AVK>>>Достаточно конкретный пример или еще что то уточнить нужно? V>>Уточнять можно бесконечно.
AVK>Но ответа все равно не будет?
Я надеюсь кто-нибудь другой найдёт в моей писанине рациональное зерно.
V>>>>Стереотипный код писать я тоже не люблю. AVK>>>Зачем тогда советуешь? Особенно это касается рассказов про CQRS, чтоб Фаулеру икалось. V>>Советуй другое.
AVK>Т.е. ответа опять нет.
Тебе не нужны мои ответы. Ты прочитал мой первый пост в ветке, решил что это туфта — и написал свой пост. Но начал мутить воду наводящими вопросами. Лучше сразу писать что это всё туфта и по пунктам. Вдруг действительно туфта. Или показываешь какой-нибудь проект, свой или чужой, где репозитории и прочее приготовлены правильно. Это будет полезно хотя бы.
Здравствуйте, Vladek, Вы писали:
AVK>>Зачем для отрабатывания бизнес-логики читать ненужные данные? V>Затем, что сколь-нибудь нетривиальная программа имеет своё внутреннее состояние, накопленное за время жизни программы или загруженное откуда-нибудь
Спорно. Особенно весело с состоянием становится в кластерах. Прошли те времена, когда серверный софт работал на standalone серверах. Сейчас минимум пара машин просто для обеспечения минимальной надежности.
AVK>>К чему эта абстракция? Вопрос был вполне конкретным — зачем что то читать, если задача стоит записать? V>Максимально конкретно — пришёл запрос от пользователя что-то записать в бд. Надо проверить права пользователя, вдруг у него прав нет что-то менять. Права пользователя прописаны в той же бд. Будем слепо выполнять запрос пользователя или проверим его права сначала? Проверим, то есть загрузим профиль пользователя из бд, совершим операцию чтения, и уже решим выполнять запись или нет. Окей, у нас профиль пользователя в памяти висит, не надо ничего читать из бд. Выполняем проверку на права и решаем выполнять запись или нет.
При чем тут права? Ты говорил о том, что надо читать тот объект, который ты менять собрался.
V>>> Ты рассуждаешь об изменении бд — ну так и отобрази это в коде — выполни нужные запросы к серверу бд и дело с концом. AVK>>Я спрашиваю как предполагается это все делать у тебя. V>Создаём объект лога через фабрику или репозиторий.
Непонятно. Что за объект лога? Уже появились какие то объекты, привязанные не к записи, а к таблице целиком?
V>С Tasks то же самое.
Покажи псевдокод. Словами непонятно.
V>Я надеюсь кто-нибудь другой найдёт в моей писанине рациональное зерно.
Кто ж его найдет, если ты сам не можешь ответить на простые и прямо поставленные вопросы?
AVK>>Т.е. ответа опять нет. V>Тебе не нужны мои ответы.
Решил перейти на личности? Кончились аргументы? Не во мне причина того, что ты не можешь на вопросы ответить.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AndrewVK, Вы писали:
AVK>Облачныые БД все кластерные, бери да смотри. С>> И как это вообще делается? AVK>Что именно?
Мне непонятно, как БД, спроектированную для работы на одном сервере БД, растягивают на несколько серверов?
Насколько мне известно, существует ограниченное количество способов распределения нагрузки по БД:
1) Шардинг, партиционирование в общем виде — из некоего ключа можно вывести номер узла с БД, к которому следует обратиться с запросом. Как только запрос выходит за рамки одной таблицы, задача резко усложняется. Этот пункт применим ко всем нижележащим.
2) В мастер читают и пишут, всё это летит на реплику, если мастер помер — запросы переключаются на реплику. Не увеличивает скорость, повышает надёжность.
3) В мастер пишут, читают из одной или нескольких реплик. Повышает и скорость — частично, и надёжность.
4) Oracle RAC, работает непонятно как, стоит как самолёт.
5) Облако. Внезапно, в облаке обычные базы (не Oracle RAC) приобретают волшебную способность распределять нагрузку по узлам. Как они это делают, непонятно. Возможно и не делают, я не видел. Магия какая-то. Почему это стоит дешевле Oracle RAC, и нет ли тут каких недоговорённостей (а.к.а. хайпово-маркетинговая магия), тоже непонятно.
Здравствуйте, Слава, Вы писали:
С>1) Шардинг, партиционирование в общем виде — из некоего ключа можно вывести номер узла с БД, к которому следует обратиться с запросом. Как только запрос выходит за рамки одной таблицы, задача резко усложняется. Этот пункт применим ко всем нижележащим. С>2) В мастер читают и пишут, всё это летит на реплику, если мастер помер — запросы переключаются на реплику. Не увеличивает скорость, повышает надёжность. С>3) В мастер пишут, читают из одной или нескольких реплик. Повышает и скорость — частично, и надёжность. С>4) Oracle RAC, работает непонятно как, стоит как самолёт. С>5) Облако. Внезапно, в облаке обычные базы (не Oracle RAC) приобретают волшебную способность распределять нагрузку по узлам. Как они это делают, непонятно. Возможно и не делают, я не видел. Магия какая-то. Почему это стоит дешевле Oracle RAC, и нет ли тут каких недоговорённостей (а.к.а. хайпово-маркетинговая магия), тоже непонятно.
Ну вот ты сам на все и ответил. Пачка NoSQL БД, ажуровский сторадж и сиквер при явном задании используют шардинг. Сиквел, кроме того, может и геореплики делать автоматично, и вручную настроенную синхронизацию.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
AVK>Зачем для отрабатывания бизнес-логики читать ненужные данные?
это могут быть примерно такие случаи: мы пишем логику по обновлению чего-то.
но перед сохранением изменений, должны отработать разные правила проверок. эти правила проверок и могут использовать другие данные (поля)
из обновляемой записи и даже из других записей. причём когда мы пишем нашу логику по обновлению, нам конечно не хочется думать ещё и
обо всей логике этих проверок и тем более о их реализации. и в общем случае (тк мы не знаем какие там правила и какие им данные из записи понадобятся)
делается что в объект представляющий запись поднимаются все поля записи.
если конечно, когда мы пишем эту логику по обновлению мы точно знаем что проверять дополнительно ничего не придётся
и важно выжать максимум быстродействия — то вариант с прямым update в базу (ну или аналогом через ORM) оптимален.
но если мы заранее точно не знаем, или проверки могут появиться потом — то для общего случая приходиться так делать, в ущерб
производительности, но зато с упрощение кода (за счет отсутсвия заморачивания о ручном выполнении проверок — и фактически дублирования их логики).
под "так делать" имею ввиду наиболее часто применяемый способ (на своей практике) — работать через Entity и уже в самом классе Entity и реализованы эти правила проверки перед сохранением изменений в БД, а также и может быть бизнес-логика при присвоении в поля.
Здравствуйте, MadHuman, Вы писали:
MH>делается что в объект представляющий запись поднимаются все поля записи. MH>если конечно, когда мы пишем эту логику по обновлению мы точно знаем что проверять дополнительно ничего не придётся
То есть, требуется что-то вроде row level security, а то и какого-то механизма правил, но эти правила не представлены в БД, а прикрутили их потом, в коде, и объем их только растёт. И кто-то это должен поддерживать, а то и развивать.
Лучше, конечно, вообще не работать с подобными проектами и в подобных конторах. Пусть оно умрёт.
Здравствуйте, Слава, Вы писали:
С>Здравствуйте, MadHuman, Вы писали:
MH>>делается что в объект представляющий запись поднимаются все поля записи. MH>>если конечно, когда мы пишем эту логику по обновлению мы точно знаем что проверять дополнительно ничего не придётся
С>То есть, требуется что-то вроде row level security, а то и какого-то механизма правил, но эти правила не представлены в БД, а прикрутили их потом, в коде, и объем их только растёт. И кто-то это должен поддерживать, а то и развивать.
да.
код проверок — на языке программирования, со всеми вытекающими плюсами.
минус только в сценариях массового обновления, когда реально надо много данных заапдейтить. но к счастью обычно (на моей практике) кол-во таких сценариев относительно немного. но когда прижмет, что делать ведь ясно.
а вы предлагаете всё в базе на триггерах реализовать?
Здравствуйте, MadHuman, Вы писали:
MH>код проверок — на языке программирования, со всеми вытекающими плюсами.
А если проверка затрагивает большой объем данных? Все равно все вычитываем? Или можно уже что то в БД оставить, и вытаскивать запросом только результат?
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, MadHuman, Вы писали:
AVK>>Зачем для отрабатывания бизнес-логики читать ненужные данные? MH>это могут быть примерно такие случаи: мы пишем логику по обновлению чего-то. MH>но перед сохранением изменений, должны отработать разные правила проверок. эти правила проверок и могут использовать другие данные (поля) MH>из обновляемой записи и даже из других записей. причём когда мы пишем нашу логику по обновлению, нам конечно не хочется думать ещё и MH>обо всей логике этих проверок и тем более о их реализации. и в общем случае (тк мы не знаем какие там правила и какие им данные из записи понадобятся) MH>делается что в объект представляющий запись поднимаются все поля записи.
Давай без абстракций. Вот свежий пример из одного из реальных продуктов (ненужные подробности убраны):
var packageTemplate =
await db
.PackageTemplates
.Where(pt =>
pt.Type == PackageType.Subsription
&& pt.DeletedDate == null
&& pt.Tag == activationInfo.Edition)
.Select(pt => new { pt.Id, pt.PagesCount })
.SingleOrDefaultAsync();
HttpServiceCode.AssertState(
packageTemplate != null,
"PackageTemplateNotFound",
false,
$"Internal error: package template for {activationInfo.Edition} subscription not found.");
var actQuery =
db
.Subscriptions
.Where(s => s.SerialNumber == activationInfo.SerialNumber);
if (activationInfo.Limit > 1)
HttpServiceCode.AssertArgument(
!await actQuery.AnyAsync(s => s.UserId == userId),
nameof(activationInfo.SerialNumber),
"SerialNumberAlreadyUsedForUser",
true);
HttpServiceCode.AssertArgument(
await actQuery.CountAsync() < activationInfo.Limit,
nameof(activationInfo.SerialNumber),
activationInfo.Limit == 1
? "SerialNumberAlreadyUsed"
: "SerialNumberActivationsExceeded",
true);
db
.BalanceLogs
.Insert(() =>
new BalanceLog
{
Id = Guid.NewGuid(),
UserId = userId,
ClientId = clientId,
ObjectType = ObjectType.PackageTemplate,
ObjectId = packageTemplate.Id,
OperationType = OperationType.SubscriptionActivate,
PagesCount = packageTemplate.PagesCount.GetValueOrDefault(),
EndDate = activationInfo.ExpirationDate,
CreatedDate = DateTime.UtcNow
});
var subsId = Guid.NewGuid();
db
.Subscriptions
.Insert(() =>
new FRSubscription
{
Id = subsId,
CreatedOn = DateTime.UtcNow,
SerialNumber = activationInfo.SerialNumber,
UserId = userId
});
Вот здесь имеют место быть три проверки, потом две записи. Но нигде ничего не вычитывается из БД сверх скалярных данных в минимально требуемом объеме. Как это будет выглядеть в пропагандируемом тут подходе? Можно псевдокод.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
AVK>Давай без абстракций. Вот свежий пример из одного из реальных продуктов (ненужные подробности убраны): AVK> ..... skip ..... AVK>Вот здесь имеют место быть три проверки, потом две записи. Но нигде ничего не вычитывается из БД сверх скалярных данных в минимально требуемом объеме. Как это будет выглядеть в пропагандируемом тут подходе? Можно псевдокод.
ну во первых, я не пропагандирую какой-то конкретный подход как единственный тру, я за справедливость)
в твоем примере я невижу необходимости что-то менять. если проверки именно часть бизнес-процедуры, то таким образом ок.
но может быть другой пример. вот один из сценариев — выгружаются данные из таблицы в ексель (нужные колонки).
затем юзеры что-то в них правят и затем загружается обратно.
//процедура импорта
//пропущены всякие подготовительные действияvar updatedFields = ... //массив имен обновляемых полейforeach(var r in importedRows){
var record=db.Get(r["id"]);
foreach(var fname in updatedFields){
record[fname] = r[fname];
}
}
db.SaveChanges(); //тут и сработают предусмотренные для изменённых записей проверки
//конец процедуры
//проверки в классе Order, вызываются перед сохранением изменений (в недрах SaveChanges).
//Order может меняться: при сохранении изменений из ЮИ, при сохранении изменений от апи, при импорте изменений из файла, вариантов ЮИ-форм может быть много.
//и во всех этих случаях перед тем как сохранить надо проверить валидность нового состояния сущности.public override void Validate(){
//если заказ подтверждён - то должны быть заполнены поля номер, дата и приатачены все необходимые документыif (Status == OrderStatus.Accepted){
if (Number == null)
//для упрощения кода в примере - генерится исключение. в реальности возвращается ValidationResult с подробностями что не так. throw new UserError("Не заполнен номер");
if (Date == null)
throw new UserError("Не заполнена дата");
//и тд
}
//и тут ещё что-нибудь дохрена и не такого простого как выше, код логики может быть довольно сложный.
}
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, MadHuman, Вы писали:
MH>>код проверок — на языке программирования, со всеми вытекающими плюсами.
AVK>А если проверка затрагивает большой объем данных? Все равно все вычитываем? Или можно уже что то в БД оставить, и вытаскивать запросом только результат?
вопрос риторический? конечно в таком случае лучше запросом.
Здравствуйте, Shmj, Вы писали:
S>Приглашаю высказать свое фи: https://habrahabr.ru/post/335856/
Приставка "анти" не перед тем словом стоит, должна быть перед EF =)