-
Notifications
You must be signed in to change notification settings - Fork 411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplifying the API #71
Comments
Also, instead of: public class MyJobFactory : IJobFactory
{
public IJob GetJobInstance<T>() where T : IJob
{
return MyDepencyInjectionFramework.GetInstance<T>();
}
}
JobManager.JobFactory = new MyJobFactory(); We could just: // "type" here is System.Type
JobManager.GetJobInstance = type => MyDepencyInjectionFramework.GetInstance(type) as IJob; We lose the generics ("<T>") but a decent dependency injection framework (if not all of them) supports dealing with Type. There would be no worry on the dependency injection framework returning |
How come schedule is a generic that instantiates the job classes? JobManager.Schedule(new MyJob(_someConstructorArg)).ToRun....... Would be far more simple. JobFactory would be redundant, state could be hold from run to run (if needed) and injection is a breeze. |
Simpler indeed. But I don't get it how "injection is a breeze", I'm not seeing any dependency injection at all. I don't really like the DI aspect of the library to be honest but I know people use it. |
Regarding Injection, it's a lot simpler since the user of the API instantiate the job class themselves, they can use any injection framework they like without creating a job factory. I guess it's the same as saying the JobFactory is redundant ;o) |
Without "proper" DI in the library, you may have cases where the user have to call I don't personally like that, I'm more on your side. Ditching DI completely is something tempting to do (for me), but I have to remember the folks that are using it. I'll consider doing it or not when closing this issue. |
I actually disagree. A library should not be DI aware, it should just support DI by exposing the needed interfaces. A good rule of thumb is to never do new/activator.createinstance on a generic type. What users of the library would have to do to use DI is something like kernel.getinstance<MyJob>(), which makes perfect sense if you are into DI. As for instantiation, I would think that most people would like it better if the class was created once in stead of at every run, and if you really need a new instance at every run you could just new an inner calls up in the Execute method, but I think this would be a special case. This also gives the option to call Dispose on .Stop if the Job class implements IDisposable. |
Amen to that. That's my argument since, well, forever. A library design shouldn't entangle their design with yours. If it starts doing so you're in the way of making a framework (which is usually not a good thing in my book). As I said, I never really liked the whole DI aspect of it. Heck, I never really used it except for testing the feature itself. The thing is, I'm not the original author of the library (#59), I didn't implement it. But that's the problem no. 2 (and a piece of cake to solve it, despite being a major change). The no. 1 is by far the global scheduler, that I'm addressing on #148. While DI is more of a source of confusion than anything, the global scheduler is the reason of 99% bugs/bad pull requests I get.
Getting rid of the global scheduler makes the job factory (along with the Waiting until #148 to ditch DI brings both the benefit of a (yet another) justification to do so and of bundling together two major breaking changes. P.S.: Giving a second thought, this issue is from the time that I wanted to simplify the API without changing how it fundamentally works. It was more like a note to myself. I'm closing it, but feel free keep the conversation going. |
Sure thing, #148 makes perfect sense as well. |
When it's done, being an early adopter and reporting issues would be an amazing contribution. Coding it's just one part of the equation. If you want to know more about the redesign stay tuned on #148. Thanks for the conversation by the way! |
Are these links worth looking at? They are related to dependency injection "inside" a library. http://stackoverflow.com/questions/2045904/dependency-inject-di-friendly-library |
While the links are great to someone that is starting to grasp IoC (and DI for that matter), they don’t say much about why or when should a library be DI aware. (I love how this guy had a breakdown) In my experience, DI is a powerful tool when it comes to application architecture, but it's only useful when you have a well-defined core domain. The domain defines the interfaces, then other layers implement it (sometimes it’s useful to have more than one implementation of an interface). Then, just before the domain starts to run, you inject the implementation into the domain. The domain defines only the interface, yet it still directly calls the injected implementation (even though it doesn’t know its type or from where it comes from). There you have it, the Inversion of Control. But a library has nothing to do with that. Unless you are aiming for making more of a framework, a library shouldn’t dictate in any way the high level details of your architecture. A library is supposed to be used inside an implementation of yours of an interface of yours. You, the system designer and library user, should be 100% responsible of your DI design and usage. Just imagine the mess if every library attempted to have some sort of DI awareness corner. |
@tallesl I could not have said it better... |
The issue here is not really whether a library should force you to use DI or not, but rather the fact that FluentScheduler resolves instances and executes them, it is in fact another composition root. All obvious differences aside, FluentScheduler is no different to a web request being executed, and as such it should allow some manner of providing dependency injection so that each request (scheduled task) can be executed by the instance being resolved from a container. This approach allows things like lifecycle scopes to be used effectively (ie transient versus scoped to current request versus singleton). I currently use an implementation of IJobFactory that has a reference to my container (SimpleInjector) which does an ok job (pun intended) of allowing this but unfortunately has the design flaw of wanting to return a resolved instance outside of an async scope, so a bit more work is required if you are using request scope lifestyles. Perhaps it might be an option to make FluentScheduler behind the scenes operate more like a webrequest where it will resolve jobs using the IJobsFactory but will allow hooks for creating "child containers" when per request life cycles are required. As a final comment, please don't remove the current support for DI in this "library" because any library that can act as a composition root should provide (but not force/require) a way of using DI, and also I would consider FS a small framework for scheduling jobs, not a library. |
like @nrjohnstone I was afraid of having the JobFactory removed, but I can confirm that after implementing this approach the design get's much clearer. I am using SimpleInjector, too, and this is how it is working right now. A public interface IJobExecutor
{
void ExecuteJob();
} Some implementations of public class UnitOfWorkJobExecutor<TJob> : IJobExecutor<TJob>
{
private static readonly ILogger Logger = LogManager.Create<UnitOfWorkJobExecutor<TJob>>();
private readonly IUnitOfWork unitOfWork;
private readonly IJobExecutor<TJob> jobExecutor;
public UnitOfWorkJobExecutor(IUnitOfWork unitOfWork, IJobExecutor<TJob> jobExecutor)
{
Logger.Info($"Beginning unit of work for {typeof(TJob).Name}");
this.unitOfWork = unitOfWork;
this.jobExecutor = jobExecutor;
}
public void ExecuteJob()
{
try
{
unitOfWork.Begin();
jobExecutor.ExecuteJob();
Logger.Info($"Completing unit of work for {typeof(TJob).Name}");
unitOfWork.Complete();
}
catch
{
Logger.Info($"Aborting unit of work for {typeof(TJob).Name}");
throw;
}
finally
{
unitOfWork.Dispose();
}
}
} A small method resides in my application root class ( private void ExecuteJob<TJob>() where TJob : IMyPersonalJob
{
using (ScopeManager.BeginScope())
{
CompositionRoot.GetInstance<IJobExecutor<TJob>>().ExecuteJob();
}
} This method is used to add the jobs to the schedule: JobManager.AddJob(ExecuteJob<SendSmtpJob>, s => s.ToRunNow().AndEvery((int) jobScheduleOptions.SendSmtpJobInterval.TotalSeconds).Seconds()); Today, |
@marcwittke perfect, that looks EXACTLY like what I want, just haven't had the time (or will) to sit down and fight with it... Thanks mate ! |
@tallesl you should take the time to make sure the documentation includes @marcwittke example above for designing your jobs as a composition root with an IoC container |
I do agree with your sentiment about libraries having to be oblivious to DI. Mark Seemann wrote a great article about this, which, considering the above comments, you are already aware of. FluentScheduler, however, is not a library. It is a framework. It is a framework in the same right that ASP.NET Core MVC and Nancy are (web) frameworks, and in the same right that NServiceBus and Rebus are (messaging) frameworks. The FluentScheduler framework is in control over the execution of our application code. This is Inversion of Control and that's the primary difference between a library and a framework. With that in mind, frameworks will often require to be "DI aware". This doesn't mean a framework should dictate that a DI Container should be used, or even what the shape of a DI Container's API should be. That is a fallacy and an often made mistake that leads to the Conforming Container anti-pattern. Instead, a framework should define interception points that allows users to plug in a DI Container of their choice, or use Pure DI if they rather use DI without the use of a container, or not use DI at all, if their application is truly simple. Mark Seemann, again, has some great guidance on making your framework DI friendly. TBH, I think the current |
Let's once again beat the dead horse.
Repeat with me: "FluentScheduler is not a framework."
Once more: "FluentScheduler is not a framework." It does control some of your code. But, usually, it's a tiny portion of the application code. Unless you're redoing cron, the Windows Task Scheduler or something alike, users typically just sprinkle some schedules here or there in their (big) systems, in which most of the code is unrelated to scheduling.
One last time: "FluentScheduler is not a framework." That's terrible definition. Take JavaScript's setTimeout(function () { alert('a second just passed') }, 1000) Is You know what else is (supposed to be) just a timer? This very library. FluentScheduler should be nothing more than a glorified easy-to-use no-frills timer. Factories, managers, registries, interfaces. Needlessly abstractions muddling the water of the real value of the library. Thank god they'll all soon be gone (#214). I purposely try to make my designs and discussions free from all that mindset of Java flavored OO illustrated by Martin Fowler blog posts. The cliché "simplicity is prerequisite for reliability" quote goes here. |
I'm sorry for beating the dead horse. As I see it, FluentScheduler currently is a framework. But this is actually caused by it having an If I understand correctly, your goal is to simplify FluentScheduler's API in such way that it effectively becomes a library again. This means removing the I wholeheartedly agree with that approach, because libraries are often simpler compared to frameworks and cause less coupling between with application code. As I browsed through the code base, I noticed there's a lot of complexity in FluentScheduler that can be removed when you remove this interception model. Less complexity is a win for the maintainer and a win for the user. So double thumbs up for that. The only thing that seems to be missing right now is good documentation examples that explain to users how to use integrate their DI Container effectively while registering delegates (or at least, I couldn't find any in the documentation). I did my share by updating my recent Stack Overflow answer to try to explain how to write an extension method for Simple Injector that simplifies integrating with FluentScheduler, with the aim of preventing code duplication. This might be of help to you and an inspiration to others using different DI libraries. I wish you the best of luck. |
The only excuse to let this documentation burden on my shoulders (as the library maintainer) is due this nasty little line, that thankfully it's on its way out. Yet I find baffling how folks can't get around that on their own. So you can't have a constructor with parameters (automatically resolved by your DI framework). How hard is to conclude that you're going to either access something static (that then resolves the instance) or get via closure (which is infinitely better)? I believe the fault (mostly) lies with the complexity goo created by DI frameworks. Those fancy-pants concepts of "transient", "scoped" and "lifetime managers" makes programmers unable to think for themselves. I sincerely don't know why we're still here on #71, when the real discussion took place on #148 which resulted in #214. Rest in peace, 2016 issue. |
I'm not really involved in any of these issues but as I commented in the past I've been getting emails. I think saying things like |
I'm locking this issue since its value is long gone plus people taking things personally. I don't shy away from stating what I've experienced around here, even if it isn't smiles and sunshines. And remember, you're not your code. |
Instead of:
We could just:
The latter is way simpler to use (not to mention that we could also ditch the
AddJob
method for late jobs, just use theSchedule
whenever need).One could argue that this way you can't have multiple registries implementation, but I bet that no one does that.
It's a little tricky to implement since the current
JobManager
is designed to work with anRegistry
(but it's worth the effort nonetheless).The text was updated successfully, but these errors were encountered: