Дублирование работы сервиса (dependency injection)
От: Буравчик Россия  
Дата: 15.05.20 18:50
Оценка:
Имеется несколько сервисов (классов). Каждый сервис имеет операцию, которая возвращает некое значение для заданного X. Типы возвращаемых значений отличаются для каждого сервиса.

class CommonService:
  getY0(x: X) -> Y0

class Service1:
  getY1(x: X) -> Y1

class Service2:
  getY2(x: X) -> Y2

class MainService
  getY(x: X) -> Y


Реализация сервисов:
— Service1 запрашивает Y0 из CommonService и преобразовывает Y0 в Y1.
— Service2 запрашивает Y0 из CommonService и преобразовывает Y0 в Y2.
— MainService запрашивает Y1 и Y2 и вычисляет Y

Composition root:
dep0 = CommonService()
dep1 = Service1(dep0)
dep2 = Service2(dep0)
main = MainService(dep1, dep2)


Проблема в том, для Service1 и Service2 нужны от CommonService одни и те же данные (вот прям абсолютно одинаковые, для одного и того же X). Поэтому CommonService делает свою работу дважды, хотя мог бы сделать один раз. А работа CommonService может быть очень долгой.

Как изменить интерфейсы сервисов, чтобы избавиться от двойной работы CommonService?
Best regards, Буравчик
Re: Дублирование работы сервиса (dependency injection)
От: wildwind Россия  
Дата: 15.05.20 19:01
Оценка: +2
Здравствуйте, Буравчик, Вы писали:

Б>Как изменить интерфейсы сервисов, чтобы избавиться от двойной работы CommonService?


Интерфейсы не менять, внутри CommonService реализовать кэширование.
Re[2]: Дублирование работы сервиса (dependency injection)
От: javacoder Россия http://upwork.com/freelancers/~016e5772d90cce5fd1
Дата: 15.05.20 19:11
Оценка: +2
Здравствуйте, wildwind, Вы писали:

Б>>Как изменить интерфейсы сервисов, чтобы избавиться от двойной работы CommonService?

W>Интерфейсы не менять, внутри CommonService реализовать кэширование.

более ООП подход — создать кеширующий wrapper наследник CommonService, обернуть им оригинальный CommonService
java шараги -> enterprise галеры, банки -> highload/bigdata/ml стартапы => Upwork $$$
Re[2]: Дублирование работы сервиса (dependency injection)
От: Буравчик Россия  
Дата: 15.05.20 22:32
Оценка:
Здравствуйте, wildwind, Вы писали:

W>Интерфейсы не менять, внутри CommonService реализовать кэширование.


Про кэш думал. Но у меня этот кэш должен инвалидироваться после каждого вызова getX из MainService, т.е. между вызовами MainService этот кэш не нужен (и даже вреден). Непонятно, как обеспечить такую инвалидацию.
Best regards, Буравчик
Re: Дублирование работы сервиса (dependency injection)
От: Буравчик Россия  
Дата: 16.05.20 08:37
Оценка: +1
Здравствуйте, Буравчик, Вы писали:

Б>Как изменить интерфейсы сервисов, чтобы избавиться от двойной работы CommonService?


Пока придумалось изменить реализацию сервисов — вынести преобразование в отдельные классы.
Y0 в Y1 => ConverterY0Y1
Y0 в Y2 => ConverterY0Y2

Cвязь сервисов станет такая:
dep0 = CommonService()
dep1 = Service1(dep0, ConverterY0Y1)
dep2 = Service2(dep0, ConverterY0Y2)
main = MainService(dep0, ConverterY0Y1, ConverterY0Y2)


Тогда MainService не будет зависеть от сервисов, а будет получать данные из dep0 напрямую, один раз. Потом с помощью конвертеров сможет получить нужные ему Y1 и Y2. Да и конвертеры можно будет тестировать по-отдельности.

Наверное, даже лучше конвертеры не передавать в качестве зависимостей, а просто вызывать (и создавать) классы напрямую, т.к. все нужные параметры для них передаются в сервис.

dep0 = CommonService()
dep1 = Service1(dep0) # внутри создает и использует ConverterY0Y1
dep2 = Service2(dep0)  # внутри создает и использует ConverterY0Y2
main = MainService(dep0)  # внутри создает и использует ConverterY0Y1 и ConverterY0Y2


В чем могут быть проблемы такого подхода (выделение ConverterY0Y1 и ConverterY0Y2)?
Best regards, Буравчик
Re[3]: Дублирование работы сервиса (dependency injection)
От: wildwind Россия  
Дата: 16.05.20 13:56
Оценка: 1 (1)
Здравствуйте, Буравчик, Вы писали:

