C
C#2y ago
.tree

❔ 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
phaseshift
phaseshift2y ago
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
.tree
.treeOP2y ago
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
canton7
canton72y ago
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
.tree
.treeOP2y ago
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
canton7
canton72y ago
Sounds like that should belong in the service, then
.tree
.treeOP2y ago
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
canton7
canton72y ago
So, why does the API depend on classA?
.tree
.treeOP2y ago
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
canton7
canton72y ago
I'm assuming that implementation implements an interface which is defined in the library?
.tree
.treeOP2y ago
Yes, exactly Both the interface and its 3 implementations are in the library
canton7
canton72y ago
So, why does the API need to fetch this implementation from a concrete instance of classA specifically?
.tree
.treeOP2y ago
I mean technically it doesn't I guess After all the info comes from the config
canton7
canton72y ago
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?
.tree
.treeOP2y ago
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?
canton7
canton72y ago
So instead of:
class ClassA
{
public IDatabaseProvider DatabaseProvider { get; }
}

class SomeRestApiClass
{
public SomeRestApiClass(ClassA classA) => this.databaseProvider = classA.DatabaseProvider;
}
class ClassA
{
public IDatabaseProvider DatabaseProvider { get; }
}

class SomeRestApiClass
{
public SomeRestApiClass(ClassA classA) => this.databaseProvider = classA.DatabaseProvider;
}
You have:
class ClassA : IDatabaseImplementationProvider
{
public IDatabaseProvider DatabaseProvider { get; }
}

class SomeRestApiClass
{
public SomeRestApiClass(IDatabaseImplementationProvider classA) => this.databaseProvider = classA.DatabaseProvider;
}
class ClassA : IDatabaseImplementationProvider
{
public IDatabaseProvider DatabaseProvider { get; }
}

class SomeRestApiClass
{
public SomeRestApiClass(IDatabaseImplementationProvider classA) => this.databaseProvider = classA.DatabaseProvider;
}
.tree
.treeOP2y ago
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?
canton7
canton72y ago
No, it's very common for types to implement multiple interfaces
.tree
.treeOP2y ago
Wait in this case, whats the difference between IDatabaseImplementationProvider and IDatabaseProvider?
canton7
canton72y ago
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 up
.tree
.treeOP2y ago
Oh 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
canton7
canton72y ago
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
.tree
.treeOP2y ago
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); } }
canton7
canton72y ago
Where is it accessing classA?
.tree
.treeOP2y ago
The GeodatabaseService.GeodatabaseAccess.Login is classA.interfaceobject.method
canton7
canton72y ago
GeodatabaseService.GeodatabaseAccess is a static property?
.tree
.treeOP2y ago
So GeodatabaseAcces in classA is this public static IGeodatabase GeodatabaseAccess { get; set; } And that IGeodatabase interface is the one in my library
canton7
canton72y ago
Oh, 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 statically
.tree
.treeOP2y ago
Which part exactly is bad The static access?
canton7
canton72y ago
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?
.tree
.treeOP2y ago
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 project
canton7
canton72y ago
That's in the windows service?
.tree
.treeOP2y ago
This code is from the program.cs of my windows service
canton7
canton72y ago
Cool, just register the IGeodatabase there
.tree
.treeOP2y ago
private 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...?
canton7
canton72y ago
services.AddSingleton<IGeodatabase>(GeodatabaseService.GeodatabaseAccess); ?
.tree
.treeOP2y ago
What did we achieve by doing this
canton7
canton72y ago
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 service
.tree
.treeOP2y ago
Sorry, how does registering the IGeodatabase in the windows service allow my rest controller to access it?
canton7
canton72y ago
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 container
.tree
.treeOP2y ago
Ok I see, but how do I do that in my constructor exactly? I don't seem to be able to access the IGeodatabase
canton7
canton72y ago
What exactly do you see which makes you think you can't access it? Some compiler/runtime error?
.tree
.treeOP2y ago
No I'm talking about how I would do this access in code, the way you did it in your code example above?
canton7
canton72y ago
Add an IGeodatabase parameter to the rest controller's constructor
public class MyRestController
{
public MyRestController(IGeodatabase geoDatabase) { ... }

[HttpGet]
[Route("Login")]
public string Login(string username, string password, string DBAlias) { ... }
}
public class MyRestController
{
public MyRestController(IGeodatabase geoDatabase) { ... }

[HttpGet]
[Route("Login")]
public string Login(string username, string password, string DBAlias) { ... }
}
.tree
.treeOP2y ago
Ok and in my constructor do I not have to set this geoDatabase?
canton7
canton72y ago
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
.tree
.treeOP2y ago
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
canton7
canton72y ago
Got it all working? No worries!
.tree
.treeOP2y ago
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
canton7
canton72y ago
do I still need the suggested IDatabaseImplementationProvider?
Nope -- that was based on my bad assumption about what you were doing before
.tree
.treeOP2y ago
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!
canton7
canton72y ago
No problem! Anything else you need help with here, or shall I mark this as resolved?
.tree
.treeOP2y ago
Actually yes, I am getting an error now
.tree
.treeOP2y ago
.tree
.treeOP2y ago
services.AddSingleton<IGeodatabase>(GeodatabaseService.GeodatabaseAccess); In this line
canton7
canton72y ago
Is GeodatabaseService.GeodatabaseAccess null at that point?
.tree
.treeOP2y ago
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?
phaseshift
phaseshift2y ago
It needs to be the proper one, not just a placeholder
.tree
.treeOP2y ago
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?
phaseshift
phaseshift2y ago
No Once you register an instance, it's registered
.tree
.treeOP2y ago
Oh, so it needs to know the implementation the moment it's registered already?
phaseshift
phaseshift2y ago
If you register an actual instance, yes You can just register the interface type and impl type instead
.tree
.treeOP2y ago
Right, but I didn't, did I. I registered the interface
canton7
canton72y ago
You could register it as a factory which fetches the instance when it's needed
phaseshift
phaseshift2y ago
Or even register a factory service instead No You passed in an object (null)
.tree
.treeOP2y ago
Registration is this line correct services.AddSingleton<IGeodatabase>(GeodatabaseService.GeodatabaseAccess);
phaseshift
phaseshift2y ago
Yes
.tree
.treeOP2y ago
So the issue is that it needs an instance for registration?
phaseshift
phaseshift2y ago
It doesn't need one It's an option
.tree
.treeOP2y ago
But if it doesn't get one it's just null
phaseshift
phaseshift2y ago
No
.tree
.treeOP2y ago
Ok then I'm confused
phaseshift
phaseshift2y ago
You should read the docs. You can register an interface type and impl type
canton7
canton72y ago
@phaseshift Did you read the scrollback? I wonder whether you're missing the context
phaseshift
phaseshift2y ago
You can also just register the instance There's multiple ways to do it Yeah, I know they're creating it else where
phaseshift
phaseshift2y ago
But they're still asking general questions and making wrong assertions about what they think they're doing
canton7
canton72y ago
services.AddSingleton<IGeodatabase>(s => GeodatabaseService.GeodatabaseAccess)
.tree
.treeOP2y ago
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
canton7
canton72y ago
(Put a breakpoint on that lambda to see when it's called)
Accord
Accord2y ago
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.
Want results from more Discord servers?
Add your server