Переиспользование существующих методов
От: Stalker. Австралия  
Дата: 23.09.17 00:30
Оценка:
У нас в компании разрабатывается софт, который с некоторыми изменениями продается разным клиентам. Т.е. по-сути один и тот-же продукт, но с отличиями для разных клиентов. Довольно стандартная ситуация в общем. Имеется фасад сервисов, который содержит бизнес-логику, код EF и всякий маппинг. Тимлид одержим идеей переиспользования методов для разных клиентов, и его можно понять — множество методов различаются на уровне доп. параметра к запросу, полями, отображаемыми на экране итп. Есть процент совершенно одинаковых методов, но довольно малый — скажем процентов 20%. На данный момент на каждый проект код просто копируется в новый солюшн, где методы изменяются под новые требования. Тимлид очень сильно хочет иметь ОДИН проект для сервисов, который-бы юзался для разных клиентов.
Вопрос — кому-нибудь удавалось найти удачное решение для такой проблемы? Проблема сама в том, что если начинать делать методы более "общими", то это очевидно будет давить на производительность. Причем даже не случае различных параметров, когда новый метод практически неизбежен, но и в случае когда новому клиенты вместо одного поля сущности требуется отобразить другое. Если тянуть из базы ВСЕ поля, а потом преобразовывать их в необходимый срез DTO для конкретного клиента, то нагрузка на базу довольно сильно возрастает
Re: Переиспользование существующих методов
От: scf  
Дата: 23.09.17 06:35
Оценка: 2 (2)
Здравствуйте, Stalker., Вы писали:

S>У нас в компании разрабатывается софт, который с некоторыми изменениями продается разным клиентам. Т.е. по-сути один и тот-же продукт, но с отличиями для разных клиентов. Довольно стандартная ситуация в общем. Имеется фасад сервисов, который содержит бизнес-логику, код EF и всякий маппинг. Тимлид одержим идеей переиспользования методов для разных клиентов, и его можно понять — множество методов различаются на уровне доп. параметра к запросу, полями, отображаемыми на экране итп. Есть процент совершенно одинаковых методов, но довольно малый — скажем процентов 20%. На данный момент на каждый проект код просто копируется в новый солюшн, где методы изменяются под новые требования. Тимлид очень сильно хочет иметь ОДИН проект для сервисов, который-бы юзался для разных клиентов.

S>Вопрос — кому-нибудь удавалось найти удачное решение для такой проблемы? Проблема сама в том, что если начинать делать методы более "общими", то это очевидно будет давить на производительность. Причем даже не случае различных параметров, когда новый метод практически неизбежен, но и в случае когда новому клиенты вместо одного поля сущности требуется отобразить другое. Если тянуть из базы ВСЕ поля, а потом преобразовывать их в необходимый срез DTO для конкретного клиента, то нагрузка на базу довольно сильно возрастает

Был у меня аналогичный опыт. Я считаю, что для подобных систем нужно разделять бэкенд ("движок") и фронтенд. Бэкенд и схема данных должны быть общими для всех, а фронтенды — разными, их переиспользовать нельзя. Причина даже не в быстродействии, а в объеме конфигурации. Чтобы добавлять функционал для конкретного клиента, придется добавлять в код if-ы россыпью в разных местах и завязывать их на некоторые флаги в настройках клиента. Такая система быстро выходит из под контроля.

Я так понял, у вас нет гуя, просто API. Тогда я бы сделал общую схему БД, общий слой DAO для этой БД и индивидуальные реализации API под каждого клиента, опирающиеся на общий слой DAO. Что касается быстродействия, о нем нельзя говорить абстрактно, всегда нужно сначала сделать замеры. Это как я думал, что база — это медленно, а потом оказалось, что постгрес на AWS micro держит 3000 селектов в секунду.
Re[2]: Переиспользование существующих методов
От: Stalker. Австралия  
Дата: 23.09.17 10:05
Оценка:
Здравствуйте, scf, Вы писали:

