views:

41

answers:

1

Just wanted to get the groups thoughts on how to handle configuration details of entities.

What I'm thinking of specifically is high level settings which might be admin-changed. the sort of thing that you might store in the app or web.config ultimately, but from teh DDD perspective should be set somewhere in the objects explicitly.

For sake of argument, let's take as an example a web-based CMS or blog app.

A given blog Entry entity has any number of instance settings like Author, Content, etc.

But you also might want to set (for example) default Description or Keywords that all entries in the site should start with if they're not changed by the author. Sure, you could just make those constants in the class, but then the site owner couldn't change the defaults.

So my thoughts are as follows:

1) use class-level (static) properties to represent those settings, and then set them when the app starts up, either setting them from the DB or from the web.config.

or

2) use a separate entity for holding the settings, possibly a dictionary, either use it directly or have it be a member of the Entry class

What strikes you all as the most easy / flexible? My concerns abou the first one is that it doesn't strike me as very pluggable (if I end up wanting to add more features) as changing an entity's class methods would make me change the app itself as well (which feels like an OCP violation). The second one feels like it's more heavy, though, especially if I then have to cast or parse values out of a dictionary.

A: 

I would say that that whether a value is configurable or not is irrelevant from the Domain Model's perspective - what matters is that is is externally defined.

Let's say that you have a class that must have a Name. If the Name is always required, it must be encapsulated as an invariant irrespective of the source of the value. Here's a C# example:

public class MyClass
{
    private string name;

    public MyClass(string name)
    {
        if(name == null)
        {
            throw new ArgumentNullException("name");
        }

        this.name = name;
    }

    public string Name
    {
        get { return this.name; }
        set
        {
            if(value == null)
            {
                throw new ArgumentNullException("name");
            }
            this.name = value;
        }
    }
}

A class like this effectively protects the invariant: Name must not be null. Domain Models must encapsulate invariants like this without any regard to which consumer will be using them - otherwise, they would not meet the goal of Supple Design.

But you asked about default values. If you have a good default value for Name, then how do you communicate that default value to MyClass.

This is where Factories come in handy. You simply separate the construction of your objects from their implementation. This is often a good idea in any case. Whether you choose an Abstract Factory or Builder implementation is less important, but Abstract Factory is a good default choice.

In the case of MyClass, we could define the IMyClassFactory interface:

public interface IMyClassFactory
{
    MyClass Create();
}

Now you can define an implementation that pulls the name from a config file:

public ConfigurationBasedMyClassFactory : IMyClassFactory
{
    public MyClass Create()
    {
        var name = ConfigurationManager.AppSettings["MyName"];
        return new MyClass(name);
    }
}

Make sure that code that needs instances of MyClass use IMyClassFactory to create it instead of new'ing it up manually.

Mark Seemann
Default values were an example, perhaps a poor one; I am familiar with the Factory pattern.Another example would be something that should apply to all instances of a class, but which can change at the app level. Perhaps something like the number of instances to load into a list (always) or a rule about whether or not to show comments. These don't really strike me as instance-level values, or am I misunderstanding something?
Paul
You could always encapsulate one or more related values into a type and then inject an instance of that type into all consumers. For efficieny reasons (or whatever other reasons) you can choose to inject a single, shared instance while the consumers have no idea about the lifetime of the injected object. That'll keep your options open.
Mark Seemann