7mind / baboon   0.0.138

MIT License GitHub

Data modeling and versioning language with automatic evolution derivation

Scala versions: 2.13

baboon

Build Status License Latest Release IntelliJ Plugin VSCode Extension Nix Nix Flake

Let the Baboon do the monkey job.

Baboon is a minimal Data Modeling Language and compiler that provides ergonomic, declarative schemas and enforces reliable schema evolution. The compiler runs as a fast immutable multi-phase DAG transform, and is easy to understand and maintain.

Essentially, you define your data structures and Baboon generates implementations for you. Then you define new versions, Baboon generates new versions of the structures, the conversions from old structure versions to new ones and forces your to provide conversions which cannot be derived automatically. Also it comes with extremely efficient tagless binary encoding for all your structures.

The language is completely formal and platform/implementation-agnostic. Unlike e.g. OpenAPI, you can't leave some parts of a structure undefined; you still have an unsafe escape hatch in the form of foreign types.

Use cases:

  • Serializable application state with safe and automatic version upgrades
  • Efficient data transfer with custom tagless binary format
  • Quick data model design with an extremely concise and expressive language

Generates:

  • C#
  • Scala
  • Python, Typescript and more backends are on the way.

Highlights

  • Automatic codec derivation for JSON and UEBA (Ultra-Efficient Binary Aggregate, a custom tagless binary format)
  • Evolution-aware codegen: derives migrations when possible, emits stubs when manual work is required
  • Set-based structural inheritance with +, -, and ^ operators
  • Algebraic data types (adt), DTOs (data) and enums.
  • Basic form of nominal inheritance (contract)
  • Namespaces, includes, and imports
  • Collections (opt, lst, set, map) and timestamps/UID primitives
  • Deduplicated C# output (reuses as much code as possible to lower binary footprint)

You define your data model:

model acme.billing
version "1.0.0"

root adt PaymentMethod {
  data Card {
    pan: str
    holder: str
  }
  data Wallet {
    provider: str
    token: str
  }
}

Then you refactor and extend it:

model acme.billing
version "2.0.0"

data Token {
  token: str
}

root adt PaymentMethod {
  data Card {
    pan: str
    holder: str
  }
  // refactored, but same structure as before
  data Wallet {
    provider: str
    + Token
  }

  // new ADT member
  data BankTransfer { iban: str }
}

Baboon generates conversions (migrations) from version 1.0.0 to 2.0.0. In this particular case all the migrations will be generated automatically.

Detailed language walkthrough with copy-paste examples: docs/language-features.md.

Editor support

Limitations

  1. No templates
  2. Only Enums, DTOs and ADTs
  3. Nominal inheritance support is limited to trait model
  4. Generic/type constructor support is limited to builtin collections
  5. (*) This is a DML, not an IDL, service/interface definitions support is extremely limited at the moment
  6. (*) Comments are not preserved in the transpiler output
  7. (*) No structural inheritance information is preserved in the transpiler output
  8. (*) Only integer constants may be associated with enum members
  9. (*) No newtypes/type aliases
  10. (*) No inheritance-based lenses/projections/conversions

Points marked with (*) will/may be improved in the future.

CLI

See build configuration in .mdl/defs/actions.md and test configuration in .mdl/defs/tests.md.

Notes

  1. All the types which are not transitively referenced by root types will be eliminated from the compiler output.
  2. Usages in structural inheritance are not considered references, so structural parents which are not directly referenced as fields and not marked as roots will be eliminated

Foreign types

Be careful about foreign types. It is your responsibility to wire codecs correctly.

For every foreign type:

  1. Create a custom codec
  2. Override the generated dummy codec with BaboonCodecs#Register
  3. Override the generated dummy codec using the setter on ${Foreign_Type_Name}_UEBACodec#Instance
  4. Override the generated dummy codec using the setter on ${Foreign_Type_Name}_JsonCodec#Instance

Make sure your foreign types are NOT primitive types or other generated types. It's a funny idea, but it will explode in runtime.

Foreign types may hold any position in generics but it's up to you to ensure correctness.

Development

Build commands

This project uses mudyla for build orchestration.

Common Commands

# Format code
direnv exec . mdl :fmt

# Build the compiler
direnv exec . mdl :build

# Run complete test suite
direnv exec . mdl :build :test

# Run full build pipeline (format, build, test)
direnv exec . mdl :full-build

# Run specific test suites
direnv exec . mdl :build :test-gen-regular-adt :test-cs-regular :test-scala-regular
direnv exec . mdl :build :test-gen-wrapped-adt :test-cs-wrapped :test-scala-wrapped
direnv exec . mdl :build :test-gen-manual :test-gen-compat-scala :test-gen-compat-cs :test-manual-cs :test-manual-scala

# Create distribution packages
direnv exec . mdl :build :mkdist

# Build with custom distribution paths
direnv exec . mdl --mkdist-source=./custom/path --mkdist-target=./output :build :mkdist

Setting up the environment

# Enter the nix development shell
nix develop

# Or use direnv for automatic shell activation
direnv allow