scf>Я так понял, у вас нет гуя, просто API. Тогда я бы сделал общую схему БД, общий слой DAO для этой БД и индивидуальные реализации API под каждого клиента, опирающиеся на общий слой DAO. Что касается быстродействия, о нем нельзя говорить абстрактно, всегда нужно сначала сделать замеры. Это как я думал, что база — это медленно, а потом оказалось, что постгрес на AWS micro держит 3000 селектов в секунду.


есть и гуи, и API, схема базы данных сейчас одна и есть, на ней сидит ЕF, который получается тоже один, вы это подразуемваете под "общий слой DAO"? Если да, то это уже есть. Чего нет, это дальнейшей унификации методов. Т.е. нужен к примеру метод FindCustomers(). Этот метод находится в фасаде, к которому обращаются клиентские GUI и различные API. Теперь одному клиенту надо всех кастомеров с ФИО и датой рождения, а другому — с адресом. Третий клиент еще захочет добавить к поиску кастомеров доп. фильтр (например по городу) и вывести телефоны кастомеров.
Т.е. варианты по-сути такие:

1) Выводить все возможные поля кастомеров и возвращать их все всем клиентам. Т.е. клиент, которому не нужен адрес все равно его получит, просто должен будет проигнорировать.
2) Создать надстройку над фасадом уникальную для каждого клиента, которая занимается тем, что создает специфичные для клиента DTO из общей массы того, что вернул фасад. Т.е. по-сути создать еще одну фасад-прослойку ответственную только за сборку специфичных DТО.
3) Каждому клиенту создавать свой метод, т.е. по-сути для каждого клиента создается свой фасад (примерно что сейчас и происходит) где вся логика дублируется для всех клиентов.
4) Для случая с под. фильтром попробовать какие-нибудь динамические фильтры/параметры? Возможно-ли такое и имеет-ли смысл?

У методов 1 и 2 получается только 1 общий фасад, что хорошо, но он начнет разрастаться, ведь если какому-то одному клиенту будет нужен совершенно новый метод — он там добавится и может в конце концов получится фасад-монстр
Re[3]: Переиспользование существующих методов
От: Qulac Россия  
Дата: 23.09.17 10:35
Оценка:
Здравствуйте, Stalker., Вы писали:

S>Здравствуйте, scf, Вы писали:


S>есть и гуи, и API, схема базы данных сейчас одна и есть, на ней сидит ЕF, который получается тоже один, вы это подразуемваете под "общий слой DAO"? Если да, то это уже есть. Чего нет, это дальнейшей унификации методов. Т.е. нужен к примеру метод FindCustomers().


А так нельзя сделать:
  public class CustomerService<T> where T:CustomerBase
    {
        public T FindCustomers()
        {
            throw new NotImplementedException();
        }
    }

    public class CustomerBase { }


Вообще стандартный рефакторинг, выделяйте общее для всех клиентов, а там уже наследование, композиция и т.д.
Программа – это мысли спрессованные в код
Re[3]: Переиспользование существующих методов
От: Слава  
Дата: 23.09.17 12:11
Оценка:
Здравствуйте, Stalker., Вы писали:

S>1) Выводить все возможные поля кастомеров и возвращать их все всем клиентам. Т.е. клиент, которому не нужен адрес все равно его получит, просто должен будет проигнорировать.


Вам OData или GraphQL еще не советовали? Жаль, а у меня еще столько замечательных идей было! ((с) анекдот про Горбачева)
филин-стратег
Re: Переиспользование существующих методов
От: vsb Казахстан  
Дата: 23.09.17 13:49
Оценка:
Вам нужен фреймворк (то бишь из вашего решения сделать ядро-фреймворк и с его помощью писать конкретные кастомизированные решения). Посмотрите на Cuba, возможно вдохновитесь.

S>Вопрос — кому-нибудь удавалось найти удачное решение для такой проблемы? Проблема сама в том, что если начинать делать методы более "общими", то это очевидно будет давить на производительность. Причем даже не случае различных параметров, когда новый метод практически неизбежен, но и в случае когда новому клиенты вместо одного поля сущности требуется отобразить другое. Если тянуть из базы ВСЕ поля, а потом преобразовывать их в необходимый срез DTO для конкретного клиента, то нагрузка на базу довольно сильно возрастает


