DDD: фильтр по временной таблице в Репозиториях
От: zelenprog  
Дата: 10.06.24 08:06
Оценка:
Здравствуйте!

Делаю программу, стараюсь применять DDD и "правильную" архитектуру.

Столкнулся с такой задачей.
В слое "Бизнес-логики" есть сущности: товары (TovarEntity), аналоги (AnalogEntity), упаковка товара (PackEntity).
Сущности AnalogEntity и PackEntity — это как бы "подчиненные" сущности, то есть они относятся к конкретному товару.
Для каждой сущности есть "свой" Репозиторий.

Задача следующая.
Пользователь запрашивает коллекцию-список товаров по определенному фильтру.
После получения из TovarRepository этой коллекции, далее нужно получить коллекцию аналогов и коллекцию упаковок этих товаров.
То есть нужно сделать что-то типа следующего:

TovarList = TovarRepository.GetList(FilterCritery);
AnalogList = AnalogRepository.GetListByTovar(TovarList);
PackList = PackRepository.GetListByTovar(TovarList);


Концептуально на этом уровне вроде все просто.
Но как реализовать это — не совсем понятно. Проблема в следующем.
Чтобы получить список аналогов для указанного списка товаров, Репозиторий аналогов должен записать список товаров во временную таблицу БД. А затем выполнить запрос, который фильтрует аналоги по товарам во временной таблице (SELECT * FROM analogs WHERE analog.tovar_id IN (SELECT tovar_id FROM temptable_tovar)).
Чтобы получить список упаковок для указанного списка товаров, Репозиторий упаковок должен сделать то же самое.

Если каждый Репозиторий будет использовать "свою" временную таблицу — это будет неэффективно.
Значит, чтобы дважды не формировать временную таблицу, оба Репозитория (и Репозиторий аналогов и Репозиторий упаковок) должны "знать" про одну и ту же временную таблицу, и понимать что указанный в методе GetListByTovar список товаров уже "лежит" во временной таблице.

Как это сделать не нарушая ответственности слоев?
Конечно можно сделать что-то типа такого:

TovarList = TovarRepository.GetList(FilterCritery);
TovarTempTable = TovarRepository.SaveTempTable(TovarList);
AnalogList = AnalogRepository.GetListByTovar(TovarTempTable);
PackList = PackRepository.GetListByTovar(TovarTempTable);


Однако, здесь получается что бизнес-логика "знает" детали реализации выборки.
В бизнес-логику "внедряется" класс TovarTempTable, который не имеет отношения к бизнес-логике.
Это очень не хорошо.

Как бы сделать так, чтобы работа с временной таблицей "ушла" на более низкий уровень — на уровень Репозиториев?
Re: DDD: фильтр по временной таблице в Репозиториях
От: Sinclair Россия https://github.com/evilguest/
Дата: 11.06.24 04:23
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Концептуально на этом уровне вроде все просто.

Z>Но как реализовать это — не совсем понятно. Проблема в следующем.
Z>Чтобы получить список аналогов для указанного списка товаров, Репозиторий аналогов должен записать список товаров во временную таблицу БД. А затем выполнить запрос, который фильтрует аналоги по товарам во временной таблице (SELECT * FROM analogs WHERE analog.tovar_id IN (SELECT tovar_id FROM temptable_tovar)).
Ошибка уже здесь. Временная таблица — это очень плохое решение:
1. Если несколько пользователей выполняют запросы одновременно, то их результаты перемешаются во временной таблице. Это можно решить, но потребуется набор костылей.
2. Даже если решить предыдущую проблему, производительность связанных запросов будет ограничена. Выполнение поиска аналогов как единого запроса позволяет СУБД построить оптимальный план в зависимости от селективности конкретных поисковых предикатов на конкретных данных. Принудительное сохранение во временную таблицу ограничивает возможный выбор планов, да ещё и создаёт нагрузку на IO там, где без неё можно было обойтись.

Z>Чтобы получить список упаковок для указанного списка товаров, Репозиторий упаковок должен сделать то же самое.


Z>Если каждый Репозиторий будет использовать "свою" временную таблицу — это будет неэффективно.