Б>Про кэш думал. Но у меня этот кэш должен инвалидироваться после каждого вызова getX из MainService, т.е. между вызовами MainService этот кэш не нужен (и даже вреден). Непонятно, как обеспечить такую инвалидацию.


Самый быстрый путь к полезным советам — перестать шифроваться и объяснить реальную задачу по-русски простыми словами.
Re[4]: Дублирование работы сервиса (dependency injection)
От: Буравчик Россия  
Дата: 16.05.20 14:50
Оценка: 6 (1)
Здравствуйте, wildwind, Вы писали:

W>Самый быстрый путь к полезным советам — перестать шифроваться и объяснить реальную задачу по-русски простыми словами.


Не думаю, что это поможет. Вопрос не про реальную задачу, а про DI

Примерно так:
1. Есть данные о действиях пользователя (обновляются ежеминутно). Хранятся в БД, нужно вытаскивать их из БД. Это CommonService
2. По этим данным нужно посчитать "активность" пользователя (как часто покупает). Это Service1(CommonService)
3. По этим данным нужно посчитать "платежеспособность" пользователя (как много тратит). Это Service2(CommonService)
4. По активности и платежеспособности нужно посчитать "важность" пользователя. Это MainService(Service1, Service2)

Все эти параметры рассчитываются в реальном времени. В сервис передается id пользователя.

Проблема в том, что для выполнения п.4 нужно дважды выполнить п.1.

Само по себе кэширование п.1 в данной задаче не нужно, и даже вредно, т.к. выдаст неактуальную информацию.
Best regards, Буравчик
Re[2]: Дублирование работы сервиса (dependency injection)
От: romangr Россия  
Дата: 17.05.20 07:05
Оценка: +1
Здравствуйте, Буравчик, Вы писали:

Б>Здравствуйте, Буравчик, Вы писали:


Б>>Как изменить интерфейсы сервисов, чтобы избавиться от двойной работы CommonService?


Можно как-нибудь так сделать:
public interface ICommonService
{
    RawData GetRawData();
}

public interface IService1
{
    Service1Data GetService1Data(RawData data);
}   

public interface IService2
{
    Service2Data GetService2Data(RawData data);
}
    
public interface IMainService
{
    DoMainWork();
}
    
public class MainService: IMainService
{
    public MainService(ICommonService common, IService1 service1, IService2 service2)
    {
        _common = common;
        _service1 = service1;
        _service2 = service2;
    }

    public void DoMainWork()
    {
        var rawData = _common.GetRawData();
        var data1 = _service1.GetService1Data(rawData);
        var data2 = _service2.GetService2Data(rawData);
        ...
    }
}
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re[5]: Дублирование работы сервиса (dependency injection)
От: wildwind Россия  
Дата: 17.05.20 09:17
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>4. По активности и платежеспособности нужно посчитать "важность" пользователя. Это MainService(Service1, Service2)


Хотелось бы еще уточнить, как и когда вызывается MainService. Одно дело, если это ежемесячная генерация отчета по всем пользователям. Другое дело, если MainService вызывается в результате какого-то действия пользователя (например, чтобы решить, дать или не дать скидку).

Б>Само по себе кэширование п.1 в данной задаче не нужно, и даже вредно, т.к. выдаст неактуальную информацию.


Почему? Если для расчета "активности" и "платежеспособности" используются одни и те же данные, их вполне можно закешировать на время от первого вызова CommonService до второго.


Отдельный вопрос, а надо ли все это считать в реальном времени, но это вам виднее. Если от пары действий пользователь из "важного" может вдруг стать "неважным", я бы считал это багом.
Re[6]: Дублирование работы сервиса (dependency injection)
От: Буравчик Россия  
Дата: 17.05.20 13:49
Оценка:
Здравствуйте, wildwind, Вы писали:

W>Хотелось бы еще уточнить, как и когда вызывается MainService. Одно дело, если это ежемесячная генерация отчета по всем пользователям. Другое дело, если MainService вызывается в результате какого-то действия пользователя (например, чтобы решить, дать или не дать скидку).


MainService вызывается в результате действия пользователя. Много раз в день.

W>Почему? Если для расчета "активности" и "платежеспособности" используются одни и те же данные, их вполне можно закешировать на время от первого вызова CommonService до второго.


Да, с кэшем мне нравится больше, чем с конвертерами.

Но глобальный CommonService живет и после выполнения запроса пользователя. Поэтому нужно отследить, что это второй запрос к CommonService от первого пользователя (надо отдать кэш), а не первый запрос от второго пользователя (надо игнорировать кэш). Плюс в кэше остаются данные, которые после выполнения второго запроса никому и никогда не понадобятся. Как их очищать? Поэтому, я считаю, что глобальный кэш делает не совсем то, что надо.