Не очевидно, что это будет давить на производительность. Клиент должен указывать список полей, которые ему нужны, сервер их будет вытаскивать. Тут самое главное даже не в обычных полях (их тянуть недорого), а в джойнах. В принципе любой ORM позволяет это.
Re[3]: Переиспользование существующих методов
От: scf  
Дата: 23.09.17 14:09
Оценка:
Здравствуйте, Stalker., Вы писали:

S>есть и гуи, и API, схема базы данных сейчас одна и есть, на ней сидит ЕF, который получается тоже один, вы это подразуемваете под "общий слой DAO"? Если да, то это уже есть. Чего нет, это дальнейшей унификации методов. Т.е. нужен к примеру метод FindCustomers(). Этот метод находится в фасаде, к которому обращаются клиентские GUI и различные API. Теперь одному клиенту надо всех кастомеров с ФИО и датой рождения, а другому — с адресом. Третий клиент еще захочет добавить к поиску кастомеров доп. фильтр (например по городу) и вывести телефоны кастомеров.

S>Т.е. варианты по-сути такие:

S>1) Выводить все возможные поля кастомеров и возвращать их все всем клиентам. Т.е. клиент, которому не нужен адрес все равно его получит, просто должен будет проигнорировать.

S>2) Создать надстройку над фасадом уникальную для каждого клиента, которая занимается тем, что создает специфичные для клиента DTO из общей массы того, что вернул фасад. Т.е. по-сути создать еще одну фасад-прослойку ответственную только за сборку специфичных DТО.
S>3) Каждому клиенту создавать свой метод, т.е. по-сути для каждого клиента создается свой фасад (примерно что сейчас и происходит) где вся логика дублируется для всех клиентов.
S>4) Для случая с под. фильтром попробовать какие-нибудь динамические фильтры/параметры? Возможно-ли такое и имеет-ли смысл?

S>У методов 1 и 2 получается только 1 общий фасад, что хорошо, но он начнет разрастаться, ведь если какому-то одному клиенту будет нужен совершенно новый метод — он там добавится и может в конце концов получится фасад-монстр


Я за вариант 3. Основное требование в такой "древовидной" системе — минимизация объема тестирования при внесении изменений. Т.е. архитектура должна быть такая, чтобы не тестировать фасады, поведение которых меняться не должно. Вот будет у вас 100 фасадов — если доработка одного из них может затронуть остальные, то вы погрязнете в тестировании.

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

Я предлагаю перекладывание данных из одной модели в другую вообще не считать дублированием — повторно использовать подобный код чревато хрупкостью системы. Если же есть нечто а) повторно используемое и б) что можно назвать бизнес-логикой, то я бы сделал его отдельным модулем/сервисом. Чтобы фасад обращался и к слою DAO, и к этому сервису. Основное ограничение при проектировании такого сервиса — гарантированная обратная совместимость при внесении изменений.

Если вас волнует быстродействие, этот самый сервис посередине может использовать GraphQL или что-то подобное в качестве протокола: он позволяет указывать конкретные поля. Но я повторюсь — гарантии обратной совместимости должны быть такими, чтобы никому в голову не пришло перетестировать фасады, в которые напрямую изменения не вносились.
Re[4]: Переиспользование существующих методов
От: scf  
Дата: 23.09.17 14:13
Оценка:
Здравствуйте, Qulac, Вы писали:

Q>Вообще стандартный рефакторинг, выделяйте общее для всех клиентов, а там уже наследование, композиция и т.д.


Это проблема архитектурная) Проблема общего кода в том, что при внесении в него изменений может измениться (сломаться) поведение всего функционала, который пользуется этим общим кодом. Для небольших приложений или для монолита это не проблема, но вот в ситуации ТС, когда из общей базы растут десятки приложений, которые все должны работать стабильно при внесении изменений в одно из них... Нужно либо писать тесты со 100% покрытием (долго и не факт что надежно), либо соорудить такую архитектуру, чтобы эта проблема решалась автоматически.
Re[5]: Переиспользование существующих методов
От: Stalker. Австралия  
Дата: 23.09.17 22:41
Оценка: +1
Здравствуйте, scf, Вы писали:

