0

The Situation

I'm building a C# web application and I want to model my app configuration as an explicit dependency to be handed in through the constructor of a service instead of relying on System.Configuration.ConfigurationManager directly in each of the classes. This did bite my quite often in the past so I want the dependency to be explicit so that the next maintainer of the project (probably future me) doesn't have to guess where my services get their configuration settings - on top of that it is more TDD friendly. Furthermore I'm currently reading Eric Evan's Domain Driven Design and I really want to embrace his DDD approach.

I started modeling the configuration class and corresponding value objects to avoid Primitive Obsession but I hit some bumps on the way and I'm not sure how to handle them appropriately. Here is my current approach:

// Role interface that can be requested via constructor injection
interface IAppConnectionStringsConfig
{
    OleDbConnectionString AuthenticationConnectionString { get; }
}

// A base class for handling common functionality like
// parsing comma separated lists or default values
class abstract AppConfigBase
{
    protected string GetStringAppSetting(string key) 
    {
        // Get the appropriate string or a default value from
        // System.Configuration.ConfigurationManager
        return theSettingFromSomeConfigSource;
    }
}

// A value object for OLEDB connection strings that also has a
// convenient implicit conversion to string
class OleDbConnectionString
{
    public readonly string Value;

    public OleDbConnectionString(string connectionString)
    {
        Contract.Requires(connectionString != null);
        this.VerifyStructure(connectionString);
        this.Value = connectionString;
    }

    private void VerifyStructure(string text)
    {
        Contract.Requires(text != null);
        // Verify that the given string fulfills the special
        // needs of an OleDbConnectionString (including Provider=...)
        if (!/* isValidOleDbConnectionString */)
        {
            throw new FormatException();
        }
    }

    public implicit operator string(ConnectionString conn)
    {
        return conn.Value;
    }
}

// The actual app config that implements our role interface
class AppConfig : AppConfigBase, IAppConnectionStringsConfig
{
    public OleDbConnectionString AuthenticationConnectionString 
    { 
        get 
        { 
            return new OleDbConnectionString(this.GetStringAppSetting("authconn")); 
        }
    }
} 

The Problem

I know that constructor logic should be minimal and that is not a good idea to call virtual methods from the constructor. My questions are as follows:

  • 1) Where should I put the validation logic for the OleDbConnectionString? I really want to prevent the creation of value objects in an invalid state - that's excrutiatingly usefull at a day to day basis :-)
    • I have the feeling that this is domain logic that should be owned by the class itself but on the other hand the constructor should do as little as possible - wouldn't the string parsing be too much or is this ok?
    • I could create a validator but I most certainly had to hand that in through the constructor for being able to test that thing properly and then I have to wire that manually or use a factory (I'm definitely not using a Service Locator). On top of that the validation now would be hidden in a separate service; I wouldn't have the temporal coupling since the constructor requires the validator but still that doesn't look right.
  • 2) I wonder if it would be appropriate to make DDD value objects structs? They - like the name suggests - represent a single value and this value is immutable. But they would contain business logic in the form of validation
  • 3) Is it OK to use a property for retrieving the connection string? It could throw an exception if the format for the string isn't valid. Furthermore it's perfectly possible that the implementation will be changed from reading from an xml config file to querying a database.
  • 4) Any other comments on the design are welcome!

As a side note, I'm already using Code Contracts and there is a way to specify object invariants but I don't know whether this is really a good idea since these contracts are opt-in and in the case that they are inactive the invariants are no longer actively protected. I'm not sure about this, for development purposes to catch errors early it might be fine but for production it seems off.

Thx!

mfeineis
  • 2,607
  • 19
  • 22

1 Answers1

0

I never really thought about general settings as a DDD problem - are you modelling a domain that is about settings and how they are saved, or just allowing settings to be saved and used in an application that has some inner parts modeled as DDD?

You can split this out by separating concerns of getting settings away from the things that use the settings.

Is it OK to use a property for retrieving the connection string? It could throw an exception if the format for the string isn't valid.

I don't think its a good idea to throw an exception if a setting cannot be retrieved so you can return defaults which would allow the program to continue.

But also remember that the default returned value (i.e. a password, or network address) will probably cause the thing that depends on that setting to throw an exception.

I would look at allowing the construction to happen OK but when coming to use the service i.e. Sender.Send() or Sender.Connect() is when you would throw an exception.

Where should I put the validation logic for the OleDbConnectionString? I really want to prevent the creation of value objects in an invalid state

I create objects that can never return an invalid result, but they do return a default settings value:

public class ApplicationSettings : IIdentityAppSettings, IEventStoreSettings
{
    /* snip */

    static readonly object KeyLock = new object();

    public byte[] StsSigningKey
    {
        get
        {
            byte[] key = null;

            lock (KeyLock)
            {
                var configManager = WebConfigurationManager.OpenWebConfiguration("/");
                var configElement = configManager.AppSettings.Settings["StsSigningKey"];

                if (configElement == null)
                {
                    key = CryptoRandom.CreateRandomKey(32);
                    configManager.AppSettings.Settings.Add("StsSigningKey", Convert.ToBase64String(key));
                    configManager.Save(ConfigurationSaveMode.Modified); // save to config file
                }
                else
                {
                    key = Convert.FromBase64String(configElement.Value);
                }
            }

            return key;
        }

        /* snip */
    }
}

What I generally do

I have the settings interfaces for each bounded context defined in the domain model as part of the infrastructure - this allows a number of known interfaces which I can reference and trust to provide some form of settings.

ApplicationSettings is defined in the code that hosts my bounded context(s) be it a Console app or WebAPI or MVC etc, I may have multiple bounded contexts hosted under the same process, or may split them out as separate processes, either way it is the job of the hosting application to provide the relevant application settings and wiring can be done via the IoC container.

public class ApplicationSettings : IIdentityAppSettings, IEventStoreSettings
{
    // implement interfaces here
}

public interface IEventStoreSettings
{
    string EventStoreUsername { get; }
    string EventStorePassword { get; }
    string EventStoreAddress { get; }
    int EventStorePort { get; }
}

public interface IIdentityAppSettings
{
    byte[] StsSigningKey { get; }
}

I use SimpleInjector .NET IoC container to wire up my applications. I then register all the application interfaces with SimpleInjector (so i can query based on any of the application interfaces and have the settings class object returned):

resolver.RegisterAsImplementedInterfaces<ApplicationSettings>();

I can then have the specific interface injected in, an example is a command handler that uses an IRepository, which in turn the EventStoreRepository (which is wired up as an implementation of IRepository) uses IEventStoreSettings (which is wired up as the ApplicationSettings instance):

public class HandleUserStats : ICommandHandler<UserStats>
{
    protected IRepository repository;

    public HandleUserStats(IRepository repository)
    {
        this.repository = repository;
    }

    public void Handle(UserStats stats)
    {
        // do something
    }
}

And my repository would in turn be wired up:

public class EventStoreRepository : IRepository
{
    IEventStoreSettings eventStoreSettings;

    public EventStoreRepository(IEventStoreSettings eventStoreSettings)
    {
        this.eventStoreSettings = eventStoreSettings;
    }

    public void Write(object obj)
    {
        // just some mockup code to show how to access setting
        var eventStoreClient = new EventStoreClient(
                                        this.eventStoreSettings.EventStoreUsername,
                                        this.eventStoreSettings.EventStorePassword,
                                        this.eventStoreSettings.EventStoreAddress,
                                        this.eventStoreSettings.Port
                                        ); 

        // if ever there was an exception either during setup of the connection, or
        // exception (if you don't return a default value) accessing settings, it
        // could be caught and bubbled up as an InfrastructureException

        // now do something with the event store! ....
    }
}

I allow settings to be passed in from some external source (like a WCF receive, or MVC controller action) and wired up by getting resolver.GetInstance<CommandHandler<UserStats>>(); which wires up all the settings for me all the way down to the implementation level.

morleyc
  • 2,169
  • 10
  • 48
  • 108
  • This approach works, but in the end the constructor/getter exception is the behavior I want, this blows up as soon as something is wrong and has worked well for my projects for a while now. – mfeineis Apr 21 '17 at 22:49