Persisting immutable objects to a relational database
I've seen certain object-oriented experts advise that domain objects (POCOs) should be immutable.
That is, their state should be entirely determined at construction, and changes to state should require creating a completely new instance.
But say I'm persisting to a relational DB using an ORM - how do I determine which properties to persist, and how do I reconstruct the domain object based on data in a database?
For example, say I want to write this object to the database:
class User
{
private string _emailAddress;
public User(string emailAddress, string password)
{
// ... generate hash & seed
_emailAddress = emailAddress;
}
public string PasswordHash { get; private set; }
public string PasswordSeed { get; private set; }
public bool ValidatePassword(string password)
{
// validate it against the stored hash
}
}
In the example above, I want to store the state of the object and retrieve it later. This means storing the PasswordHash, PasswordSe开发者_Python百科ed and EmailAddress.
But the structure of the class prevents me from doing so.
PasswordHash and PasswordSeed - I can store them, but what happens when I want to read them back from the database and reconstruct the object? There's no constructor parameters to pass them to, and I can't even construct the object without them because 'password' must be supplied and I don't know what the original password was. I can't set the 'PasswordHash' or 'PasswordSeed' properties, because they're 'private set'.
EmailAddress - I can't store it because even though it's part of the object state, it's marked private and can't be accessed internally.
Yes, I could just make every property in the object public and read/write. But then, where's my encapsulation and immutability?
See, I wanted to store the password as a hash/seed and hide the emailaddress for a reason. I wanted to enforce a certain workflow into my domain objects.
But when it comes to storing them, I have to undo all those constraints and turn my domain objects into dumb key/value stores, with no constraints.
The underlying problem, it seems, is that relational databases have no concept of encapsulation. Every column in a table is 'public' and every value is 'mutable'.
So does this mean I have to throw out relational mapping altogether if I'm to have an immutable domain model? Or is there some well-known strategy for getting around this that I just don't know about yet?
First of all - statement that domain objects should be immutable is wrong.
Only value objects should be immutable. User most likely is an entity.
Usually ORM`s uses some tricks to workaround this issue.
E.g. NHibernate leverages proxy libraries like LinFu to silently sub-class every class You have mapped with dynamically created ones. Doing this, it attaches handlers for every method that checks if state has been modified and triggers persisting changes of dirty entities.
NHibernate forces mapped objects to have protected, argument less constructors, all properties and methods to be overridable. Object is created "empty" through this constructor and state is set through overriden properties.
Private Fields can be serialized too - see http://msdn.microsoft.com/en-us/library/system.serializableattribute.aspx
In you case you want to serialize the values to a DB and also read them back...
There are a couple of ways to do that, for example:
Implement 2 static methods as class member which can access all internal/private fields and thus serialize and reconstruct any field in the class
Implement some Interface for serialization/deserialization which you can use as a parameter to the static method to lessen the dependency between the serialization/deserializeton process and the class
EDIT - as per comments:
In the static methods you can freely choose which fields to persist in DB and how (binary, XML or as one column per field etc.). Just implement your serialization/deserialization interface accordingly.
You can read into serialization and deserialization of objects and store the data that way in your database (some of these are language specific so may not work cross-language). If you want to store all the fields that is. This will solve your password problem but would pose a security risk if someone were to gain access and deserialize your instances and get the plaintext password (if it's stored in the object).
Here's a basic example of how to do it : http://blog.kowalczyk.info/article/Serialization-in-C.html
I would recommend that you decouple your domain layer from your data layer. In other words:
- Make a data layer class that corresponds directly to your User table's columns with all public getters/setters, a parameterless constructor, and no behavior.
- Make a domain layer class which has the behaviors (i.e. methods), no public setters (if possible, in order to avoid the Anemic Domain Model anti-pattern), and minimal public getters.
- Create a mapper class to map between the two.
- (Optional, but good if this is a larger app.) Make sure that only the domain class (or ideally its interface) is accessible to the outside world. The mapper and data classes are not for public consumption so don't expose them.
Problem solved! It seems like a pain to write all the mapping code (and you will need to test it because mapping is very error-prone) but in the end, it'll solve the headache caused by trying to server 2 purposes with one class.
精彩评论