Pregunta

I have read other questions about circular references on here but I can't find the answer to my question.

I have three class libraries: Authentication, EmailService and ExceptionService.

Authentication controls user login to various applications, EmailService sends emails, the ExceptionService logs errors/exceptions to a database.

At the moment Authentication references the EmailService and the ExceptionService, to use their functionality, and this works. The ExceptionService references the EmailService to send report emails. All is well.

What I would like to know is if the following is possible/advisable/stupid, and if there is a better way to do it:

  • I want the EmailService to be able to use the functionality of the ExceptionService, so any errors in the EmailService are reported. In theory this could mean that the ExcpetionService would then call back to the EmailService to send the reporting email, which may trigger the same error, so I would have to write a method that was used by the EmailService only which did not send the email, only logged it.

  • The ExceptionService should still reference the EmailService.

  • The Authentication class library should also still use both of the other services.

This all sounds very complicated and circular, which is why I think it might not be a good thing to do. But what should I do instead?

I have tried referencing the ExceptionService in the EmailService, but it then will not compile when I create a private ExceptionService object and try to use it.

I suppose what I really want is for any of my applications to reference the EmailService and ExceptionService, but for them to also reference each other.

The only way of solving this that I have found so far is to forget about reporting exceptions in the EmailService.

Many thanks for your help :)

¿Fue útil?

Solución

The reason you are having problems is because you are tightly coupling your classes and the compiler very sensibly gets upset when you try to create a circular coupling. You could solve this by making interfaces for your services and supplying an instance of the interface of one service to the implementation of the other and vice versa.

A much better solution though would be to cease reinventing the wheel and to use an existing logging framework. Both NLog and Log4Net will meet your logging and emailing needs.

Otros consejos

Merge the assemblies. It is a common mistake to split a project into as many assemblies as possible. This adds management burden and causes trouble in case of cyclic references. Tight coupling is to be avoided, but it cannot be entirely avoided. Accept it.

Your situation is a valid case of a cyclic reference. The two classes just need each other for logical reasons.

You can resolve the cyclic reference issue with interfaces, but the dependencies still exists at runtime. The interfaces do not improve code quality, they just shut up the compiler warnings.

Do not manage dependencies with assemblies. Use namespaces and folders. Assemblies are deployment units, not dependency management tools.

The problem is that you uses concrete classes, not interfaces. So, my proposition is to introduce two interfaces i.e.:IExceptionService, IEmailService and place them in the separate project e.g.: Services. Projects containing implementations of these two services will reference this new project. Thanks to that ExceptionService can use IEmailService and EmailService can use IExceptionService. At the same time ExceptionService and EmailService can be defined in different assemblies.

What is important ExceptionService and EmailService shoudn't be aware what is behind these interfaces. The concrete implementations should be somehow injected into them. In order to do that you can use dependency injection container. If you don't want to use another new library, you can also implement simple service locator.

(I'm ignoring the AuthenticationService for now because it only muddles the issue - what you have is a simple circular dependency between two services - Exception and Email).

The best way to solve these circular dependency issues is by using a layer of interfaces and a repository.

Lets say you have two classes, EmailService and ExceptionService. They can't reference each other's DLL, so what you do, you create a third assembly, Interfaces, and create two interfaces for them, IEmailService and IExceptionService. Now your two classes can both reference only that shared Interfaces assembly.

Using some sort of Inversion of Control mechanism, your EmailService gets a reference to a IExceptionService, and vice versa, and thus the circle is broken.

One simple IoC mechanism is the Service Locator pattern. Create this (simplified) object:

public class ServiceLocator
{
     public static IEmailService EmailService {get;set;}
     public static IExceptionService ExceptionService {get;set;}
}

Now your EmailService, on startup, can register itself with the ServiceLocator, and allow other classes to get a reference to it, without having a dependency on its assembly.

Of course, most IoC solutions have a bit more to them than that, but that's the basic idea - prevent circular dependencies by extracting shared interfaces into a shared assembly, and reference only that.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top