Как стать автором
Обновить

Комментарии 24

Почему бы не сделать пару виртуальных методов:
"Task ExecuteIterationAsync(CancellationToken ct)" и
"void ExecuteIteration(CancellationToken ct)",
чтобы исключить все эти ограничения на то, как должен быть определён метод???


PS: Всё понял... тогда DI в методе не сделать

Да, Вы правы, это ограничение вызвано именно необходимостью дать возможность инъекции scoped-зависимостей. При проектировке конкретно этого момента я опирался на то, как это сделано для middleware в ASP.NET Core.

Важно! Автоматическая регистрация работает только для работ, определенных в той же сборке, что и код, вызвавший метод регистрации.
А если сделать указание assembly в имени типа? Тогда по идее можно подгрузить эту assembly по необходимости. Т.е. использовать assembly qualified name, на подобие:

System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35

Вручную, разумеется, Вы можете зарегистрировать работу из любой сборки. Однако, в процитированном Вами предложении речь идет про автоматическую регистрацию, при которой библиотека сама ищет все типы, реализующие абстрактный класс WorkBase.

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

Может я конечно что-то недопонял, но я как раз говорю про автоматическую регистрацию. Можно конечно сделать форк и реализовать всё что хочешь, но это одна из востребованных функций, почему бы не реализовать это.

Т.е. мы решаем проблему decouple worker от приложения. На момент создания приложения, мы не знаем что нам понадобится и потом в процессе эксплуатации, добавляем или заменяем воркеры на новые, без необходимости перекомпиляции и передеплоймента приложения, через конфигурацию.

Можно поиспользовать Assembly.Load

И наверное было бы ещё полезно, не столько загружать по конкретному имени типа, а сказать — вот у нас есть интерфейс, загрузи с этой ассембли, воркер реализующий этот интерфейс.

Похоже, это я Вас изначально неправильно понял. Интересная идея, спасибо!

Есть ли какие-то киллер фичи, чтобы выбрать для использования вашу библиотеку, а не hangfire?

Hangfire намного более продвинутая библиотека, скорее даже целый фреймворк. Однако, по моему мнению, там много своего бойлерплейта, если мы не берем в расчет несерьезные примеры вроде создания работы из лямбда-выражения. В dotWork я старался решить одну конкретную задачу - уменьшение бойлерплейта, а весь остальной функционал основывается на стандартном для .NET классе BackgroundService. Кроме того, могу ошибаться, но в hangfire вроде бы нельзя использовать scoped и transient сервисы со стандартным DI контейнером.

Если мне не изменяет память, то можно делать вот так:

public class ExampleWork
{
  	private readonly ISomeDependency _someDep;
    public ExampleWork(ISomeDependency someDep){_someDep = someDep}
    public async Task ExecuteIteration(CancellationToken ct)
    {
        await _someDep.DoWorkAsync(TimeSpan.FromSeconds(1), ct); // симулируем работу
        Console.WriteLine("Work iteration finished!");
    }
}

Далее регистрируем джобу:

BackgroundJob.Enqueue<ExampleWork>(x => x.ExecuteIteration(CancellationToken.None));

И в итоге каждый раз будет создаваться экземпляр ExampleWork и зависимости с любым временем жизни подойдут.

Спасибо, не знал, выглядит очень лаконично. Здесь хочу подчернуть несколько моментов:

  • если мы регистрируем много работ, то нужно дублировать указанный Вами код регистрации, в то время как dotWork позволяет зарегистрировать "все сразу" вызовом AddWorks;

  • если нам потребуется получить настройки (а это достаточно частая необходимость, по крайней мере, по моему опыту), нужно будет в каждую работу инжектить свой IOptions<MyOptType> (или даже IOptionsMonitor<MyOptType>, чтобы поддерживать горячую перезагрузку), а если один тип используется несколькими работами - пользоваться именованными настройками... В общем, Вы меня поняли :)

Если же стандартных возможностей .NET не хватает, то можно и нужно использовать hangfire.

Важно! Автоматическая регистрация работает только для работ, определенных в той же сборке, что и код, вызвавший метод регистрации. Работы из других сборок придется регистрировать вручную.

