February 2009
Volume 24 Number 02
Patterns in Practice - Convention Over Configuration
By Jeremy Miller | February 2009
Contents
The Impact of Language Innovation
Say It Once and Only Once
Sensible Defaults
Convention over Configuration
Next Steps
Have you ever thought about how much time on your project is spent on the core problems versus the time you spend wrestling with purely technical concerns? Some may say that the most important goal of all new software technologies and techniques is to reduce the distance between the developer's intent for the software and the realization of that intent in code. And the industry has continuously raised the level of abstraction to allow developers to spend more time delivering functionality and less time writing low-level infrastructure—but there's still a long way to go.
Think about this for a minute. If you were to show your code to your business representatives—assuming that they were actually willing to read the code with you—how much of that code would they care about? Those business representatives will probably only care about the code that expresses the business functionality of the system. That code is the "essence" of the system. On the other hand, they probably wouldn't have the slightest interest in code such as type declarations, configuration settings, try/catch blocks, and generic constraints. That code is the infrastructure, or "ceremony," that you, the developer, have to go through just to ship code.
In previous installments of this column I've largely explored fundamental design concepts and principles, most of which have been part of the design canon for quite some time. This time around, I'd like to look at some newer techniques that you can adopt to reduce the amount of ceremony in your code. Some of these concepts may be new to you, but I believe that all of this will be part of the .NET mainstream within a few years.
The Impact of Language Innovation
The first factor I'd like to consider is the choice of programming language and how you use programming languages. To illustrate the impact of a programming language on the amount of ceremony in the code, let's take a little detour into the musty pages of history.
Earlier in this decade I was building a large system in Visual Basic 6.0. Each method would look something like what you see in Figure 1. Every single bit of that code was ceremony.
Figure 1 Standing on Ceremony
Sub FileOperations() On Error Goto ErrorHandler Dim A as AType Dim B as AnotherType ' Put some code here Set A = Nothing Set B = Nothing ErrorHandler: Err.Raise Err.Number, _ "FileOperations" & vbNewLine & Err.Source, _ Err.Description, _ Err.HelpFile, _ Err.HelpContext Exit Sub
I used a fair amount of boilerplate on each method to create an equivalent of a stack trace for easier debugging. I also had to dereference variables inside the method (Set A = Nothing) to release those objects. I had a strict standard for code reviews just to verify that the error handling and object cleanup was coded correctly on each method. The actual code, the essence of the system, floated somewhere in the middle of all that ceremony.
Flash forward to today and contemporary programming languages such as C# or Visual Basic .NET. Today, garbage collection eliminated most of the explicit memory cleanup that you used to face in Visual Basic 6.0 programming. Stack traces in exceptions are built into the Microsoft .NET Framework itself, so you no longer have to do that for yourself. If you think about all the rote boilerplate code that was eliminated when you moved to the .NET Framework, you can say that the .NET-compliant languages are more productive and readable than Visual Basic 6.0 because of the reduction in ceremony code.
The .NET Framework was a big jump, but the evolution in languages isn't finished yet. Let's consider a simple property in C# implemented the classical way:
public class ClassWithProperty { // Higher Ceremony private string _name; public string Name { get { return _name; } set { _name = value; } } }
The essence of this code is simply that the class ClassWithProperty has a string property named Name. Let's fast forward to C# 3.0 and do the same thing with an auto property:
public class ClassWithProperty { // Lower Ceremony public string Name { get; set; } }
This code has the exact same intent as the classic style property, but it required noticeably less "compiler noise" code.
Generally, though, software developers do not have absolute control over programming languages. While I definitely think that we should take advantage of new programming language innovations or even alternative languages, it's time to talk about design ideas that you can use today with mainstream C# and Visual Basic.
Domain-Centric Validation
The .NET Framework makes it nearly trivial to add declarative field-level validation in the user interface with tools such as the ASP.NET Validator controls. All the same, I think it's advantageous to place validation logic on the actual domain model classes, or at least close by in domain services, for these reasons:
- Validation logic is a business logic concern, and I'd prefer that all business logic be contained in the business logic classes.
- Putting the validation logic into the domain model or domain services disconnected from the user interface potentially reduces duplication across screens and allows the same validation logic to be exercised in non-user interface services (Web services, for example) exposed by the application (Saying It Once and Only Once, yet again).
- It is far easier to write unit and acceptance tests against validation logic in the model than it is to test that same logic implemented as part of the user interface.
Say It Once and Only Once
What happens when you find out midway through a project that something about the definition of a single data field needs to be changed? In far too many cases, that small change to the database will ripple through your application as you make an analogous change to various parts of the middle tier, data access code, and even into the user interface layer in order to accommodate a single logical change.
At a past employer, we called that rippling effect from small data field changes the Wormhole Anti-Pattern. You want to get away from the need for a small change in one place being teleported all through the layers. You can slow down the wormhole effect by reducing unnecessary layering, and that's the first step. The harder but more rewarding step is to find a way to say it once and only once.
The Say It Once and Only Once Principle states that there should only be a single authoritative source for any fact or policy in the system as a whole. Let's take the example of building a Web application with create, read, update, and delete (CRUD) functionality. This kind of system is largely concerned with editing and storing data fields. These fields need to be edited in screens, validated on the server, stored in the database, and hopefully validated on the client as well for a better user experience—but you'd like to specify that "this field is required and/or this field must be no more than 50 characters" in the code only once.
There are a couple of different approaches you could take. You could make the database schema the master definition and generate both middle-tier and user-presentation code from the schema. You could also define the data fields in some sort of external metadata storage such as an XML file, then use code generation to build the database schema, any middle-tier objects, and the user interface screens. Personally, I'm not a fan of large-scale code generation, so my team chose another direction.
We generally design the domain model classes first, and we consider the validation logic to be the responsibility of the domain model entity classes. For simple validation rules such as required fields and maximum string length rules, we decorate properties with validation attributes such as the Address class shown in Figure 2.
Figure 2 Using Validation Attributes
public class Address : DomainEntity { [Required, MaximumStringLength(250)] public string Address1 { get; set; } [Required, MaximumStringLength(250)] public string City { get; set; } [Required] public string StateOrProvince { get; set; } [Required, MaximumStringLength(100)] public string Country { get; set; } [Required, MaximumStringLength(50)] public string PostalCode { get; set; } public string TimeZone { get; set; } }
Using attributes is a simple and fairly common technique in order to specify validation rules. You are also able to say that since the validation rules are expressed declaratively instead of implemented with imperative code, you have passed the essence-versus-ceremony test.
Now, though, you need to replicate the rules for required fields and maximum string lengths to the database. In my team's case, we use NHibernate for our persistence mechanism. One of the powerful capabilities of NHibernate is to generate data definition language (DDL) code from the NHibernate mappings that you can then use to create the database schema and keep it in sync with the domain model (this strategy obviously works best in new projects). In order for this strategy of generating the database from the domain model to be useful, it was necessary for us to add additional information to the NHibernate mappings to mark non-null fields and specify string lengths.
We used the new Fluent NHibernate mechanism to define our object mappings. In our setup code for Fluent NHibernate, we established automatic conventions in the mapping by teaching Fluent NHibernate how to handle the presence of the [Required] and [MaximumStringLength] attributes in our model classes with the code shown in Figure 3.
Figure 3 Handling Attributes in NHibernate
public class MyPersistenceModel : PersistenceModel { public MyPersistenceModel() { // If a property is marked with the [Required] // attribute, make the corresponding column in // the database "NOT NULL" Conventions.ForAttribute<RequiredAttribute>((att, prop) => { if (prop.ParentIsRequired) { prop.SetAttribute("not-null", "true"); } }); // Uses the value from the [MaximumStringLength] // attribute on a property to set the length of // a string column in the database Conventions.ForAttribute<MaximumStringLengthAttribute>((att, prop) => { prop.SetAttribute("length", att.Length.ToString()); }); } }
These conventions will now be applied to all mappings in the project. For the Address class, I simply tell Fluent NHibernate which properties are persisted:
public class AddressMap : DomainMap<Address> { public AddressMap() { Map(a => a.Address1); Map(a => a.City); Map(a => a.TimeZone); Map(a => a.StateOrProvince); Map(a => a.Country); Map(a => a.PostalCode); } }
Now that I've taught Fluent NHibernate about the validation attributes, I can generate the DDL for the Address table (see Figure 4). Note in the SQL in Figure 4that the string lengths match the definition from the [MaximumStringLength] attributes on the Address class. Likewise, the NULL / NOT NULL values follow from the [Required] attributes on the Address class.
Figure 4 Generating DDL Code
CREATE TABLE [dbo].[Address]( [id] [bigint] IDENTITY(1,1) NOT NULL, [StateOrProvince] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [Country] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [PostalCode] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [TimeZone] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [Address1] [nvarchar](250) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [Address2] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [City] [nvarchar](250) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
We've invested in some infrastructure that derives the database structure from the validation attributes on the Domain Model objects, but we still have the matter of client-side validation and client-side appearance. We'd like to perform the simple input validations inside the browser and mark the required field elements in some way for a better user experience.
The solution that my team arrived at was to make the HTML rendering of input elements aware of the validation attributes. (For context, we're using the ASP.NET Model View Controller, or MVC, Framework Beta1 with the Web Forms engine as our view engine.) Figure 5shows what the markup might look like for an Address View in our system.
Figure 5 Address View Markup
<div class="formlayout_body"> <p><%= this.TextBoxFor(m => m.Address1).Width(300)%></p> <p><%= this.TextBoxFor(m => m.Address2).Width(300) %></p> <p> <%= this.TextBoxFor(m => m.City).Width(140) %> <%= this.DropDownListFor(m => m.StateOrProvince).FillWith(m => m.StateOrProvinceList) %> </p> <p><%= this.TextBoxFor(m => m.PostalCode).Width(80) %></p> <p><%= this.DropDownListFor(m => m.Country).FillWith(m => m.CountryList) %></p> <p><%= this.DropDownListFor(m => m.TimeZone).FillWith(m => m.DovetailTimeZoneList) %></p> </div>
TextBoxFor and DropDownListFor are little HTML helpers in the common base view that are used for all views in our MVC architecture. The signature of TextBoxFor is shown here:
public static TextBoxExpression<TModel> TextBoxFor< TModel >( this IViewWithModel< TModel > viewPage, Expression<Func< TModel, object>> expression) where TModel : class { return new TextBoxExpression< TModel >( viewPage.Model, expression); }
The important thing to note in this code is that the input argument is an Expression (Expression<Func<TModel, object>> to be precise). While constructing the actual HTML for the textbox, the TextBoxExpression class will:
- Parse the Expression and find the exact PropertyInfo object for the bound property.
- Interrogate that PropertyInfo for the existence of the validation attributes.
- Render the HTML for the textbox accordingly.
We simply added a class called required to all HTML elements that are bound to properties marked with the [Required] attribute. Likewise, if we found a [MaximumStringAttribute] on a bound property, we set the maxlength attribute of the HTML textbox to match the attribute and limit the user input to allowable lengths. The resulting HTML looks something like this:
<p><label>Address:</label> <input type="text" name="Address1" value="" maxlength="250" style="width: 300px;" class="required textinput" /></p>
The appearance of required fields is easy to control simply by editing the appearance of the CSS class required (we set the color of required fields on the screen to a light blue). We did the actual client-side validation with the jQuery Validation plug-in, which, conveniently enough, simply looks for the existence of the required class on input elements. The maximum length of a text element is enforced simply by setting the maxlength attribute on the input elements.
By no means was this a complete implementation. Building the actual implementation over time wasn't that difficult. The hard part was thinking through ways to eliminate repetitive metadata and coding in the multiple layers. I'm sure that many teams will not be keen on the way that my team generated the database from the object model, but that's OK because my real goal is just to give you some ideas about how you might use the Say it Once and Only Once Principal to streamline your own development efforts.
Sensible Defaults
In the previous section, I showed a very small example (via an AddressMap class) of expressing object relation mapping (ORM) with Fluent NHibernate. Here's a slightly more complicated example that expresses a reference from a Site class to Address objects:
public SiteMap() { // Map the simple properties // The Site object has an Address property called PrimaryAddress // The code below sets up the mapping for the reference between // Site and the Address class References(s => s.PrimaryAddress).Cascade.All(); References(s => s.BillToAddress).Cascade.All(); References(s => s.ShipToAddress).Cascade.All(); }
When you configure ORM tools, you generally have to:
- Specify the table name to which an Entity class maps.
- Specify the primary key field of an Entity and usually also specify some sort of strategy for assigning the primary key values. (Is it an auto number/database sequence? Or does the system assign the primary key values? Or do you use GUIDs for the primary key?)
- Map object properties or fields to table columns.
- When making a "to one" relationship from one object to another (such as the mapping from Site to Address), you need to specify the foreign key column that the ORM tool can use to join parent and child records.
All of that is generally tedious work. It's ceremony that you have to follow to get the ORM to persist objects. Fortunately, you can eliminate some of the tedium by embedding some Sensible Defaults in our code (note the presence of capital letters).
You might notice in the mapping examples that I did not specify a table name, a primary key strategy, or any foreign key field names. In my team's Fluent NHibernate mapping superclass, we set some defaults for our mapping:
public abstract class DomainMap<T> : ClassMap<T>, IDomainMap where T : DomainEntity { protected DomainMap() { // For every DomainEntity class, use the Id property // as the Primary Key / Object Identifier // and use an Identity column in SQL Server, // or an Oracle Sequence UseIdentityForKey(x => x.Id, "id"); WithTable(typeof(T).Name); } }
Opinionated Software
You might noticethat I described the adoption of conventions as "restrictive." Part of the philosophy behind convention over configuration is to make "opinionated software" that creates artificial constraints on the design.
An opinionated framework expects developers to do things in a certain way almost to the point of eliminating flexibility. Proponents of opinionated software feel that these constraints make development more efficient by removing decisions from the developers and promoting consistency.
One opinion used by my team is that all domain model classes are completely identified by a single long property called Id:
public virtual long Id { get; set; }
It's a simple rule, but it has had a number of profound impacts on design. Because all entity classes are identified in the same way, you have been able to use a single repository class instead of writing specialized repository classes for each top-level entity. In the same vein, URL handling in a Web application is consistent across entity classes without your having to register special routing rules for each entity.
Following this opinion reduces the cost of infrastructure for adding a new entity type. The downside of this approach is that it would be very difficult to accommodate a natural key or even a composite key or to use a GUID for an object identifier. That's not an issue for my team, but it could easily block another team from adopting our opinion.
Now, how do you enforce these opinions? The first step is simply creating a common understanding and agreement within a team about these opinions. Informed developers stand the best chance of effectively using these opinionated design choices to their advantage. Likewise, convention over configuration can be a near disaster if the developers aren't familiar with the existing conventions or the conventions are confusing.
You might also consider using a static code analysis tool as part of your continuous integration build to automatically enforce your project's conventions.
In this code we are setting a policy that all classes that subclass DomainEntity will be identified by the Id property that is assigned with the identity strategy. The table name is assumed to be the same as the name of the class. Now, we can always override these choices on a per-class basis, but we've rarely had to do this (a class named User had to be mapped to a table named Users just to avoid a conflict with a reserved word in SQL Server). In the same way, Fluent NHibernate assumes a foreign key name based on the property name that references another class.
Granted, this isn't saving many lines of code per mapping class, but it makes the parts of the mapping that do vary easier to read by reducing the overall noise code in the mapping.
Convention over Configuration
Software developers have sought to gain more productivity and make systems more dynamic by moving behavior out of imperative code and into declarative XML configuration. Many developers felt that the proliferation of XML configuration went too far and was becoming a harmful practice. The strategy of defaults over explicit configuration is also known as convention over configuration.
Convention over configuration is a design philosophy and technique that seeks to apply defaults that can be implied from the structure of the code instead of requiring explicit code. The idea is to simplify development by allowing the developer to worry only about the unconventional parts of the application and architecture.
Right now, many people are eagerly playing with the ASP.NET MVC Framework and experimenting with different ways to use it. In the MVC model of Web development there are a couple of sources of repetitive code that might be a great opportunity to apply conventions over configuration.
Here are the five steps in the basic flow of a single request in the MVC model:
- Receive a URL from the client. The routing subsystem will parse the URL and determine the name of the controller that handles this URL.
- From the controller name determined by the routing subsystem, build or locate the proper controller object.
- Invoke the correct controller method.
- Select the proper view, and marshal the model data generated from the controller method to this view.
- Render the view.
Out of the box, there is some repetitive ceremony in building Web pages with the ASP.NET MVC Framework that you can mitigate by adopting some restrictive conventions.
The first task is to connect an incoming URL to the Web site with the proper controller class. The Routing library in the MVC Framework can interrogate a URL and determine the name of the controller. The MVC Framework will then ask the registered IControllerFactory object for the controller object that matches the controller that matches the controller name determined from the incoming URL.
Many teams simply delegate controller construction to an inversion of control (IOC) tool. In my team's case, we use the open source StructureMap tool for resolving controller instances by name:
public class StructureMapControllerFactory : IControllerFactory { public IController CreateController( RequestContext requestContext, string controllerName) { // Requests the named Controller from the // StructureMap container return ObjectFactory.GetNamedInstance<IController>( controllerName.ToLowerInvariant()); } }
Requesting the controller is fairly simple, but first you need to register all of the controller classes by name with the IOC container. Wait! Isn't that adding some ceremony to the architecture? A year or two ago I would have plunged ahead with explicit IOC configuration of the controller classes like this:
public static class ExplicitRegistration { public static void BootstrapContainer() { ObjectFactory.Initialize(x => { x.ForRequestedType<IController>().AddInstances(y => { y.OfConcreteType<AddressController>().WithName("address"); y.OfConcreteType<ContactController>().WithName("contact"); // and so on for every possible type of Controller }); }); } }
This code represents pure tedium and ceremony that exists for no other reason than to feed the IOC tool. If you look closer at the registration code, you'll notice that it's following a consistent pattern. AddressController is registered as address and ContactController is registered as contact. Instead of explicitly configuring each controller, you could simply create a convention for automatically determining the routing name of each controller class.
Fortunately, there's direct support in StructureMap for convention-based registration, so you can create a new ControllerConvention that automatically registers any concrete type of IController:
public class ControllerConvention : TypeRules, ITypeScanner { public void Process(Type type, PluginGraph graph) { if (CanBeCast(typeof (IController), type)) { string name = type.Name.Replace("Controller", "").ToLower(); graph.AddType(typeof(IController), type, name); } } }
Next, you need some code that bootstraps the StructureMap container with the new convention, as shown in Figure 6. Once the new ControllerConvention is in place and part of the IOC container bootstrapping, any new controller classes that you add to the application are automatically added to the IOC registration without any explicit configuration on the part of the developer. So there are no more errors and bugs because a developer forgot to add new configuration elements for new screens.
Figure 6 New Conventions for StructureMap
/// <summary> /// This code would be in the same assembly as /// the controller classes and would be executed /// in the Application_Start() method of your /// Web application /// </summary> public static class SampleBootstrapper { public static void BootstrapContainer() { ObjectFactory.Initialize(x => { // Directs StructureMap to perform auto registration // on all the Types in this assembly // with the ControllerConvention x.Scan(scanner => { scanner.TheCallingAssembly(); scanner.With<ControllerConvention>(); scanner.WithDefaultConventions(); }); }); } }
As a side note, I want to explain that this strategy of auto registration is possible in all of the IOC containers that I'm aware of for the .NET Framework as long as the IOC container exposes a programmatic registration API.
Next Steps
In the end, it's all about reducing the distance and friction between your intention and the code that makes that intention happen. A lot of the techniques I showed in this column are really about letting the code "just figure it out" from using naming conventions instead of explicit code or finding ways to avoid duplicating information in the system. I also used some reflective techniques to reuse information buried in attributes to reduce the mechanical effort.
All of these design ideas can reduce the repetitive ceremony of the development effort, but it comes at a price. Detractors of convention over configuration complain about the inherent "magic" of the approach. Embedding opinions into your code or framework will detract from potential reuse in new scenarios where those opinions aren't as favorable.
There were a lot of other topics that I was unable to cover this time that I may cover in a later column. I definitely want to explore how language-oriented programming; alternative languages such as F#, IronRuby, and IronPython; and internal domain-specific language usage affects the software design process.
Send your questions and comments to mmpatt@microsoft.com.
Jeremy Miller, a Microsoft MVP for C#, is also the author of the open source StructureMaptool for Dependency Injection with .NET and the forthcoming StoryTeller toolfor supercharged FIT testing in .NET. Visit his blog, The Shade Tree Developer.