Re[19]: Блокировки в бизнес-слое
От: Sinclair Россия https://github.com/evilguest/
Дата: 05.10.17 03:19
Оценка:
Здравствуйте, Poul_Ko, Вы писали:

S>>Хотелось бы убедительный пример.

P_K>100%-но убедительный пример привести не смогу, так как конкретно это технологией не пользовался и реального опыта её применения не имею.
Не-не-не. Вы мне не linq пример приведите, в пример логики, которая "плохо соответствует таблицам".
P_K>Но могу описать на основании каких представлений у меня сложилось озвученное мнение.
P_K>Во-первых, то что бизнес-сущности не всегда один-к-одному соответствуют таблицам я думаю вы согласитесь. В простейших случаях это следствие преднамеренной денормализации (храним список Id строкой, не охота городить таблицу; слабоструктурированные динамические данные — храним JSON в строке; ...).
Откуда в бизнес-логике взялся JSON? Это же всего лишь представление — оно появляется только при взаимодействии с другими системами.
P_K>Во-вторых, поведение часто бывает динамическим. Давайте разовьём пример с заказами. Пусть всего в системе у заказа может быть пять способов его оплаты. Кроме того, заказ может входить в некие программы поставок двух типов (а может и не входить). Грубо говоря, это нам даёт максимум 3х5=15 вариаций поведения на каждый аспект заказа. Окей, происходит бизнес-операция — изменение заказа, включающее в себя изменение способа оплаты и перенос в программу поставок. Рассмотрим один из аспектов — стоимость заказа. Стоимость определяется ценой (которая зависит от способа оплаты) и скидкой (которая зависит от программы поставок). В итоговом запросе мы должны получить что-то вроде
P_K>
P_K>UPDATE order SET
P_K>  ...
P_K>  Cost = x.Price * quantity * y.discount,
P_K>  ...
P_K>FROM Orders
P_K>    INNER JOIN PriceListFromPaymentMethod x ON ....
P_K>    INNER JOIN SupplyPrograms y ON ...
P_K>    ...
P_K>

P_K>Это и есть одна из 15 вариаций, и только для свойства Cost.


P_K>Когда всё на сущностях, то всё просто. Способ определения цены — это абстракция (некая стратегия), способ определения скидки — тоже. Имплементации смотрят на сущность заказа и вычисляют значение по соответствующему алгоритму. Первая стратегия выдаёт "используйте цену 1000", вторая — "используйте скидку 10%". Код, выполняющий операцию, посчитал итоговую стоимость (900), проставил её в сущность. По другим аспектам отработали свои стратегии, заполнились остальные свойства. В итоге свойства сущности были изменены как надо, дёргаем DAL, он всё сохранил, красота.

Давайте напишем для начала всё это на чистом С#.
Вот у нас, допустим, класс Order. У него есть операция CalculateCost(). Как она устроена?
Мы будем делать 15 наследников класса Order c перегрузками?
Или у нас будут свойства PaymentMethod и SupplyProgram, у которых методы GetPrice() и GetDiscount() — виртуальные?
Давайте детализировать.
P_K>Теперь как это провернуть на linq? Вижу два варианта.
P_K>а) Какие-то динамические запросы... Каждая стратегия вместо того чтобы просто "взять и посчитать" будет куда-то добавлять свою часть запроса. В итоге будет построен какой-то огромный запрос, которые таки да, одной операцией всё пересчитает и обновит. Но зачем эта промежуточная модель? Попробуйте её отладить...
Ничего сложного.
Предположим, к примеру, что PaymentMethod — это один из пяти well-known типов, т.е. покрыт перечислением (и добавлять новый метод без перекомпиляции мы не планируем).
У нас есть где-то на более-менее корневом уровне сервис получения прайс-листа: GetPriceList(PaymentMethod paymentMethod).
В Linq-мире он возвращает IQueryable<PriceListItem>. Внутри он может быть устроен более-менее как угодно:
{
  switch(paymentMethod)
  {
    case PaymentMethod.Cash: 
      return from db.CashPrices select new PriceListItem(itemId, price);
    case PaymentMethod.Visa: 
      return from db.CCPrices select new PriceListItem(itemId, visaPrice);
    case PaymentMethod.MasterCard:
      return from db.CCPrices select new PriceListItem(itemId, masterCardPrice);
  }
}

То есть у нас тут и разные таблицы, и разные колонки в одной таблице.
В итоге, метод заказа устроен как-то примерно так:
var totalCost = (from pl in GetPriceList(PaymentMethod) join i in Items on pl.itemId equals i.itemId select i.quantity * pl.price).Sum();

И его вычисление вместо нудных N+1 запросов превратится в нормальный join c агрегатом на стороне СУБД. Писать и отлаживать это ещё проще, чем пошаговую логику в традиционном ERP-приложении с Rich ORM и Lazy Load.
При этом производительность будет как минимум на порядок выше.

P_K>б) Можно апдейтить свойства по одному — одним запросом обновили цену заказа, другим — другое свойство. Будет ли это просто, понятно и эффективно? Тоже сомневаюсь. Какие там ещё проблемы всплывут в конкурентной среде? Теоретически ведь можем в разных запросах использовать одни и те же данные — значит уже нужен repeatable read...
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.