0

Background

I have a number of services that are implemented by components that depend on a connection string to be created - for instance:

public interface IImportantRepository { ... }

public class ImportantRepository
{
    public ImportantRepository(IOracleConnection connection) { ... }
    public ImportantRepository(string connectionString) { ... } // rarely my constructor of choice, but included for clarity
}

I generally prefer the first constructor to the second because in order to consume a connection string I must create a connection with it, which will in turn require resolution of IOracleConnection with a string dependency.

My IImportantController has a single constructor and parameter of IImportantRepository. My IControllerFactory is the composition root (not counting the bootstrapper) and uses IWindsorContainer.Resolve<T>() at runtime to activate the right controller for the job.

So I suppose the whole thing looks like this at bootstrap-time:

Global.Asax => ControllerBuilder.Current => MyControllerFactory : IControllerFactory

and this at request time:

IControllerFactory => IImportantController => IImportantRepository => IOracleConnection

Motivation

Now, in one application, IOracleConnection (and the underlying string that builds it) may be known when:

  • The application is designed (a fictitious connection string for testing)
  • The application is compiled (a connection string completely determined by a build var)
  • The application is deployed (a connection string set in a web.config transformation)
  • The application is bootstrapped (a connection string set in a configuration source read only once)
  • The application handles a certain type of request (say, "typically" but not "always" you want to display data from ProductionDb)
  • The application handles a request in which the connection is parameterized (a request to manage Important stuff in the ChicagoDb instead of the NewYorkDb, or whatever)
  • The application handles a request in which the session or user details completely determine the connection (10 percent of users live on Db1 and the rest on Db2 by design)

Question

How would you write maintainable code without violating DRY too much that achieves all of these potential uses of the same dependency for the same graph simultaneously?

tacos_tacos_tacos
  • 9,687
  • 10
  • 65
  • 116

1 Answers1

0

Primitives are pretty similar to any other type on which other components might depend and which you might register in your container. They can be named, they can be resolved from some static context (e.g. a config file, an environment variable, or the current users session), etc.

The scenarios you list can all apply to complex types OR primitive types and most of them have clean DRY solutions.

However there are a couple of challenges in dealing with primitives:

  1. I think the one thing that differentiates primitives, from complex types in the container, is the concept of purpose (service identity). A "connection string" is, as far as the container is concerned, simply a string. It is not a connection string or an IP address or a host name - it's just a string.
  2. Putting aside the possibility of custom resolvers, the primary way of wiring primitives to fulfill the dependency requirements of other components is to name the primitive at registration time and then reference that name when registering the dependent component. However, using names imposes a coupling (at least in terms of registration) that is often not necessary.

I therefore prefer to never register or depend on primitives. I always wrap them in an interface (generally ending with '...Settings') like so:

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

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

This relatively simple wrapper now provides the primitive with an indication of it's purpose. It assists when developing components that require these primitives because I can now clearly communicate through the component constructor exactly what type of connection I expect (an MSSQL Repository component simply will not accept a connection string to an Oracle database). It also assists at registration in making it very clear what the dependency is.

In practice, most of the time I offload the construction of these 'Settings' objects to a variant of the excellent AppSettingWrapperAttribute from Krzysztof Koźmic. (Long story short: it provides a strongly typed wrapper around a dictionary of settings from the AppSettings of the config file.) So, I rarely actually ever code an implementation of these classes, let alone instantiate them. However, you can of course do so (e.g. to provide a hard-coded string for tests, or formalise command-line parameters passed to a console app).

Wrapping primitives in an interface that explicitly indicates their purpose also means that in many cases you don't need to use names for registration. E.g. if there is only one 'Production Oracle Database' then any component that requires a IOracleProductionDatabaseConnectionSettings will resolve the correct settings from the container without any magic strings required.

Phil Degenhardt
  • 7,010
  • 3
  • 33
  • 45