Z>Значит, чтобы дважды не формировать временную таблицу, оба Репозитория (и Репозиторий аналогов и Репозиторий упаковок) должны "знать" про одну и ту же временную таблицу, и понимать что указанный в методе GetListByTovar список товаров уже "лежит" во временной таблице.

Z>Как это сделать не нарушая ответственности слоев?

Лучше всего — работать с системой, которая позволяет манипулировать с запросами как с запросами.
Например, на linq:
var tovarList = TovarRepository.GetList(filterCritery); // никакого обращения в базу ещё нет, просто построен запрос.
/// запрос устроен так, что при его исполнении в базу поедет что-то вроде select * from tovar where ...
var analogList = AnalogRepository.GetListByTovar(tovarList); // никакого обращения в базу ещё нет, просто построен запрос
/// запрос устроен так, что при его исполнении в базу поедет что-то вроде select * from tovar where SELECT * FROM analogs WHERE analog.tovar_id IN (select * from tovar where ...)


Z>Конечно можно сделать что-то типа такого:


Z>
Z>TovarList = TovarRepository.GetList(FilterCritery);
Z>TovarTempTable = TovarRepository.SaveTempTable(TovarList);
Z>AnalogList = AnalogRepository.GetListByTovar(TovarTempTable);
Z>PackList = PackRepository.GetListByTovar(TovarTempTable);
Z>

Если не получается прикрутить ленивые запросы с возможностями подстановки друг в друга, то можно обойтись простым вариантом, в котором AnalogRepository при построении GetListByTovars(IEnumerable<Tovar>) просто берёт и строит список ID переданных ему товаров: SELECT * FROM analogs WHERE analog.tovar_id IN (4, 8, 15, 16, 23, 42)
Здесь нет никакой временной таблицы и связанных с ней проблем. Производительность всё равно будет хуже, чем в предыдущем варианте, но лучше любых других вариантов.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: DDD: фильтр по временной таблице в Репозиториях
От: Буравчик Россия  
Дата: 11.06.24 05:53
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Чтобы получить список аналогов для указанного списка товаров, Репозиторий аналогов должен записать список товаров во временную таблицу БД.

Z>А затем выполнить запрос, который фильтрует аналоги по товарам во временной таблице (SELECT * FROM analogs WHERE analog.tovar_id IN (SELECT tovar_id FROM temptable_tovar)).

Эта вся логика должна находиться внутри репозитория.

Просто создай метод вида TovarRepository.GetListWithAnalogAndPack. Или параметризируй поиск товаров TovarRepository.GetList(withAnalog=true, withPack=true).
Внутри, на уровне базы, сможешь сделать все, что нужно.

P.S. Скорее всего решение с временной таблицей плохое. Лучше все сделать в одном запрос, БД сама разберется, что надо создавать.
P.P.S. Repository не должен быть синглтоном
Best regards, Буравчик
Re[2]: DDD: фильтр по временной таблице в Репозиториях
От: zelenprog  
Дата: 11.06.24 06:21
Оценка:
S>Ошибка уже здесь. Временная таблица — это очень плохое решение:

Я пока пользуюсь sqlite.
Как мне кажется, там кроме временных таблиц ничего другого нельзя применить.

Кроме того, из БД нужно выбрать и аналоги и упаковки для одного и того же списка товаров.
То есть все равно придется выполнить два запроса. Значит, список товаров должен каким-то образом быть "общим".
Поэтому мне кажется, что все-таки лучше этот список товаров предварительно положить куда-то на sql-сервер, чтобы затем выполнить эти два запроса, использующие этот "общий" список товаров.
Зачем его дважды гонять туда-сюда на сервер?
Куда его положить, кроме как во временную таблицу?

S>1. Если несколько пользователей выполняют запросы одновременно, то их результаты перемешаются во временной таблице. Это можно решить, но потребуется набор костылей.


Наверно это можно решить добавлением нового поля во временную таблицу типа "ID_сессии".

S>... Выполнение поиска аналогов как единого запроса позволяет СУБД построить оптимальный план ...


А как это — "единый запрос"?

S>Лучше всего — работать с системой, которая позволяет манипулировать с запросами как с запросами.

S>Например, на linq:
S>var tovarList = TovarRepository.GetList(filterCritery); // никакого обращения в базу ещё нет, просто построен запрос.
S>/// запрос устроен так, что при его исполнении в базу поедет что-то вроде select * from tovar where ...
S>var analogList = AnalogRepository.GetListByTovar(tovarList); // никакого обращения в базу ещё нет, просто построен запрос
S>/// запрос устроен так, что при его исполнении в базу поедет что-то вроде select * from tovar where SELECT * FROM analogs WHERE analog.tovar_id IN (select * from tovar where ...)

Я читал про linq, но по сути не знаю.
Если при вызове функций репозиториев "GetList" формируются только запросы, то в какой момент они выполняются?
По какой-то отдельной команде?

Так как у меня sqlite, я весь код для работы с БД пишу сам "вручную": и слой Репозиториев и слой DataAccess.
Поэтому можно попробовать сделать так, чтобы "мои" репозитории тоже манипулировали запросами. Главное понять принцип как это все должно работать.

S>Если не получается прикрутить ленивые запросы с возможностями подстановки друг в друга ...


Ленивые запросы — это и есть "отложенное" манипулирование запросами?

S>... то можно обойтись простым вариантом, в котором AnalogRepository при построении GetListByTovars(IEnumerable<Tovar>) просто берёт и строит список ID переданных ему товаров: SELECT * FROM analogs WHERE analog.tovar_id IN (4, 8, 15, 16, 23, 42)

S>Здесь нет никакой временной таблицы и связанных с ней проблем.

А если список товаров очень большой? ID-шники задаются в виде GUID-ов, то есть очень длинные.
Строка запроса по списку 1000 товаров превысит все допустимые размеры.
Отредактировано 11.06.2024 6:22 zelenprog . Предыдущая версия .
Re[2]: DDD: фильтр по временной таблице в Репозиториях
От: zelenprog  
Дата: 11.06.24 06:43
Оценка:
Б>Эта вся логика должна находиться внутри репозитория.

Б>Просто создай метод вида TovarRepository.GetListWithAnalogAndPack. Или параметризируй поиск товаров TovarRepository.GetList(withAnalog=true, withPack=true).

Б>Внутри, на уровне базы, сможешь сделать все, что нужно.

Ты предлагаешь сделать один Репозиторий для всех Сущностей.
Я читал, что это неправильно. Для каждой сущности должен быть свой репозиторий.
У меня есть сущности "Товар", "Аналог", "Упаковка". Соответственно должно быть три Репозитория.

Кроме того, этот подход по меньшей мере необычный.
Например, выполнив метод "TovarRepository.GetListWithAnalogAndPack" я получу три списка-коллекции?
Обычно метод Репозитория возвращает одну коллекцию.

Б>P.S. Скорее всего решение с временной таблицей плохое. Лучше все сделать в одном запрос, БД сама разберется, что надо создавать.


Как минимум у меня должно быть два запроса: нужно получить список аналогов и список упаковок.
Хотя, в принципе я не против сделать это одним запросом.
Только как это "вписать" в Domain-Model?

Б>P.P.S. Repository не должен быть синглтоном


Используется Репозиторий как обычный класс.
Хотя по сути он не имеет состояния.
То есть чисто теоретически, если его использовать не как обычный класс, а как синглтон, то никакой разницы не будет.
Re[3]: DDD: фильтр по временной таблице в Репозиториях
От: Буравчик Россия  
Дата: 11.06.24 07:07
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Ты предлагаешь сделать один Репозиторий для всех Сущностей.

Z>Я читал, что это неправильно. Для каждой сущности должен быть свой репозиторий.
Z>У меня есть сущности "Товар", "Аналог", "Упаковка". Соответственно должно быть три Репозитория.

Почему должны? Где ты такое читал?

Z>Кроме того, этот подход по меньшей мере необычный.

Z>Например, выполнив метод "TovarRepository.GetListWithAnalogAndPack" я получу три списка-коллекции?

Получишь ровно то, что напишешь в реализации метода.