Возможно https://github.com/khellang/Scrutor помог бы зарегестрировать работы определенные и в других подключенных сборках. Если конечно сами сборки не грузить ручками.

Спасибо за идею! Я с осторожностью отношусь к автоматическому сканированию всех сборок, потому что это может привести к регистрации "лишних" работ, а также преждевременной подргузке тех библиотек, которые еще не загружены в домен приложения. Мне больше импонирует вариант, предложенный чуть выше - разрешить пользователю самому указывать сборки, которые нужно просканировать на предмет наличия работ.

Scrutor позволяет указать какие сборки сканировать. Но с другой стороны все это у нас не что иное как Back, и как показывает мой опыт, первые же клиенты подключившиеся к сервису заставят прогрузится все сборки, так что я не вижу особой разницы в моменте их загрузки, при первом вызове или при сканировании. Более того второй вариант мне кажется предпочтительней. Хотя конечно мой опыт не перекрывает все возможные сценарии.

Ну и по поводу автоматического сканирования. Мой текущий проект содержит порядка полторы сотни различных сервисов, простыня из AddSingleton/AddTransient значительно перегружает конфигурацию. Я использую пометку атрибутом [SingletonService]/[TransientService], и сканированием регистрирую именно их. Очень удобно, и не надо волноваться забыл ты зарегестрировать или нет.
Более того, если сборку под проект делает другой человек, а мы их подключаем через нугет-репозиторий, то ему достаточно пометить атрибутом и его сервисы также будут зарегистрированы. Таким образом, если я использую какую либо зависимость, а она имеет еще 20-30 своих зависимостей, то простыню моих 150 сервисов расширять еще 30 не нужно.
Единственный момент, о сканировании надо помнить, потому как это не явная регистрация зависимостей.

Спасибо, интересно было узнать про регистрацию в крупных приложениях. Хочу отметить, что благодаря простоте dotWork совместима с любым способом регистрации зависимостей. По желанию разработчик может использовать стороннюю библиотеку, упрощающую регистрацию, и с большой долей вероятности dotWork будет с ней совместим.

в DI .net core можно если в конструктор передается класс с дефолтным конструктором в мэпинге не указывать?

или нужно партянки вот такие писать?

services.AddSingleton<АProvider>();

services.AddSingleton<BProvider>();

services.AddSingleton<DProvider>();

services.AddSingleton<CProvider>();

Такой возможности нет, все зависимости нужно явно регистрировать в контейнере. Спасибо за идею, мне очень нравится. Займусь, как будет время.

Если я правильно понял вопрос, то может помочь класс ActivatorUtilities. В нем определены методы, позволяющие конструировать сервисы, не зарегистрированные в IServiceProvider, при этом зависимости конструктора резолвятся из него. Если коротко, то он просто делает provider.GetService() для каждого параметра конструктора

Я так понял, что в данном случае ситуация обратная - у нас есть сервисы, которых нет в контейнере, и нам нужно их подать в конструктор сервиса (в данном случае работы), который в контейнере есть.

Вы проводили какой-нибудь R&D? Например, есть Quartz.NET либа, которая покрывает перечисленные Вами в статье требования и предлагает даже больше.

Вы проводили какой-нибудь R&D?

Да, разумеется. Все решения, которые я нашел, включая Quartz.NET, привносят много своих концепций в разрабатываемое приложение. dotWork, напротив, сводих их число к минимуму, по сути своей являясь оберткой над BackgroundService.

Quartz.NET либа, которая покрывает перечисленные Вами в статье требования и предлагает даже больше

И это отлично! Если я буду разрабатывать приложение, в котором будут специфичные требования, сложнореализуемые через BackgroundWorker, я выберу Quartz.NET, или Hangfire, или любой другой фреймворк, который мне подойдет. Если же я захочу реализовать воркер, которому достаточно возможностей "сырого" .NET, то для уменьшения бойлерплейта выберу dotWork. Заметьте - не чтобы закрыть потребность в функционале, а именно чтобы потратить меньше кода на функционал, который уже имеется в .NET.

Спасибо, выглядит интересно.

Спасибо автору, интересный проект. Недавно использовал такие сервисы, понадобилось запускать по крону. Может быть имеет смысл тоже добавить такую фичу вместе/вместо delay

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории