In this post I would like to introduce design pattern which is particularly close to my heart - Convention over Configuration. What I like the most about this pattern is that it eliminates lots of monkey code which we have to write from time to time. Firstly let me explain to you what I mean by monkey code.
Monkey code
Typical case of monkey code can be found in old-fasihion NHibernate mappings:1: <hibernate-mapping>
2: <class name="Product" table="tblProduct">
3: <id name="Id" type="Int32" column="ProductID">
4: <generator class="identity" />
5: </id>
6: <property name="ProductLine" type="String">
7: <column name="ProductLine" length="2" />
8: </property>
9: <property name="Class" type="String">
10: <column name="Class" length="2" />
11: </property>
12: <property name="Size" type="String">
13: <column name="Size" length="5" />
14: </property>
15: <property name="DaysToManufacture" type="Int32">
16: <column name="DaysToManufacture" not-null="true" />
17: </property>
18: <property name="ModifiedDate" type="DateTime">
19: <column name="ModifiedDate" not-null="true" />
20: </property>
21: <property name="ListPrice" type="Int32">
22: <column name="ListPrice" not-null="true" sql-type="money" />
23: </property>
24: </class>
25: </hibernate-mapping>
Even though in most cases column name and property name are the same, we still have to specify them. There is nothing really creative in this code. I think it's general problem for all sorts of mappings, it doesn't really matter if they are in a form of XML or a plain-old-CLR-objects - someone has to write this pesky piece of code. If you multiply time needed to map single column by number of columns and then by number of tables then you will get huge waste of time.
Conventions
I like Fluent NHibernate so much because it uses conventions, for instance I can say that all my tables have the same names as my classes and let fluent-nh map everything for me.
Within one application there might be number of aspects to which conventions can be applied. For instance, in fluent-nh it might be:
- class name vs table name
- property name vs column name
- reference property name vs foreign key column name
Another project using conventions is ASP.NET MVC:
- project structure - there are separate folders for Views, Controllers and Model
- views placement - by default ASP.NET MVC framework will try to find view for given controller and action in folder like this: /Views/controllername/actionname.aspx.
Action name equals view name.
Implementation
Lets try to put this high level ideas into concrete ... how Convention over Configuration can be implemented? I will try to present an algorithm based on fluent-nh and real life scenario where we have to map database tables to classes but all tables in database have "tbl" prefix. Of course we don't want to name our classes with this prefix, also we don't want to specify manually for each class that corresponding table's name is "tbl" + class name.
- In the first step fluent-nh loads classes responsible for "convention discovery". Each class from this group is responsible for single convention, this way it's really simple to add new additional conventions later.
To find all "discovery" classes you can for instance use reflection to get all classes from a given namespace (flunet-nh's way) or all classes implementing some interface. - Then it instantiates all mapping classes, custom conventions and default conventions. Default conventions will be used if there are no custom conventions. Be default class name will be equal to table name, we want to change it therefore we have to provide our own convention. It's important to remember that default convention shouldn't overwrite custom one.
- Next, it executes all "convention discovery" classes. Corresponding methods will get as a parameter all mappings and are responsible for finding and applying specific conventions.
- Any convention (default or custom) shouldn't overwrite explicit configuration!
- Default convention shouldn't overwrite custom conventions!
Code samples
All conventions have to implement this simple interface:
1: public interface IConvention<T> : IConvention
2: {
3: /// <summary>
4: /// Whether this convention will be applied to the target.
5: /// </summary>
6: /// <param name="target">Instace that could be supplied</param>
7: /// <returns>Apply on this target?</returns>
8: bool Accept(T target);
9:
10: /// <summary>
11: /// Apply changes to the target
12: /// </summary>
13: /// <param name="target">Instance to apply changes to</param>
14: void Apply(T target);
15: }
Accept() method allows you to have number of conventions of given type and use them based on your custom logic.
This is how "discovery" is implemented:
1: public void Apply(IEnumerable<IClassMap> classes)
2: {
3: var conventions = conventionFinder.Find<IClassConvention>();
4:
5: foreach (var classMap in classes)
6: {
7: foreach (var classConvention in conventions)
8: {
9: if (classConvention.Accept(classMap))
10: classConvention.Apply(classMap);
11: }
12: }
13: }
Finally, our convention which will add "tbl" prefix for table name:
1: public class TableNameConvention : IClassConvention
2: {
3: public bool Accept(IClassMap target)
4: {
5: return string.IsNullOrEmpty(target.TableName);
6: }
7:
8: public void Apply(IClassMap target)
9: {
10: target.WithTable("tbl" + target.EntityType.Name);
11: }
12: }
And a few interesting pieces of code which I found in fluent-nh sources:
1: private void AddDefaultConventions()
2: {
3: foreach (var foundType in from type in typeof(PersistenceModel).Assembly.GetTypes()
4: where type.Namespace == typeof(TableNameConvention).Namespace && !type.IsAbstract
5: select type)
6: {
7: ConventionFinder.Add(foundType);
8: }
9: }
The above method finds all default conventions. Next method instantiates type using default constructor or the one which takes (in this case) ConventionFinder as a parameter:
1: private object Instantiate(Type type)
2: {
3: object instance = null;
4:
5: foreach (var constructor in type.GetConstructors())
6: {
7: if (IsFinderConstructor(constructor))
8: instance = constructor.Invoke(new[] { this });
9: else if (IsParameterlessConstructor(constructor))
10: instance = constructor.Invoke(new object[] { });
11: }
12:
13: return instance;
14: }
I hope that this post gives you much better idea about Convention over Configuration in general but also explains how to implement this pattern in practice. I encourage you to give it a try instead of writing monkey code.
If you encountered different cases where CoC fits perfectly I would be very interested to hear about it, leave a comment or send me an email. Or maybe you in general disagree ... leave a comment ... it will be interesting to know your view on this.
5 comments:
I'm just at the beginning so only glanced on the article, but to my knowledge you don't need to specify the collumn name... (so no redundancy here)
cowgaR
Amazing & Great informative blog,it gives very useful practical information to developer like me. Besides that Wisen has established as Best Hibernate Training in Chennai . or learn thru Online Training mode Hibernate Online Training . Nowadays Hibernate ORM has tons of job opportunities on various vertical industry.
Will be trying this for myself to see what I get. Thanks for sharing.
buy logo online
this is a great article thanks for sharing it with us...
we design the best websites & logos with a discount also with a guarantee are you interested?
Logo Designers
Thank you for sharing it with us.
I am really happy to see this blog.
It is very helpful for me.
Nursing assignment help
Post a Comment