Обычно возвращают то, что нужно домену. Например один список объектов, из которого можно получить и товары, и аналоги и т.п. Ну или три списка, если так нужно

Z>Обычно метод Репозитория возвращает одну коллекцию.


Репозитории существуют ДЛЯ домена. Могут возвращать хоть одну коллекцию, хоть десять. Лишь бы домену было удобно, и домен не знал детали реализации.

Можешь создать отдельный репозиторий — Tovaroiskatel (не надо такой нейминг), который будет возвращать одну коллекцию.

Z>Как минимум у меня должно быть два запроса: нужно получить список аналогов и список упаковок.

Z>Хотя, в принципе я не против сделать это одним запросом.
Z>Только как это "вписать" в Domain-Model?

Я выше уже сказал. Завернуть один-два-десять запросов в БД в один метод репозитория.

Z>Используется Репозиторий как обычный класс.

Z>Хотя по сути он не имеет состояния.
Z>То есть чисто теоретически, если его использовать не как обычный класс, а как синглтон, то никакой разницы не будет.

Состояние он имеет — как минимум, настройки для подключения к БД, ссылки на зависимости.
Плюс может добавиться еще состояние, про которое домену знать не нужно, а в реализации репозитория может быть полезно.
Ну и прочие недостатки синглтона
Best regards, Буравчик
Re: DDD: фильтр по временной таблице в Репозиториях
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 11.06.24 07:15
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Здравствуйте!


Z>Делаю программу, стараюсь применять DDD и "правильную" архитектуру.

Правильная это та где работает быстрее, кода меньше и плотность ошибок ниже.


Z>Столкнулся с такой задачей.

Z>В слое "Бизнес-логики" есть сущности: товары (TovarEntity), аналоги (AnalogEntity), упаковка товара (PackEntity).
Z>Сущности AnalogEntity и PackEntity — это как бы "подчиненные" сущности, то есть они относятся к конкретному товару.
Я правильно понимаю, что все приседания чтобы не писать .Include ? зачем? какие характеристики программы это улучшит?
Re[4]: DDD: фильтр по временной таблице в Репозиториях
От: zelenprog  
Дата: 11.06.24 07:45
Оценка:
Z>>Я читал, что это неправильно. Для каждой сущности должен быть свой репозиторий.
Z>>У меня есть сущности "Товар", "Аналог", "Упаковка". Соответственно должно быть три Репозитория.

Б>Почему должны? Где ты такое читал?


Вот здесь:
https://blog.byndyu.ru/2011/01/domain-driven-design-repository.html

Проблема: Один Repository на всех
Попытка создать один универсальный Repository для всех доменных сущностей всегда оборачивается нарушением принципа единственности ответственности и разрастанием этого Repository. Зачастую такие универсальные решения нарушают принцип открытости/закрытости (пример с Repository).
Решение
Нужно разделить реализацию универсального Repository на более маленькие и специфические.


Хотя дальше вот что написано:

Проблема: Создание Repository для каждого объекта домена
Отдельный объект Repository нужно делать только для корня агрегации, к которому нам нужен непосредственный доступ. Более подробно в статье Domain-Driven Design: aggregation root.


Но ведь у меня "Аналог" и "Упаковка" — это же корни агрегации.
Значит для них нужны отдельные репозитории.
Re[4]: DDD: фильтр по временной таблице в Репозиториях
От: zelenprog  
Дата: 11.06.24 07:51
Оценка:
Z>>... выполнив метод "TovarRepository.GetListWithAnalogAndPack" я получу три списка-коллекции?

Б>Получишь ровно то, что напишешь в реализации метода.

Б>Обычно возвращают то, что нужно домену. Например один список объектов, из которого можно получить и товары, и аналоги и т.п. Ну или три списка, если так нужно
Б>Репозитории существуют ДЛЯ домена. Могут возвращать хоть одну коллекцию, хоть десять. Лишь бы домену было удобно, и домен не знал детали реализации.

В общем согласен.

Z>>Используется Репозиторий как обычный класс.

Z>>Хотя по сути он не имеет состояния.

Б>Состояние он имеет — как минимум, настройки для подключения к БД, ссылки на зависимости.


Настройки для подключения к БД один раз задаются и не меняются в течение работы приложения. То есть настройки можно сохранить в синглтоне.
А какие зависимости ты имеешь ввиду? Приведи пример.

Б>Плюс может добавиться еще состояние, про которое домену знать не нужно, а в реализации репозитория может быть полезно.


Тоже можно пример? То есть в приложении могут существовать два Репозитория одного типа, но с разными "внутренними" данными?
Что еще может понадобиться хранить в репозитории?

Б>Ну и прочие недостатки синглтона


Я читал про недостатки.
Как я понял, главный недостаток: если неаккуратно пользоваться, то синглтон "испортит" архитектуру приложения.
Верно?
Re[2]: DDD: фильтр по временной таблице в Репозиториях
От: zelenprog  
Дата: 11.06.24 08:00
Оценка:
Z>>Делаю программу, стараюсь применять DDD и "правильную" архитектуру.
G>Правильная это та где работает быстрее, кода меньше и плотность ошибок ниже.

Не согласен.
Насчет "кода меньше" — это вообще в корне неверно. Объем кода ничего не говорит о качестве программы.

Насчет "плотность ошибок ниже" — с этим согласен — это я бы поставил на первое место, именно в этом и есть цель архитектуры. С кривой архитектурой ошибок будет больше.
Только у тебя причинно-следственная связь перепутана.
Продумав и применив правильную архитектуру — как следствие получаем меньше ошибок.
Если мы каким-то загадочным образом написали код без ошибок — это не значит, что он построен по правильной архитектуре.

Насчет "работает быстрее" — это на следующем месте по важности.
Но если нарушение архитектуры и усложнение кода приводит к незначительному увеличению производительности, то лучше от такого кода отказаться.

G>Я правильно понимаю, что все приседания чтобы не писать .Include ? зачем? какие характеристики программы это улучшит?


У меня нету .Include.
Есть "голые" sql-запросы, которые я сам пишу в методах класса Репозитория.
Re[5]: DDD: фильтр по временной таблице в Репозиториях
От: dmitry_npi Россия  
Дата: 11.06.24 10:37
Оценка: +1
Здравствуйте, zelenprog, Вы писали:

Насчёт DDD.

Z>Проблема: Один Repository на всех

Z>Попытка создать один универсальный Repository для всех доменных сущностей всегда оборачивается нарушением принципа единственности ответственности и разрастанием этого Repository. Зачастую такие универсальные решения нарушают принцип открытости/закрытости (пример с Repository).
Нет, здесь под понятием "универсальный" понимается не репозиторий с методами для всех сущностей, а решение типа Repository<T> — один параметризованный класс для всех типов сущностей.

Z>Но ведь у меня "Аналог" и "Упаковка" — это же корни агрегации.

Z>Значит для них нужны отдельные репозитории.

Наоборот, корень агрегации — это Tovar, а Аналоги и Упаковки — подчиненные ему сущности. Поэтому по канонам DDD здесь нужно создавать один репозиторий — TovarRepository с методом типа GetTovarWithAnalogAndPack().

Но еще следует помнить про bounded context из DDD. В вашем сценарии, действительно, Tovar — корень агрегации. Но в других сценариях, например, вы пишете админку с редактирование справочников, Аналоги и Упаковки тоже могут быть корнями.
И что же, вы спросите, теперь плодить отдельные почти одинаковые сущности под каждый сценарий? Насколько я понимаю DDD, да, придется. Хоть это и усложнит систему в целом, но зато упростит каждый отдельный контекст. Но ведь DDD подход и предназначен для сложных систем, в простых он только даёт ненужный оверхед. ИМХО.

Насчет временной таблицы уже написали выше. Могу только добавить что в нормальных СУБД временные таблицы имею видимость, ограниченную чем-то: соединением или пользователем. Так что при правильном подходе можно, наверное, избежать конфликта данных во временной таблице при одновременных запросах. Есть и другие подходы, см. тут

Зы: а еще лучше вместо паттерна "репозиторий" использовать что-то типа Query Object или Command. Каждый запрос в отдельном классе. В конструктор инжектим только необходимое.
Атмосферная музыка — www.aventuel.net
Отредактировано 22.06.2024 6:12 dmitry_npi . Предыдущая версия .
Re[3]: DDD: фильтр по временной таблице в Репозиториях
От: Sinclair Россия https://github.com/evilguest/
Дата: 11.06.24 10:55
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Я пока пользуюсь sqlite.

Z>Как мне кажется, там кроме временных таблиц ничего другого нельзя применить.
Простите, но с чего вы это взяли?

Z>Кроме того, из БД нужно выбрать и аналоги и упаковки для одного и того же списка товаров.

Z>То есть все равно придется выполнить два запроса. Значит, список товаров должен каким-то образом быть "общим".
Ну вот он так и делается "общим": в него встраивается один и тот же запрос, получающий ID товаров.
Z>Поэтому мне кажется, что все-таки лучше этот список товаров предварительно положить куда-то на sql-сервер, чтобы затем выполнить эти два запроса, использующие этот "общий" список товаров.
Z>Зачем его дважды гонять туда-сюда на сервер?
У вас же sqlite, никакого "гоняния" нету.
Z>Куда его положить, кроме как во временную таблицу?
В сам запрос.

Z>Наверно это можно решить добавлением нового поля во временную таблицу типа "ID_сессии".

Я же говорю — можно обложить костылями. Когда вы будете чистить эти временные таблицы?

Z>А как это — "единый запрос"?

SELECT * FROM analogs WHERE analog.tovar_id IN (select * from tovar where ...)



Z>Я читал про linq, но по сути не знаю.

Z>Если при вызове функций репозиториев "GetList" формируются только запросы, то в какой момент они выполняются?
Z>По какой-то отдельной команде?
В тот момент, когда запрос начинают "перебирать" как IEnumerable.

Z>Так как у меня sqlite,

Дело не в sqlite. Linq2db прекрасно и с sqlite работает.
Просто построить нормальный аналог linq на платформе, которая его не поддерживает — малореальное удовольствие.

Z>Ленивые запросы — это и есть "отложенное" манипулирование запросами?

Да.

S>>... то можно обойтись простым вариантом, в котором AnalogRepository при построении GetListByTovars(IEnumerable<Tovar>) просто берёт и строит список ID переданных ему товаров: SELECT * FROM analogs WHERE analog.tovar_id IN (4, 8, 15, 16, 23, 42)

S>>Здесь нет никакой временной таблицы и связанных с ней проблем.

Z>А если список товаров очень большой? ID-шники задаются в виде GUID-ов, то есть очень длинные.

Что такое "очень большой"?
Z>Строка запроса по списку 1000 товаров превысит все допустимые размеры.
У sqlite "допустимые размеры" — 1 000 000 0000 символов. Подозреваю, что запрос на 1000 товаров их не превысит.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: DDD: фильтр по временной таблице в Репозиториях
От: · Великобритания  
Дата: 11.06.24 10:59
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Например, на linq:

S>var tovarList = TovarRepository.GetList(filterCritery); // никакого обращения в базу ещё нет, просто построен запрос.
Как я понял, ему нужен и сам список товаров тоже.

S>var analogList = AnalogRepository.GetListByTovar(tovarList); // никакого обращения в базу ещё нет, просто построен запрос

S>/// запрос устроен так, что при его исполнении в базу поедет что-то вроде select * from tovar where SELECT * FROM analogs WHERE analog.tovar_id IN (select * from tovar where ...)
Ему ещё нужно списки аналогов и упаковок, именно согласованные с этим данным списком товаров, т.е. ещё и целостность надо будет как-то обеспечивать.

S>[tt]SELECT * FROM analogs WHERE analog.tovar_id IN (4, 8, 15, 16, 23, 42)

А как оно сработает, скажем, для 100к товаров? Какая будет у этого произовительность гонять туда-сюда?

Так что я голосую за GetTovarWithAnalogAndPack, который пусть сам обеспечивает согласованность и перформанс как может — через временные таблицы или ещё как.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 11.06.2024 11:00 · . Предыдущая версия .
Re[4]: DDD: фильтр по временной таблице в Репозиториях
От: · Великобритания  
Дата: 11.06.24 11:09
Оценка: 14 (1)
Здравствуйте, Sinclair, Вы писали:


Z>>Строка запроса по списку 1000 товаров превысит все допустимые размеры.

S>У sqlite "допустимые размеры" — 1 000 000 0000 символов. Подозреваю, что запрос на 1000 товаров их не превысит.

To prevent excessive memory allocations, the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER, which defaults to 999 for SQLite versions prior to 3.32.0 (2020-05-22) or 32766 for SQLite versions after 3.32.0.

Т.е. может быть придётся генерировать строку вида "...in ('id1', 'id2', ..., 'id1000')" вместо использования ?-параметров и байдинга.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: DDD: фильтр по временной таблице в Репозиториях
От: Qulac Россия  
Дата: 11.06.24 16:55
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Здравствуйте!


Z>Делаю программу, стараюсь применять DDD и "правильную" архитектуру.


Z>Столкнулся с такой задачей.

Z>В слое "Бизнес-логики" есть сущности: товары (TovarEntity), аналоги (AnalogEntity), упаковка товара (PackEntity).
Z>Сущности AnalogEntity и PackEntity — это как бы "подчиненные" сущности, то есть они относятся к конкретному товару.
Z>Для каждой сущности есть "свой" Репозиторий.

Z>Задача следующая.

Z>Пользователь запрашивает коллекцию-список товаров по определенному фильтру.
Z>После получения из TovarRepository этой коллекции, далее нужно получить коллекцию аналогов и коллекцию упаковок этих товаров.
Z>То есть нужно сделать что-то типа следующего:

Z>
Z>TovarList = TovarRepository.GetList(FilterCritery);
Z>AnalogList = AnalogRepository.GetListByTovar(TovarList);
Z>PackList = PackRepository.GetListByTovar(TovarList);
Z>


Z>Концептуально на этом уровне вроде все просто.

Z>Но как реализовать это — не совсем понятно. Проблема в следующем.
Z>Чтобы получить список аналогов для указанного списка товаров, Репозиторий аналогов должен записать список товаров во временную таблицу БД. А затем выполнить запрос, который фильтрует аналоги по товарам во временной таблице (SELECT * FROM analogs WHERE analog.tovar_id IN (SELECT tovar_id FROM temptable_tovar)).
Z>Чтобы получить список упаковок для указанного списка товаров, Репозиторий упаковок должен сделать то же самое.

Z>Если каждый Репозиторий будет использовать "свою" временную таблицу — это будет неэффективно.

Z>Значит, чтобы дважды не формировать временную таблицу, оба Репозитория (и Репозиторий аналогов и Репозиторий упаковок) должны "знать" про одну и ту же временную таблицу, и понимать что указанный в методе GetListByTovar список товаров уже "лежит" во временной таблице.

Z>Как это сделать не нарушая ответственности слоев?

Z>Конечно можно сделать что-то типа такого:

Z>
Z>TovarList = TovarRepository.GetList(FilterCritery);
Z>TovarTempTable = TovarRepository.SaveTempTable(TovarList);
Z>AnalogList = AnalogRepository.GetListByTovar(TovarTempTable);
Z>PackList = PackRepository.GetListByTovar(TovarTempTable);
Z>


Z>Однако, здесь получается что бизнес-логика "знает" детали реализации выборки.

Z>В бизнес-логику "внедряется" класс TovarTempTable, который не имеет отношения к бизнес-логике.
Z>Это очень не хорошо.

Z>Как бы сделать так, чтобы работа с временной таблицей "ушла" на более низкий уровень — на уровень Репозиториев?


А в чем проблема? Вытаскиваете нужные Вам сущности из репозитория в месте с TovarEntity.
Программа – это мысли спрессованные в код
Re[3]: DDD: фильтр по временной таблице в Репозиториях
От: Sharov Россия  
Дата: 11.06.24 22:04
Оценка:
Здравствуйте, zelenprog, Вы писали:


