“Custom” is also risky

 
event

Whenever you do something “custom” there is always a trade-off.
Sometimes is a cost of maintaining your customization and sometimes is just a cost of “swimming upstream”, doing that whatever you are doing in a different way than most people.

NMoneys customization

In NMoneys there are quite a few custom things. Amongst the ones I regret the most (if not top one) is serialization.

Serialization is the process of saving an instance to a persistent storage with the hope of bringing that serialized instance to memory in the very same shape it was saved in the first place.

Having been hit with serialization issues of monetary quantities in the past (in the previous incarnation of NMoneys, before becoming Open Source) I set to solve those problems in the library itself. And that is why instances of Money and Currency have baked-in serialization concerns.

The .NET framework handles several flavors of serialization: binary serialization, ye olde XML serialization, the relatively newer Data Contract serialization, XAML serialization,… In NMoneys, binary and XML and Data Contract serialization are customized. For XAML, I did not succeed after a couple of tries.
And at the very least, one of the choices in the customization has proved to be tricky: writing members in camelCase.

Persisting to RavenDB

My company and I have been using RavenDB a lot lately. I am a big fan of document databases and RavenDB has an edge over other products by having a pretty good story with  free-text search, due to its usage of Lucene.Net.

RavenDB stores JSON documents and uses a version of Json.NET to serialize and deserialize .NET objects.

Our case (all the pun is intended)

We have the prototypical example of an Order that may contain multiple Lines with lines that we are storing in the database:

There is also an Snapshot that contains a subset of the information, plus some information that can be projected from the object hierarchy itself:

Project as it was 1999

If documents representing Order are stored in RavenDB and we wanted to get Snapshot results, a possible solution would be creating an index to query only those orders that belong to a given owner and project the resulting Orders into Snapshots using a result transformer.

Candid and Naïve

Money includes a code that performs a summation of a list of instances. That is exactly what we want to achieve in Snapshot.Total.
Let’s put our Ralph Wiggum’s brain on and create a result transformer that aggregates the price of each line using Money.Total():

What would be the result of this code that uses our naïve transformer?

Would it print the correct sum of all the prices of the documents? If it would, why is there two more sections in the post? Winking smile

If does not. It throws an exception when creating the transformer. RavenDB cannot compile the expression of the transformer. Remember that result transformers are “compiled into code” that is executed in the server prior to the return of the results of a query. And, unfortunately, RavenDB does not know about NMoneys, so it cannot interpret Money.Total().

Default Custom Serialization… uh?

When left to its own devices, that is, not customizing anything of the way RavenDB saves the object to the database, the price of a line is stored with lowercase properties, due to the fact that ISerializable.GetObjectData() is called whenever a Money instance gets serialized and that method writes lowercase property names.

CustomIsRisky_01

With that knowledge, let’s write a transformer that projects into Snapshot and uses regular summation for amounts:

And the result when querying using the new transformer?

To my surprise, it does not crash but it does not work either. LineCount contains the right sum of line items but Total is a surprising (at least for me it was) zero.

Why? It took me a while to figure out, but it makes sense once you find out: we mentioned that the transformation happens in the server. But serialization happens in the client, so when we tell to sum all amounts for a line price, it is trying to sum a property named Amount inside the Price, since it is translating the expression without knowing the custom rules of money serialization and so it is summing the default for a decimal, which is zero.

De-customize

If the problem is that Price does not have an Amount property, but amount, let’s change the way money instances are serialized, shall we?.
One can have total control over the serializátion of a type, by implementing a JsonConverter.

Would a PascalCaseMoneyConverter that writes pascalized properties and registering it for the IDocumentStore at hand plus having a transformer returning pascalized properties do the trick?

It totally does!

CustomIsRisky_02

Want to play with code? Go to the post’s repository and tear it apart.