scf>Это проблема архитектурная) Проблема общего кода в том, что при внесении в него изменений может измениться (сломаться) поведение всего функционала, который пользуется этим общим кодом. Для небольших приложений или для монолита это не проблема, но вот в ситуации ТС, когда из общей базы растут десятки приложений, которые все должны работать стабильно при внесении изменений в одно из них... Нужно либо писать тесты со 100% покрытием (долго и не факт что надежно), либо соорудить такую архитектуру, чтобы эта проблема решалась автоматически.



спасибо, идея OData + микросервисы определенно похоже на то, что стоит начать изучать. OData поможет унифицировать методы различающиеся полями и фильтрами, микросервисы смогут поделить "общую" логику для всех и уникальную для каждого клиента, где "общий" микросервис поставляется всем клиентам, а "уникальные" в зависимости от клиента
Re: Переиспользование существующих методов
От: Cyberax Марс  
Дата: 23.09.17 23:38
Оценка:
Здравствуйте, Stalker., Вы писали:

S> Если тянуть из базы ВСЕ поля, а потом преобразовывать их в необходимый срез DTO для конкретного клиента, то нагрузка на базу довольно сильно возрастает

Вот не верю. В большинстве случаев нет разницы грузить все поля или только несколько. В тех редких случаях, когда разница есть — можно сделать отдельный метод для тяжёлых полей (типа: GetPersonalInfo(ids) и GetPersonalPhotos(ids)).

Ну и если сервис возвращает много данных, то надо начинать задумываться о разбиении на страницы, параллельной загрузкой и т.п.

Вообще, мой совет — не надо усложнять. Если лишних методов около 20%, то не надо голову морочить — пусть так и остаются. Ну и по возможности классическими приёмами переиспользовать логику на сервере. Если уходить в дебри OData и всяких языков запросов, то можно там навсегда и остаться.
Sapienti sat!
Re[2]: Переиспользование существующих методов
От: Stalker. Австралия  
Дата: 25.09.17 01:54
Оценка:
Здравствуйте, Cyberax, Вы писали:

C>Вот не верю. В большинстве случаев нет разницы грузить все поля или только несколько. В тех редких случаях, когда разница есть — можно сделать отдельный метод для тяжёлых полей (типа: GetPersonalInfo(ids) и GetPersonalPhotos(ids)).


C>Ну и если сервис возвращает много данных, то надо начинать задумываться о разбиении на страницы, параллельной загрузкой и т.п.


C>Вообще, мой совет — не надо усложнять. Если лишних методов около 20%, то не надо голову морочить — пусть так и остаются. Ну и по возможности классическими приёмами переиспользовать логику на сервере. Если уходить в дебри OData и всяких языков запросов, то можно там навсегда и остаться.


уровень сильно/не сильно конечно можно диспутировать, но то, что нагрузка на БД увеличивается — это однозначно. Во-первых возвращая все поля мы по определению не построим эффективные индексы на базе, любой индекс на каждой таблице будет сопровождаться лукапом всех остальных полей, это недешевая операция. Во-вторых, полей все-же может быть много, у нас по-крайней мере их десятки в каждой таблице. В-третьих, множество атрибутов будет сопровождаться лишними джойнами т.к. они лежат в других таблицах, что тоже дорогая операция.
А также есть еще один момент который я не упомянул — это обновления системы. Каждые полгода-год выходят новые версии продукта. В него добавляются какие-то возможности, большая часть из которых является общими для всех. Сейчас это просто делается таким образом, что любая общая фича перекопируется по всех версиям продукта.
Re[3]: Переиспользование существующих методов
От: Muxa  
Дата: 25.09.17 11:38
Оценка:
S>Т.е. нужен к примеру метод FindCustomers(). Этот метод находится в фасаде
В фасаде не должно быть такого метода, иначе получается что бизнес логика пролазит в абстракции.

Хорошим методом для фасада может быть что-то типа:
facade.setect(table, condition, fields);

Так же необходимы промежуточные кастомные классы, которые результат запросов к фасаду заворачивают в объекты нужных клиенту классов.
// core 
interface ICustomer;
class CustomerServiceBase
{ 
    Facade facade;
    List<ICustomer> selectCustomers(Condition cond);
}

