I have been searching for a while, and have found the various threads indicating that protobuf-net can handle nullable types, the various threads about things like empty collections (not relevant here), and the default value behaviour for what would previously have been "optional" fields in proto2, but I have not found a specific answer to the following question. This will be my first time using protobuf-net, or protocol buffers as a serialization format.
Imagine I am sending messages between multiple different applications using an event carried state transfer pattern - so publishers and subscribers of messages containing state - and some kind of shared model for that state. Any participating applications have some representation of "the same" entity (hence the ECST), but not all systems understand all attributes. In terms of persistence in the applications' SQL database, that might look like this (scales left off for brevity):
table App1Products { productKey int, productName varchar null }
table App2Products { productKey int, productName varchar null, productWeightKg decimal null }
table App3Products { productKey int }
For the sake of the example, suppose the trivial shared model is the union of all of the distinct attributes: { productKey, productName, productWeightKg }
Now suppose someone updates a productName in App1Products. We want to publish that changed state. When we do this we can't populate the entire shared model, because App1 does not include the productWeightKg in its schema. We need to somehow "leave out" any value for this element in a way that potential consumers understand that it was not populated.
We can't merely send a default value (0) (or let subscribers deserialize a missing element to a default value) to convey "no update" semantics, because that would cause the productWeightKg value to be set to 0 in the App2Products table when the message is received by App2. We can't send null to convey "no update" semantics, because null is a legitimate value for the column as well.
Ultimately, we need the subscriber code at App2 to construct an update statement such that either the productWeightKg column is not referenced, or it is simply set to itself, and we need some way of telling App2's subscriber code to do that.
One solution seems to be to create an additional element in the message for each field, indicating whether the field is or is not set. In terms of the message content, we might use something like this:
[ProtoContract]
public class Product
{
[ProtoMember(1)]
public int ProductKey { get; set; }
[ProtoMember(2)]
public string productName { get; set; }
[ProtoMember(3)]
public decimal? productWeightKg { get; private set; }
[ProtoMember(4)]
public bool productWeightKgSet { get; private set; }
public void SetProductWeight(decimal? weight)
{
productWeightKg = weight;
productWeightKgSet = true;
}
public void ClearProductWeight()
{
productWeightKgSet = false;
}
}
If that is a reasonable sort of pattern to use, then the next "obvious" idea would be to create some kind of template class for this sort of behaviour which we can reuse for all of our message classes...
public class Optional<T>
{
public T Value { get; private set; }
public bool HasValue { get; private set; }
public void Set(T val) { Value = val; HasValue = true; }
public void Clear() { HasValue = false; Value = default; }
}
Is this a reasonable sort of approach to solving this problem, or is there some other "known good pattern" that I've missed, or something about this pattern which won't work as expected with protobuf-net?
My limited understanding at the moment is that this might require that the Optional class be decorated with all of its possible child implementations using the ProtoInclude attribute, is that correct?