bond

Microsoft Bond runtime schemaDef


I'm hoping someone could illustrate a common use case for the Microsoft Bond runtime schemas (SchemaDef). I understand these are used when schema definitions are not known at compile time, but if the shape of an object is fluid and changes frequently, what benefits might a runtime generated schema provide?

My use case is that the business user is in control of the shape of an object (via a rules engine). They could conceivably do all sorts of things that could break our backward compatibility (for example, invert the order of fields on the object). If we plan on persisting all the object versions that the user created, is there any way to manage backward/forward compatibility using Bond runtime schemas? I presume no, as if they invert from this:

0: int64 myInt;
1: string myString;

to this

0: string myString;
1: int64 myInt;

I'd expect a runtime error. Which implies managing the object with runtime schemas wouldn't provide much help to me.

What would be a usecase where a runtime schema would in fact be useful?

Thank you!


Solution

  • Some of the uses for runtime schemas are:

    Your case feels like schema validation, if you can pro-actively reject a schema that would no be compatible. I worked on a system that used Bond under the hood and took this approach. There was an explicit "change the schema of this entity" operation that validated whether the two schemas were compatible with each other.

    I don't know the data flow in your system, so such validation might not be possible. In that case, you could use the runtime schemas, along with some rules provided by the business users, to convert between different shapes.

    Simple Binary

    When deserializing from Simple Binary, the reader must know the exact schema that the writer used, otherwise it has no way to interpret the bytes, resulting in potentially silent data corruption.

    Such corruption can happen if the schema undergoes the following change:

    // starting struct
    struct Foo
    {
        0: uint8 f1;
        1: uint16 f2;
    }
    

    The Simple Binary serialized representation of Foo { f1: 1, f2: 2} is 0x01 0x02 0x00.

    Let's now change the schema to this:

    // changed struct
    struct Foo
    {
        0: uint8 f1;
        // It's OK to remove an optional field.
        // 1: uint16 f2;
        2: uint8 f3;
        3: uint8 f4;
    }
    

    If we deserialize 0x01 0x02 0x00 with this schema, we'll get Foo { f1: 1, f3: 2, f4: 0}. Notice that f3 is 2, which is not correct: it should be 0. With the runtime schema for the old Foo, the reader will know that the second and third bytes correspond to a field that has since been deleted and can skip them, resulting in the expected Foo { f1:1, f3: 0, f4: 0 }.

    Schema Validation and Evolution

    Some systems that use Bond have different rules for schema evolution that the normal Bond rules. Runtime schemas can be used to enforce such rules (e.g., checking a type to enforce a rule that no collections are used) before accepting structs of a given type or before registering such a schema in, say, a repository of known schemas.

    You could also walk two schemas to determine with they are compatible with each other. It would be nice if Bond provided such an API itself, so that it doesn't have to be reimplemented again and again. I've opened a GitHub issue for such an API.

    GUI

    With a runtime schema, you have extra information about the struct, including things like the names of the fields. (The binary encoding protocols omit field names, relying, instead, on field IDs.) You can use this additional information to do things like create GUI controls specific to each field.

    There's an example showing inspection of a runtime schema in both C# and C++.

    Custom Mapping

    In C++, the MapTo transform can be used to convert one struct to another, which incompatible shapes, given a set of rules. There's an example of this, that makes use of a runtime schema to derive the rules.