5

I was working on migrating over a project which uses a static logger, and a static email service.
The email service logs emails sent, and the logger service sends an email if there are any logging issues (like DB down, etc.). If the email service fails, it can also log the issue.

If the logger fails, it tries to send an email notice, but if the email service fails (which could be why the logger was called in the first place), it just continues on and writes the issue to a log file.

Email Service <--statically references--> Logger Service

The issue is that both "services" are singletons, and both would need reference to each other as a constructor parameter. My first thought was property based injection, but I quickly found that seems to be a Temporal Coupling situation that is much undesired.

The main running idea now is, at the log level, have it call the service provider to create an email service (so it will get the singleton instance), but I was really trying to keep the class library also decoupled from the .Net Core injection system. Anyone have any recommendations in case I'm missing something?

Edit: After more thought, does it make sense perhaps to create an abstracted service provider of my own (a basic one) that can have another service injected into it? Then I could perhaps just pass that around instead in such cases.

5

I suggest you to read a great book about the C# dependency injection by Mark Seemann, with a lot of real world samples for different tasks you can do with it, even with circular dependencies similar to your case.

In general, the main design rule is You can't resolve the problem on its level, and in this case it's true too. You really need a mediator here to resolve interactions between two loggers. It easily can save the state for both of them, and notify each other about changes.

For example, you can introduce some Target for the messages, like this:

GeneralMessage // both loggers got this
EmailMessage // only email logger got this
LoggerMessage // only logger got this

So, in case of some trouble with one of your loggers, mediator send the message with corresponding type to notify other about issue.

Common approach for such cutting-edge logic is an AOP implementation (personally I prefer the PostSharp, which provides you a general approach, even with conventional techniques to assign the aspects on your classes).

As for decoupling your code from the .Net Core injection system, I suggest you to examine the Logger Factory, which is:

The new framework creates an abstraction layer or wrapper that enables you to use whichever logging framework you want as a provider. This ensures you have the maximum flexibility in your work as a developer. Furthermore, even though it’s only available with .NET Core, referencing .NET Core NuGet packages like Microsoft.Extensions.Logging for a standard Visual Studio .NET 4.6 project is no problem.

With Logger Factory you can even filter the messages to notify the all loggers about problems with other ones, connect to OS event logs and much more.

  • I have my own logger that stores the user ID, some IP/Host details, and more, for auditing purposes. It seems the logger provided my MS may not be flexible enough to take the logging parameters I have. Good point on the "mediator" idea though. I could instead create a "mediator" logger that takes a DB logger and email logger, then I can just tell the mediator to "go log that" and it will handle any failures. ;) – James Wilkins Mar 16 '17 at 18:40
  • 1
    Yeah, that's what I meant, good luck with your projects. – VMAtm Mar 16 '17 at 18:47
2

I can't tell if you wish to keep the circular dependency but are dealing with circular reference errors, or if you are asking for an alternative to the circular dependency.

If you like the circular dependency but can't figure out how to implement it, you can solve that problem with Unity, as described in this article.

If you don't like the circular dependency (I don't like it either) then here are some ideas:

  1. Get rid of the singletons. While that pattern was popular around 2005, these days it is considered really bad. Terrible for unit testing, particularly.

  2. Your logging service shouldn't send emails. It should allow the ability to do one thing: write to the log. That's it.

  3. Your logging service should be simple enough so it never fails. Unless maybe you're out of memory, in which case you won't be able to send an email either.

  4. If you like, you can have a separate service that persists the log. Commit to disk, for example, or send an email if you really want. But it should be separate from the log writer.

  5. If you want to get really fancy, you can have several services that can persist the log, using the chain of responsibility pattern. This allows failover capability. For example, perhaps logs get persisted in the database; if the SQL connection fails, they get stored in a flat file; if the flat file is full, it sends an email. Maybe you can do that in a later version :)

The above assumes by "log" you mean a "debug log" of some kind, as opposed to, say, an audit log, e.g. for regulatory purposes, which may be mission critical and far more complicated.

  • The idea was to keep it, but I may not. 1. I agree static singletons are bad, I don't have issue so much with instantiated ones in core; 2. If logging fails, then that's when an email is sent (db is probably down, and it's a much faster notice to hook at this point). If not, a job is required to monitor things, which seems getting unnecessarily too complex; 3. db down, yes it will fail (used also for auditing), then dumps to a file; 4. I'm thinking now to have the email service transient instead; 5. this is more the line of thought I had for the logging service idea. ;) Thanks for the input. – James Wilkins Mar 11 '17 at 9:31
  • There's a lot of logging going on, so I didn't want to keep creating objects all the time. I'll most likely register the logging as a singleton in ASP.Net Core start up, then register the email service as transient, since that happens much less often. Also, I created an 'IServiceProvider' of my own to remove any dependency on Unity (to support injecting others). I'll let one service use it to get reference to the other. – James Wilkins Mar 11 '17 at 9:43
  • Unity is very outdated, and ASP.NET Core has built in dependency injection mechanism – VMAtm Mar 11 '17 at 16:46
  • 4
    How does adding a service that persist the log help avoid the circular dependency? email → log → persist → email is still a cycle. – svick Mar 11 '17 at 18:38
  • Sorry, I'm new to ASP.Net Core and assumed Unity was part of the design, but I'm gathering now that it is not. When I say "Unity" I really meant the ASP.Net Core native DI provider. ;) – James Wilkins Mar 11 '17 at 21:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.