Возможно ли как-то сделать CachedCommonService, который привязан к запросу пользователя в MainService. Инициируется в момент начала операции MainService, очищается после окончания операции в MainService.

W>Отдельный вопрос, а надо ли все это считать в реальном времени, но это вам виднее. Если от пары действий пользователь из "важного" может вдруг стать "неважным", я бы считал это багом.


1. В реальной задаче часть данных считается оффлайн. Но некоторые — в реальном времени, речь как раз о таких.
2. В реальной задаче считаются другие показатели. Эти показатели привел как пример, чтобы снизить абстракность задачи (как просили).
Best regards, Буравчик
Re[7]: Дублирование работы сервиса (dependency injection)
От: wildwind Россия  
Дата: 17.05.20 14:15
Оценка: +1
Здравствуйте, Буравчик, Вы писали:

Б>Но глобальный CommonService живет и после выполнения запроса пользователя. Поэтому нужно отследить, что это второй запрос к CommonService от первого пользователя (надо отдать кэш), а не первый запрос от второго пользователя (надо игнорировать кэш).


Если во все запросы передается id пользователя, как ты говорил выше, то в чем проблема? Данные в кэше тоже привязаны к id пользователя.

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


Подобрать подходящие размер кэша и время протухания. Чтобы хватило на все выполняющиеся одновременно запросы.
Плюс, если есть часть особо активных пользователей, для которых приходится часто вызывать MainService, можно еще увеличить кэш, чтобы и им хватило.

Б>Возможно ли как-то сделать CachedCommonService, который привязан к запросу пользователя в MainService. Инициируется в момент начала операции MainService, очищается после окончания операции в MainService.


Можно наверное, но смысл? Если простое кэширование поможет в 90% случаев, этого не достаточно?
Re[5]: Дублирование работы сервиса (dependency injection)
От: Мирный герцог Ниоткуда  
Дата: 17.05.20 15:16
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Примерно так:

Б>1. Есть данные о действиях пользователя (обновляются ежеминутно). Хранятся в БД, нужно вытаскивать их из БД. Это CommonService

Возьми client-side кеш для своей субд (или используй готовые кеши — memcached, redis, тысячи их), хернёй же маешься, ну.
нормально делай — нормально будет
Re[7]: Дублирование работы сервиса (dependency injection)
От: · Великобритания  
Дата: 18.05.20 10:36
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Возможно ли как-то сделать CachedCommonService, который привязан к запросу пользователя в MainService. Инициируется в момент начала операции MainService, очищается после окончания операции в MainService.

Да вроде же как слышится, так и пишется, там выше же уже показали. Если есть возможность — делай явно. Кеш это если полный бардак и сложно предсказать что когда и в какой момент времени может понадобится. А у тебя вроде всё просто:
        var userData = _common.GetUserData(userId);
        var data1 = _service1.GetService1Data(userData);
        var data2 = _service2.GetService2Data(userData);
    return useAllData(data1, data2);
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[6]: Дублирование работы сервиса (dependency injection)
От: · Великобритания  
Дата: 18.05.20 11:08
Оценка:
Здравствуйте, Мирный герцог, Вы писали:

Б>>Примерно так:

Б>>1. Есть данные о действиях пользователя (обновляются ежеминутно). Хранятся в БД, нужно вытаскивать их из БД. Это CommonService
МГ>Возьми client-side кеш для своей субд (или используй готовые кеши — memcached, redis, тысячи их), хернёй же маешься, ну.
Зависит от... Если вспомнить классику "There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.".
Закинуть в кеш просто. Сложно потом заставить это нормально работать.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: Дублирование работы сервиса (dependency injection)
От: akasoft Россия  
Дата: 20.05.20 07:41
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Реализация сервисов:

Б>- Service1 запрашивает Y0 из CommonService и преобразовывает Y0 в Y1.
Б>- Service2 запрашивает Y0 из CommonService и преобразовывает Y0 в Y2.
Б>- MainService запрашивает Y1 и Y2 и вычисляет Y

Ладно, а вариант "Y3 Service3(X)" не рассматривается?
Для активности и платёжеспособности выделены отдельные service, почему для важности нет?
А внутри запрашивать Y0 один раз. И нет кеширования.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>> SQL DE 2016
Re[2]: Дублирование работы сервиса (dependency injection)
От: Буравчик Россия  
Дата: 20.05.20 10:28
Оценка:
Здравствуйте, akasoft, Вы писали:

A>Для активности и платёжеспособности выделены отдельные service, почему для важности нет?

A>А внутри запрашивать Y0 один раз. И нет кеширования.

К этому варианту и пришел
Best regards, Буравчик
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.