grafana / thema

A CUE-based framework for portable, evolvable schema
Apache License 2.0
229 stars 12 forks source link

thema: Initial pass at Go lenses/migrations #187

Closed sdboyer closed 1 year ago

sdboyer commented 1 year ago

This introduces an alternate mechanism for writing lenses: passing in a slice of ImperativeLens to BindLineage:

type ImperativeLens struct {
    To, From SyntacticVersion
    Mapper   func(inst *Instance, to Schema) (*Instance, error)
}

where each function has exactly the same scope of responsibility as would otherwise be fulfilled by a declarative lens written in CUE.

The caller provides these via a new BindOption called ImperativeLenses(). In the current implementation, the caller must choose to implement their lenses entirely in Go, or entirely in CUE. We could relax this later.

This isn't fully ready - definitely needs more tests, and the requisite changes to Translate() aren't yet implemented. (Though i think those will be like another 40 LoC...trivial). But the test that exists so far at least verifies the correctness of the checker which ensures all and only the exact set of Go migrations are being provided.

The test also shows what actually writing Go lenses might plausibly look like - though PLEASE NOTE that when this is put to use in kindsys, we'll be putting in a wrapping layer so that the developer doesn't need to interact directly with *thema.Instance.

sdboyer commented 1 year ago

i'm gonna merge these, with this note...

Recent discussions have absolutely led me to change my mind about the necessity of having Go lenses as an essential part of Thema. They just make all of what Thema is doing considerably easier to at least get started with, because it allows writing the more complex part (lenses) in a familiar language. All the checkable invariants in the world don't matter if the learning curve is too steep.

I'm not sure that what's in this PR is the final form that imperative lenses should take. It does seem to me like there's a plausible path where we actually handle lacuna resolvers as injected funcs, and that the implementation here could be wholly replaced by that. But this PR does the absolute minimal thing necessary to enable lenses in Go, which gets us started. And because it requires a clean split - either there are imperative lenses or declarative ones, no mixing and matching - we have the simplest foundation for changing it later and minimizing impact on users.