The question came up in the ALT.Net list, and it is fairly common in the DDD circles as well.
I have an User with Email address, which I want to keep encrypted in the database, but visible to the application. How do I handle this scenario in the entity?
We can do it in our service layer, and encrypt / decrypt that outside the entity, but that has two issues. It couple the entity to the service and it means that we can forget to encrypt the email at some point.It also smells.
Another option, which I call the brute force option, is to pass the dependency to the entity and let it handle its own concerns. Here is implementation:
Yes, it is ugly, and even using this approach you can improve upon it, but let us focus on the interesting tidbits. First, not only does our entity know about an infrastructure service, it is also aware of how to create it. It has to do so, because we have to provide an empty ctor for NHibernate to use.
We can generalize this a bit, leading us to this scenario:
This piece of code is akin to nails on board to me. It isn't how I want to see code written.
A better way exists. We will start by making a slight modification to the User class, we remove the empty ctor. Well, we can't remove it at the moment, there is check in NHibernate for this, which I need to find and remove, so for now, we have this:
Note that this also requires that you'll specify the unsaved value of the identifier in the configuration.
Now that we have established that we have no way of creating the User using the empty ctor, we let NHibernate know how it can create it:
public class EntityDependenciesInterceptor : EmptyInterceptor
{
public override object Instantiate(string clazz, EntityMode entityMode, object id)
{
if(entityMode!=EntityMode.Poco)
return base.Instantiate(clazz, entityMode, id);
if(clazz != typeof(User).FullName)
return base.Instantiate(clazz, entityMode, id);
return new User(CryptoProvider.Instance);
}
}
Just for User, we override the way NHibernate create entities. Now we can register it in the session, and we are done:
using (ISession session = sessionFactory.OpenSession(new EntityDependenciesInterceptor()))
{
IList list = session.CreateCriteria(typeof(User)).List();
}
This is still not something that I particularly like. I don't see encryption as part of the responsibilities of the entity. It is just something we need in order to save encrypted values to the DB. So let make it explicit.
NHibernate has the notion of IUserType, which let you take actions on save / load of values from the DB.
First, we make User POCO again, remove all traces of the previous ugliness. Now, we change the mapping for User so the email property is defined so:
<property name="Email" type="Samples.EncryptedStringUserType, Samples" />
And now we need to create the user type:
public class EncryptedStringUserType : IUserType
{
public bool Equals(object x, object y)
{
return object.Equals(x, y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
object r = rs[names[0]];
if (r == DBNull.Value)
return null;
return CryptoProvider.Instance.Decrypt((string) r);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
object paramVal = DBNull.Value;
if (value != null)
paramVal = CryptoProvider.Instance.Encrypt((string) value);
IDataParameter parameter = (IDataParameter)cmd.Parameters[index];
parameter.Value = paramVal;
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public SqlType[] SqlTypes
{
get { return new SqlType[]{new StringSqlType()}; }
}
public Type ReturnedType
{
get { return typeof(string); }
}
public bool IsMutable
{
get { return false; }
}
}
And that is it, you can now get transparent encryption / decryption when you are going to the DB, at no cost to the application design.