4

I have a bunch of services that get registered as singletons by my IoC container on startup, all of these services have a constructor which takes in a connection string which is used by the underlying storage provider.

This has been fine given the connection string was static and never changed during a session, however, I now need to implement the ability for users to change the connection string at runtime (e.g. they can choose to point to a local/cloud DB).

Ultimately, what this means is I need to somehow invalidate all my services in the IoC container and as far as I could see my chosen IoC container doesn't appear to provide a means of doing this, so it's down to me to come up with a practical solution.

Now there are many ways I could do this but I am looking for feedback, suggestions (from experience if possible) on what would be considered the optimal solution. Here is a simplified service

public class Service
{
    public Service (string connectionString)
    {
        this.ConnectionString = connectionString;
    }

    // marked as protected as derived services may need access to them
    protected string ConnectionString { get; set; }
    ...
}

And here is how it is currently registered

public static void StartUp(IAppSettings appSettings)
{
    container.Register<IService, Service> (new Service(appSettings.ConnectionString));
    ...
}

The simplest solution, which I personally don't like, is simply to have all my services take a reference to IAppSettings rather than ConnectionString and use a read-only property e.g.

protected string ConnectionString { get { return appSettings.ConnectionString; } }

However, given all that the services need from IAppSettings is the connection string this just seems wrong from a modelling point of view. Keeping with this approach, but improving on it slightly would be to introduce a new interface which exposes only the information needed from IAppSettings e.g.

public interface IDbSettings
{
    string ConnectionString { get; set; }
}

public class AppSettings : IAppSettings, IDbSettings
{
    ...
}
...
container.Register<IService, Service> (new Service(appSettings as IDbSettings));

I say slightly because ultimately I still have the same problem (services are still referencing a large object for one simple property).

The type of approach I would like, and what I feel would be best, would be to extend the current IoC container to introduce an Invalidate method whereby I can simply set the internal instance to null and have it re-initialized when resolved. I see this looking something like

appSettings.ConnectionString = "database=db1";
container.Register<IService, Service>(new Service(appSettings.ConnectionString));
...
var svc = container.Resolve<IService>(); // connects to db1
appSettings.ConnectionString = "database=db2";
...
var svc = container.Resolve<IService>(); // still connects to db1
container.Invalidate<IService>();
var svc = container.Resolve<IService>(); // connects to db2

For me this gives all round benefits

  • You control exactly when you want the changes to take affect
  • It keeps with the Singleton pattern
  • It means the only thing that changes is the IoC container

I would be interested in everyones thoughts, critiques etc.

2

Your second approach is the correct one, I feel.

I say slightly because ultimately I still have the same problem (services are still referencing a large object for one simple property).

The size of the object that your other objects are referring to is not the issue. The size of the interface that they're referring to is the issue. It doesn't matter that the interface is implemented by an object that has 1000 properties on it, if the interface only exposes a single property.

I think your third solution is a really bad solution. It is creating a very leaky extraction. Why should the class that's responsible for changing the connection string care about other classes that use the connection string? Why should the IOC container care about the state of its objects and not simply the management of their lifetimes?

The primary purpose of an IoC container is to manage your object graph. It handles the creation and destruction of your objects for you. It allows you to more easily write code that follows the Dependency Inversion Principle. In this case, the dependency is the connection string. If many of your classes are dependent only on the connection string, then it makes sense that they should be dependent on an interface that only defines a connection string (and any methods/properties related specifically to that connection string). It doesn't matter which class actually implements that interface and provides that functionality (unless that class has significant performance issues).

This is part of the point of the DIP. It allows you to refactor the classes behind the interfaces without having to worry about the classes that use the interfaces. In this case if you created the connection string interface and had everything use that, it would be a cinch to refactor your appSettings class into an AppSettings and ConnectionSettings class behind the scenes without affecting any of the dependent classes.

Following the Dependency Inversion Principle is the best way to go in this case. Create the small interface and have your code dependent on it. The IoC container can then do what it was designed to do.

  • Why do you need the IoC Container to invert your dependencies? Couldn't you simply hand the dependencies to the constructor, directly? – Robert Harvey Apr 8 '15 at 1:46
  • 1
    "It allows you to more easily write code that follows the Dependency Inversion Principle." - I didn't say it was needed, only that it helps. – Stephen Apr 8 '15 at 1:52
  • I would argue from a memory management point of view, the size of the underlying object is an issue. Passing a huge object around because you require access to a single string property seems wasteful to me, regardless if it's interfaced or not. However, in hindsight, the underlying object isn't really bad to the point where it causes performance issues so I reckon I was guilty of a little bit of premature optimization here. – James Apr 8 '15 at 12:53
  • I don't agree that the 3rd suggestion is a "really bad" solution, the class changing the connection string doesn't care about the others listening, the application on the other hand does. When the connection string changes all services referencing the connection string need to "refresh" and as such calling Invalidate would simply destroy the old services meaning when the services where recreated by the IoC they would be pointing to the new connection string and, naturally, reconnect to the new data source.... – James Apr 8 '15 at 13:02
  • ...Using approach 2 means I would need to broadcast an event (or have an event on IDbSettings) that all the services could subscribe to to know when to refresh the connection. The services are lightweight therefore throwing them away & re-creating them is not a major hit where as having to introduce a new interface & notification system seemed more risky and likely to introduce bugs (e.g. forgetting to subscribe/unsubscribe from events). – James Apr 8 '15 at 13:08
1

Is the construction of your services expensive? What about tear down?

If your objects are singletons and responsible for building and holding a connection to the database for their lifetime, how do you handle transactions or concurrent requests to the database?

I think a better solution would be to put your database connectivity into a transient life IDBService and give your services a transient life with a dependency on the IDBService.

  • The construction of a service is very light (nothing gets initialised when they are created), internally they deal with repositories that handle the lifespan of the DB context, the services themselves are really just a BL facade that sits over them. At the moment, transactions are all handled as part of a single service call (i.e. transactions don't span across multiple services). As for concurrent requests, well that's something I haven't had to worry about because the app was designed as a standalone DB (i.e. single-user interaction). However, that is also changing... – James Feb 6 '15 at 22:34
0

I am not familiar with TinyIoC, but assuming you are caching or you can cache registered services in some kind of container, such as map/dictionary, you could set the key as the hash from the connectionString and the value as the concrete Service.

You could have a Service getService(string hash) method which would return the service based on the hash passed.

Service connection = IoCContrainer.getService(appSettings.ConnectionString.hashCode());
// use the connection

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.