Some years ago a phrase broke my mind:
“Program to an interface and not to an implementation”
As you probably know the phrase is one of the famous Gang of Four. Is the phrase valid even for entities ?
I definitive think that the answer is yes for various reason; from dyn-proxy, for lazy-loading, to the use of DTO in View.
If we want use GoF suggestion, do we must write a lot of “artifacts” ?
Let me make an example… (fasten your seat belts)
First of all I start defining a simple entity:
public interface IPerson
{
int Id { get; set; }
string Name { get; set; }
}
Now I want persist it using NHibernate2.1.0 (the next mayor version) but without write a real implementation. What I want is write a generic implementation for the whole domain; to do it I’m going to use Castle.DynamicProxy. The responsibility of my proxy will be hold entity-data so basically I need two things: the DataProxy and a “proxy marker” to discover if an object-instance is an instance of my proxy.
public interface IProxyMarker
{
DataProxyHandler DataHandler { get; }
}
public sealed class DataProxyHandler : IInterceptor
{
private readonly Dictionary<string, object> data = new Dictionary<string, object>(50);
private readonly string entityName;
public DataProxyHandler(string entityName, object id)
{
this.entityName = entityName;
data["Id"] = id;
}
public string EntityName
{
get { return entityName; }
}
public IDictionary<string, object> Data
{
get { return data; }
}
#region IInterceptor Members
public void Intercept(IInvocation invocation)
{
invocation.ReturnValue = null;
string methodName = invocation.Method.Name;
if ("get_DataHandler".Equals(methodName))
{
invocation.ReturnValue = this;
}
else if (methodName.StartsWith("set_"))
{
string propertyName = methodName.Substring(4);
data[propertyName] = invocation.Arguments[0];
}
else if (methodName.StartsWith("get_"))
{
string propertyName = methodName.Substring(4);
object value;
data.TryGetValue(propertyName, out value);
invocation.ReturnValue = value;
}
else if ("ToString".Equals(methodName))
{
invocation.ReturnValue = entityName + "#" + data["Id"];
}
else if ("GetHashCode".Equals(methodName))
{
invocation.ReturnValue = GetHashCode();
}
}
#endregion
}
Note: the IInterceptor is Castle.Core.Interceptor.IInterceptor.
Now I want persist the IPerson but before write the mapping I need some more artifacts related to some new features of NH2.1. I’m going to use Tuplizers.
The IInstantiator implementation:
public class EntityInstantiator : IInstantiator
{
private static readonly ProxyGenerator proxyGenerator = new ProxyGenerator();
private readonly Type t;
public EntityInstantiator(Type entityType)
{
t = entityType;
}
public object Instantiate(object id)
{
return
proxyGenerator.CreateInterfaceProxyWithoutTarget(t, new[] {typeof (IProxyMarker), t},
new DataProxyHandler(t.FullName, id));
}
public object Instantiate()
{
return Instantiate(null);
}
public bool IsInstance(object obj)
{
try
{
return t.IsInstanceOfType(obj);
}
catch (Exception e)
{
throw new Exception("could not get handle to entity-name as interface : " + e);
}
}
}
Note: the ProxyGenerator is Castle.DynamicProxy.ProxyGenerator.
The override of PocoEntityTuplizer:
public class EntityTuplizer : PocoEntityTuplizer
{
public EntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity)
: base(entityMetamodel, mappedEntity) {}
protected override IInstantiator BuildInstantiator(PersistentClass persistentClass)
{
return new EntityInstantiator(persistentClass.MappedClass);
}
}
Has you know when we create a proxy the System.Type will take a value that nobody can predict; to allow NH recognize my entity I use a simple NHibernate.IInterceptor implementation:
public class EntityNameInterceptor : EmptyInterceptor
{
public override string GetEntityName(object entity)
{
string entityName = ExtractEntityName(entity) ?? base.GetEntityName(entity);
return entityName;
}
private static string ExtractEntityName(object entity)
{
// Our custom Proxy instances actually bundle
// their appropriate entity name, so we simply extract it from there
// if this represents one of our proxies; otherwise, we return null
var pm = entity as IProxyMarker;
if (pm != null)
{
var myHandler = pm.DataHandler;
return myHandler.EntityName;
}
return null;
}
}
Because I will need to have a instance of “something” in my application I need a sort of EntityFactory. Here a quick implementation:
public class EntityFactory
{
private static readonly ProxyGenerator proxyGenerator = new ProxyGenerator();
public T NewEntity<T>()
{
Type t = typeof (T);
return
(T)
proxyGenerator.CreateInterfaceProxyWithoutTarget(t, new[] {typeof (IProxyMarker), t},
new DataProxyHandler(t.FullName, 0));
}
}
Before write a persistence test I need the last little thing: the mapping.
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="LessThanFew"
namespace="LessThanFew.Domain">
<class name="IPerson">
<tuplizer class="LessThanFew.EntityTuplizer, LessThanFew" entity-mode="poco"/>
<id name="Id">
<generator class="hilo"/>
</id>
<discriminator type="string" force="false"/>
<property name="Name"/>
</class>
</hibernate-mapping>
Now I can write my test…
[Test]
public void PersonCrud()
{
object savedId;
var person = entityFactory.NewEntity<IPerson>();
person.Name = "Katsumoto";
using (var session = sessions.OpenSession())
using (var tx = session.BeginTransaction())
{
savedId = session.Save(person);
tx.Commit();
}
using (var session = sessions.OpenSession())
using (var tx = session.BeginTransaction())
{
person = session.Get<IPerson>(savedId);
Assert.That(person, Is.Not.Null);
Assert.That(person.Name, Is.EqualTo("Katsumoto"));
session.Delete(person);
tx.Commit();
}
using (var session = sessions.OpenSession())
using (var tx = session.BeginTransaction())
{
person = session.Get<IPerson>(savedId);
Assert.That(person, Is.Null);
tx.Commit();
}
}
The test work but… to persist my little entity I write 6 more classes than usual… yes, is true, but now I have an AOP approach of my application even for entities. Ok let me complicate the domain a little bit:
public interface IPerson
{
int Id { get; set; }
string Name { get; set; }
IAddress Address { get; set; }
ISet<IPerson> Family { get; set; }
}
public interface ICustomer : IPerson
{
ICompany Company { get; set; }
}
public interface ICompany
{
int Id { get; set; }
string Name { get; set; }
}
public interface IAddress
{
int Id { get; set; }
string Street { get; set; }
string City { get; set; }
string PostalCode { get; set; }
}
and the new “matrix”:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="LessThanFew"
namespace="LessThanFew.Domain">
<class name="IPerson" discriminator-value="person" abstract="false">
<tuplizer class="LessThanFew.EntityTuplizer, LessThanFew" entity-mode="poco"/>
<id name="Id">
<generator class="hilo"/>
</id>
<discriminator type="string" force="false"/>
<property name="Name"/>
<many-to-one name="Address" cascade="all" column="addr_id"/>
<set name="Family" cascade="all" generic="true">
<key column="pers_id"/>
<one-to-many class="IPerson"/>
</set>
<subclass name="ICustomer" discriminator-value="customer" abstract="false">
<tuplizer class="LessThanFew.EntityTuplizer, LessThanFew" entity-mode="poco"/>
<many-to-one name="Company" cascade="none" column="comp_id"/>
</subclass>
</class>
<class name="ICompany" abstract="false">
<tuplizer class="LessThanFew.EntityTuplizer, LessThanFew" entity-mode="poco"/>
<id name="Id">
<generator class="hilo"/>
</id>
<property name="Name"/>
</class>
<class name="IAddress" abstract="false">
<tuplizer class="LessThanFew.EntityTuplizer, LessThanFew" entity-mode="poco"/>
<id name="Id">
<generator class="hilo"/>
</id>
<property name="Street"/>
<property name="City"/>
<property name="PostalCode"/>
</class>
</hibernate-mapping>
To persist this new domain I don’t need to write nothing more than what you are seeing in the last two snippets and if I need INotifyPropertyChanged for all entities, of my new domain, what I need to change is the implementation of DataProxyHandler, by the way the intention of this post is only to show another NH2.1 feature : Tuplizers.
The code, with the “full cream” test, is available here.
The last little thing… the Italian and Spanish translation of “FEW” is POCO.
That's something that i have not thought of even in my dreams!
ReplyDeletep.s. and looks very promising for some obscure scenario that i have in my mind!
how about if Person implement 2 interface?..Any suggestion how to solve this problem
ReplyDelete