Pregunta

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.

¿Fue útil?

Solución

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.

Otros consejos

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.

Licenciado bajo: CC-BY-SA con atribución
scroll top