// client
class ClientACustomer : ICustomer;
class ClientACustomerService :  CustomerServiceBase
{
    List<ICustomer> selectCustomers(Condition cond)
    {
        // транслировать запрос фасаду и вернуть список объектов класса ClientACustomer
    }
}
Отредактировано 25.09.2017 12:46 Muxa . Предыдущая версия .
Re[3]: Переиспользование существующих методов
От: Cyberax Марс  
Дата: 25.09.17 22:17
Оценка:
Здравствуйте, Stalker., Вы писали:

C>>Вообще, мой совет — не надо усложнять. Если лишних методов около 20%, то не надо голову морочить — пусть так и остаются. Ну и по возможности классическими приёмами переиспользовать логику на сервере. Если уходить в дебри OData и всяких языков запросов, то можно там навсегда и остаться.

S>уровень сильно/не сильно конечно можно диспутировать, но то, что нагрузка на БД увеличивается — это однозначно.
S>Во-первых возвращая все поля мы по определению не построим эффективные индексы на базе, любой индекс на каждой таблице будет сопровождаться лукапом всех остальных полей, это недешевая операция.
ЧЗХ? Поиск строки в таблице нужен будет в любом случае, если только в индексе не находятся ВСЕ поля, необходимые для выполнения запроса. И поиск по главному ключу — самая эффективная операция в БД.

Если это таки нужно — делаем специальный метод.

S>Во-вторых, полей все-же может быть много, у нас по-крайней мере их десятки в каждой таблице. В-третьих, множество атрибутов будет сопровождаться лишними джойнами т.к. они лежат в других таблицах, что тоже дорогая операция.

Ну пусть десятки, и что? Другие таблицы моделировать отдельными объектами.

S>А также есть еще один момент который я не упомянул — это обновления системы. Каждые полгода-год выходят новые версии продукта. В него добавляются какие-то возможности, большая часть из которых является общими для всех. Сейчас это просто делается таким образом, что любая общая фича перекопируется по всех версиям продукта.

Ну так добавляем новые поля в модель сервиса и вперёд с песней.
Sapienti sat!
Re[4]: Переиспользование существующих методов
От: Stalker. Австралия  
Дата: 25.09.17 23:23
Оценка:
Здравствуйте, Muxa, Вы писали:

M>В фасаде не должно быть такого метода, иначе получается что бизнес логика пролазит в абстракции.


M>Хорошим методом для фасада может быть что-то типа:

M>[cs]
M>facade.setect(table, condition, fields);

это и есть подход OData?
Re[4]: Переиспользование существующих методов
От: Stalker. Австралия  
Дата: 25.09.17 23:29
Оценка:
Здравствуйте, Cyberax, Вы писали:

C>ЧЗХ? Поиск строки в таблице нужен будет в любом случае, если только в индексе не находятся ВСЕ поля, необходимые для выполнения запроса. И поиск по главному ключу — самая эффективная операция в БД.


есть таблица с 30 полями. Есть поиск по 3 полям, на выходе сущность с 6 полями. Делается индекс по 3 полям, оставшиеся 3 поля добавляются как included. Поиск по индексу сразу вернет сущность. Если тянем все 30 полей, то после поиска включается лукап остальных полей (что представляет собой ЕЩЕ один поиск уже по кластерному индексу тех-же самых данных), стоимость будет порядка 10-20% oт запроса. Это не считая логических чтений страниц всех этих полей, их транспортировки и прочего

S>>А также есть еще один момент который я не упомянул — это обновления системы. Каждые полгода-год выходят новые версии продукта. В него добавляются какие-то возможности, большая часть из которых является общими для всех. Сейчас это просто делается таким образом, что любая общая фича перекопируется по всех версиям продукта.

C>Ну так добавляем новые поля в модель сервиса и вперёд с песней.

так вот к каждый солюшн и добавляются сейчас. От этого и хочется уйти
Отредактировано 25.09.2017 23:32 Stalker. . Предыдущая версия .
Re[2]: Переиспользование существующих методов
От: Слава  
Дата: 26.09.17 00:11
Оценка:
Здравствуйте, Cyberax, Вы писали:

C>Вообще, мой совет — не надо усложнять. Если лишних методов около 20%, то не надо голову морочить — пусть так и остаются. Ну и по возможности классическими приёмами переиспользовать логику на сервере. Если уходить в дебри OData и всяких языков запросов, то можно там навсегда и остаться.


Я подозреваю, что в Амазоне, где вы работаете, массово используется Java или Scala, а для них по сей день, в 2017 году нет нормальных средств для создания динамических запросов*. Ну потому что это не для кумаров, а для тех, кто пишет на скале, прикосновение к СУБД является лютейшим зашкваром. Отсюда и ваше столь скептическое отношение к средству, которое просто работает.

* Гугление по запросу odata jooq не дало вменяемых результатов, odata hibernate показало нечто монструозное (hibernate же), odata4j не содержит средств манипуляции запросами внутри кода, кроме как на уровне AST, odata slick не показывает вообще ничего.
Re[3]: Переиспользование существующих методов
От: DenisCh Россия  
Дата: 26.09.17 04:31
Оценка:
Здравствуйте, Слава, Вы писали:

С> Ну потому что это не для кумаров, а для тех, кто пишет на скале, прикосновение к СУБД является лютейшим зашкваром.


А можно спросить (не ради подкола, а ради информации) — почему так? В скале всё по-другому?
avalon/2.0.3
Re[4]: Переиспользование существующих методов
От: Слава  
Дата: 26.09.17 08:33
Оценка:
Здравствуйте, DenisCh, Вы писали:

С>> Ну потому что это не для кумаров, а для тех, кто пишет на скале, прикосновение к СУБД является лютейшим зашкваром.

DC>А можно спросить (не ради подкола, а ради информации) — почему так? В скале всё по-другому?

Честно говоря, это просто результат наблюдений. Скальщиков в энтерпрайзе я не встречал, а СУБД — оно всё про энтерпрайз.
Re[5]: Переиспользование существующих методов
От: Cyberax Марс  
Дата: 27.09.17 04:00
Оценка:
Здравствуйте, Stalker., Вы писали:

C>>ЧЗХ? Поиск строки в таблице нужен будет в любом случае, если только в индексе не находятся ВСЕ поля, необходимые для выполнения запроса. И поиск по главному ключу — самая эффективная операция в БД.

S>есть таблица с 30 полями. Есть поиск по 3 полям, на выходе сущность с 6 полями. Делается индекс по 3 полям, оставшиеся 3 поля добавляются как included. Поиск по индексу сразу вернет сущность.
Если это критический по времени запрос — ну так и делаем отдельный метод. Какие проблемы? Реально их будут ну максимум десятки штук.

C>>Ну так добавляем новые поля в модель сервиса и вперёд с песней.

S>так вот к каждый солюшн и добавляются сейчас. От этого и хочется уйти
Что в этом плохого?
Sapienti sat!
Re[3]: Переиспользование существующих методов
От: Cyberax Марс  
Дата: 27.09.17 04:03
Оценка: +1
Здравствуйте, Слава, Вы писали:

C>>Вообще, мой совет — не надо усложнять. Если лишних методов около 20%, то не надо голову морочить — пусть так и остаются. Ну и по возможности классическими приёмами переиспользовать логику на сервере. Если уходить в дебри OData и всяких языков запросов, то можно там навсегда и остаться.

С>Я подозреваю, что в Амазоне, где вы работаете, массово используется Java или Scala, а для них по сей день, в 2017 году нет нормальных средств для создания динамических запросов*.
Там где я работаю, нет динамических запросов вообще. Как и РСУБД. Там где РСУБД есть, то динамических запросов тоже почти нет или только очень контролируемые.

С>Ну потому что это не для кумаров, а для тех, кто пишет на скале, прикосновение к СУБД является лютейшим зашкваром. Отсюда и ваше столь скептическое отношение к средству, которое просто работает.

На Скале мы вполне себе пишем. Но отношение к "просто работает" монстрам типа odata — строго отрицательное. Лучше пусть будет пара сотен лишних тупых строк кода, чем недели отладки workaround'ов для "умных" решений.
Sapienti sat!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.