Design DecisionsΒΆ

Maintainability. We wanted to facilitate maintainability of the system through as many static and run time checks as possible so that most errors in the object graphs are registered prior to the deployment in the production. These checks include strong typing annotations at generation time and various runtime checks at deserialization (dangling references, range checks, minimum number of elements in arrays, pattern matching etc.).

Versatility. Since we need humans to operate on object graphs, we needed the data representation of the object graph to be readable and editable. Hence we strived to make the resulting JSONable structures succinct yet comprehensible.

We intentionally did not fixate Mapry to directly handle files to allow for a larger variety of supported formats and sources (JSON, YAML, BSON, MongoDB etc.). Mapry operates on an in-memory representation of the JSONable data (such as Python dictionaries or Go map[string]interface{}) which makes it much more versatile than if it handled data sources directly.

Code readability over speed. We wanted the generated code to be rather readable than fast. Though the seasoned developers might not care about the implementation details, we found that newcomers really like to peek under the hub. They get up to speed much faster when the generated code is readable.

In particular, when the generated code permeates most of your system components, the readability becomes a paramount when you fix bottlenecks or debug.

Avoid dependency hell. We explicitly decided to make the generated code as stand-alone as possible. This includes generating redundant data structures such as parsing errors which could be theoretically used across different generated modules.

While this redundancy seems wasteful (duplication) or impractical (specific errors need to be checked instead of general ones), stand-alone code allows us to dispense of a common Mapry library which greatly alleviates the dependency hell in larger systems.

Take Protocol buffers as a contrasting example. The code generated by protocol buffers depends on a common protobuf library. Imagine you depend on two different libraries, each using a different version of protocol buffers. Since your system now has conflicting dependencies, there is usually no easy way to use both libraries in the system. If you are not the owner, you need to contact the maintainers of one of the libraries and ask them for an upgrade.