Z>Кроме того, из БД нужно выбрать и аналоги и упаковки для одного и того же списка товаров.

Z>То есть все равно придется выполнить два запроса. Значит, список товаров должен каким-то образом быть "общим".

Сохраните id товаров в список и передавайте их в другие запросы.

Z>Поэтому мне кажется, что все-таки лучше этот список товаров предварительно положить куда-то на sql-сервер, чтобы затем выполнить эти два запроса, использующие этот "общий" список товаров.


Какой sql-server, если речь об sqlite?

Z>Зачем его дважды гонять туда-сюда на сервер?

Z>Куда его положить, кроме как во временную таблицу?

В список в памяти, какая разница, кто его будет хранить в памяти -- движок sqlite или приложение?
Кстати, разве sqlite умеет во временные таблицы?
Кодом людям нужно помогать!
Re[3]: DDD: фильтр по временной таблице в Репозиториях
От: Sharov Россия  
Дата: 11.06.24 22:06
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Ты предлагаешь сделать один Репозиторий для всех Сущностей.

Z>Я читал, что это неправильно. Для каждой сущности должен быть свой репозиторий.
Z>У меня есть сущности "Товар", "Аналог", "Упаковка". Соответственно должно быть три Репозитория.
Z>Кроме того, этот подход по меньшей мере необычный.
Z>Например, выполнив метод "TovarRepository.GetListWithAnalogAndPack" я получу три списка-коллекции?
Z>Обычно метод Репозитория возвращает одну коллекцию.

В чем проблема в Анало и Упаковку передавать id соотв. товаров?
Кодом людям нужно помогать!
Re[5]: DDD: фильтр по временной таблице в Репозиториях
От: Sharov Россия  
Дата: 11.06.24 22:12
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Но ведь у меня "Аналог" и "Упаковка" — это же корни агрегации.

Z>Значит для них нужны отдельные репозитории.

Кажется, что корень агрегации это как раз товар.
Кодом людям нужно помогать!
Re[3]: DDD: фильтр по временной таблице в Репозиториях
От: Sharov Россия  
Дата: 11.06.24 22:23
Оценка:
Здравствуйте, ·, Вы писали:

S>>[tt]SELECT * FROM analogs WHERE analog.tovar_id IN (4, 8, 15, 16, 23, 42)

·>А как оно сработает, скажем, для 100к товаров? Какая будет у этого произовительность гонять туда-сюда?

Скорее всего индексы на tovar_id будут, соотв. по идее довольно шустро все отработает.
Кодом людям нужно помогать!
Re[3]: DDD: фильтр по временной таблице в Репозиториях
От: Sinclair Россия https://github.com/evilguest/
Дата: 12.06.24 06:42
Оценка:
Здравствуйте, ·, Вы писали:

·>Как я понял, ему нужен и сам список товаров тоже.

Список товаров он получать уже умеет.

·>Ему ещё нужно списки аналогов и упаковок, именно согласованные с этим данным списком товаров, т.е. ещё и целостность надо будет как-то обеспечивать.

К счастью, для этого есть ровно один способ: выполняем всё в 1 транзакции.
Вы имеете в виду то, что в случае с разными репозиториями придётся протаскивать управление транзакциями на уровень BL?
Согласен.

S>>[tt]SELECT * FROM analogs WHERE analog.tovar_id IN (4, 8, 15, 16, 23, 42)

·>А как оно сработает, скажем, для 100к товаров? Какая будет у этого произовительность гонять туда-сюда?
Прекрасно оно сработает. Речь идёт об sqlite, который In-process engine. Так что производительность будет сопоставимой с временной таблицей:
— в обсуждаемом варианте данные едут из клиентского кода в движок в виде строки через память; (трансфер списка товаров из движка в память клиента не считаем, т.к. этот запрос нам нужен в любом варианте)
— в варианте с временной таблицей данные едут между первым запросом и вторым через дисковый кеш.
·>Так что я голосую за GetTovarWithAnalogAndPack, который пусть сам обеспечивает согласованность и перформанс как может — через временные таблицы или ещё как.
Присоединюсь. Бездумное увлечение DDD только портит архитектуру.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.