❔ Circular dependency between projects
I have 3 projects:
1.
Library
2. REST API
3. Windows service
The service
is supposed to host the API
.
Both the API
and service
depend on the library
. The service
depends on the API
so it knows to run it.
Problem: The API
also depends on the service
, as there is one property P
in service.classA
it uses, causing a circular dependency. P
is an object library.Interfaces.I
.
I'm unsure how to resolve this. Just putting classA
into the library
doesn't work, as classA
also depends on a model service.Models.M
. I guess I could put M
in the library
too, but is that a good approach?80 Replies
You can always break circular dependencies by adding another project
If you have 'interfaces' section in library, one way would be to split it into library.itf and library.impl
I already have implementations of the Interface. However I can't access the implementations directly because my classA decides which implementation I need
In my API I'm just telling him "Hey, use the login method of the interface" but that doesn't work as he needs an object reference
And that object reference is set in my classA in my service
It's kinda hard to answer without knowing what
classA
and M
are, and whether they belong in the library or the service
And why the API has this P
property which is defined in the service. The most obvious thing is to say "Remove that dependency", e.g. by removing the dependency on classA
and instead depending on an abstraction which is in library
Well the API can be used with 3 types of databases. The p Property is the interface of those 3 implementations
And in the classA, which is basically some code for initializing, it reads from a config file which of the 3 database types is to be used and sets the interface property to one of the 3 implementations
M is a class called service instance, which saves some stuff so that multiple instances of the service can run at the same time
Sounds like that should belong in the service, then
Yes, but that's why I can't just put classA into my library, because it depends on M and also on some stuff from the services settings file
So, why does the API depend on classA?
In my APIs controller I'm exposing the methods of the implementations based on which database is being worked with. The application knows which of the 3 implementations to use (aka which of the 3 database types is currently being used with the API) by reading from the config. This happens in classA, so in my service. My service reads from config to know which of the implementations to use and sets its property (which is an object of the interface) to the needed implementation. And the API controller accesses this property, which is set to an implementation, so it can choose the correct implementation and use its methods
I'm assuming that implementation implements an interface which is defined in the library?
Yes, exactly
Both the interface and its 3 implementations are in the library
So, why does the API need to fetch this implementation from a concrete instance of classA specifically?
I mean technically it doesn't I guess
After all the info comes from the config
Can it not be told this in another way? Maybe set a property on something in the API? Maybe classA needs to implement some
IDatabaseImplementationProvider
interface defined in the API / library?If classA did implement such an interface, what would the benefit be?
Would it contain a method for reading the database type from the config?
So instead of:
You have:
I see, now my classA already implements the BackgroundService interface since its a windows service. Would that be an issue if I make it implement two interfaces like that?
No, it's very common for types to implement multiple interfaces
Wait in this case, whats the difference between IDatabaseImplementationProvider and IDatabaseProvider?
Both the interface and its 3 implementations are in the library
IDatabaseProvider
is the interface which is implemented by the 3 implementations. You didn't say what its name is, so I made one upOh yes, my bad
Yeah obviously it's that since it's a property of classA
But you are accessing this.databaseProvider. In my controller there is no such property since it's only declared in classA
I'm having to guess, and build an example on the guesswork, because you haven't shown any relevant code
It's no surprise that it doesn't exactly match what you have
If you want more specific examples, give more details on your code
My Rest controller contains nothing but methods in this form
[HttpGet]
[Route("Login")]
public string Login(string username, string password, string DBAlias) {
try {
return GeodatabaseService.GeodatabaseAccess.Login(username, password, DBAlias);
}
catch (Exception ex) {
throw RESTException(ex);
}
}
Where is it accessing
classA
?The
GeodatabaseService.GeodatabaseAccess.Login
is classA.interfaceobject.methodGeodatabaseService.GeodatabaseAccess
is a static property?So GeodatabaseAcces in classA is this
public static IGeodatabase GeodatabaseAccess { get; set; }
And that IGeodatabase interface is the one in my libraryOh, don't do that
Set up your rest project's ioc container to inject an instance of
IGeodatabase
into controller which need it, then
Sorry, I hadn't guessed you were accessing classA
staticallyWhich part exactly is bad
The static access?
Accessing static state. If it's not mutable it's not too bad, but it makes things really hard to unit test, and as you've found right now, it makes things tightly coupled to each other
How does the service instantiate / set up the rest API? Presumably there's a point in there where you can pass in the
IGeodatabase
?private static IHostBuilder CreateHostBuilder(string[] args) {
return Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostBuilderContext, services) => {
services.AddHostedService<GeodatabaseService>();
})
.ConfigureWebHostDefaults(webBuilder => {
webBuilder.UseStartup<WebServiceREST.Startup>();
});
}
WebServiceREST is my api projectThat's in the windows service?
This code is from the program.cs of my windows service
Cool, just register the
IGeodatabase
thereprivate static IHostBuilder CreateHostBuilder(string[] args) {
return Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostBuilderContext, services) => {
services.AddHostedService<GeodatabaseService>();
services.AddSingleton<IGeodatabase>();
})
.ConfigureWebHostDefaults(webBuilder => {
webBuilder.UseStartup<WebServiceREST.Startup>();
});
}
like this...?
services.AddSingleton<IGeodatabase>(GeodatabaseService.GeodatabaseAccess);
?What did we achieve by doing this
Now your rest controller can take a
IGeodatabase
in its constructor
And it no longer has to depend on GeodatabaseService
And then your API project no longer depends on your windows serviceSorry, how does registering the IGeodatabase in the windows service allow my rest controller to access it?
Your windows service project sets up the ioc container for the api project. The api project fetches instances of the controllers from the ioc container. So anything which is registered in the ioc container is accessible to the rest controllers.
Your windows service has added
GeodatabaseService.GeodatabaseAccess
to the ioc container (under the interface IGeodatabase
). So any rest controller can ask the ioc container for an instance of IGeodatabase
, and it will be given the instance which the windows service added to the ioc containerOk I see, but how do I do that in my constructor exactly? I don't seem to be able to access the IGeodatabase
What exactly do you see which makes you think you can't access it? Some compiler/runtime error?
No I'm talking about how I would do this access in code, the way you did it in your code example above?
Add an
IGeodatabase
parameter to the rest controller's constructor
Ok and in my constructor do I not have to set this geoDatabase?
You'll probably want to store it in a field in the class, so that your methods can access it, yes
I assumed that was obvious
Yes it is obvious, I was just confused for some reason
So yeah of course it worked now that I created a field for it
@canton7 Thank you so much for your help
Got it all working? No worries!
Yeah it seems to work. Just so I understand this correctly, do I still need the suggested IDatabaseImplementationProvider? Since now I am accessing that IGeodatabaseproperty through the IOC container instead of through the Service class itself
do I still need the suggested IDatabaseImplementationProvider?Nope -- that was based on my bad assumption about what you were doing before
Yeah I don't need it since it's still the exact same GeodatabaseAccess property I registered, got it
Sorry for not being clearer about that!
No problem! Anything else you need help with here, or shall I mark this as resolved?
Actually yes, I am getting an error now
services.AddSingleton<IGeodatabase>(GeodatabaseService.GeodatabaseAccess);
In this line
Is
GeodatabaseService.GeodatabaseAccess
null at that point?Yes
It only sets it to an instance of one of the implementations a little later in the StartAsync method of my windows service (GeodatabaseService). So should I just give it a default implementation right away?
It needs to be the proper one, not just a placeholder
Can I not just declare it as one of my 3 implementations and then overwrite it after it read from the config which implementation it should actually use?
No
Once you register an instance, it's registered
Oh, so it needs to know the implementation the moment it's registered already?
If you register an actual instance, yes
You can just register the interface type and impl type instead
Right, but I didn't, did I. I registered the interface
You could register it as a factory which fetches the instance when it's needed
Or even register a factory service instead
No
You passed in an object (null)
Registration is this line correct
services.AddSingleton<IGeodatabase>(GeodatabaseService.GeodatabaseAccess);
Yes
So the issue is that it needs an instance for registration?
It doesn't need one
It's an option
But if it doesn't get one it's just null
No
Ok then I'm confused
You should read the docs.
You can register an interface type and impl type
@phaseshift Did you read the scrollback? I wonder whether you're missing the context
You can also just register the instance
There's multiple ways to do it
Yeah, I know they're creating it else where
@.tree You should be able to use this overload: https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollectionserviceextensions.addsingleton?view=dotnet-plat-ext-7.0#microsoft-extensions-dependencyinjection-servicecollectionserviceextensions-addsingleton-1(microsoft-extensions-dependencyinjection-iservicecollection-system-func((system-iserviceprovider-0))) . That will delay fetching the instance until it's requested for the first time
ServiceCollectionServiceExtensions.AddSingleton Method (Microsoft.E...
Adds a singleton service of the type specified in serviceType to the specified IServiceCollection.
But they're still asking general questions and making wrong assertions about what they think they're doing
services.AddSingleton<IGeodatabase>(s => GeodatabaseService.GeodatabaseAccess)
That seems to work for now. I will read up a bit on the matter, I don't think I really understand what I'm doing and why
(Put a breakpoint on that lambda to see when it's called)
Was this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.