open-feature / ofep

A focal point for OpenFeature research, proposals and requests for comments
https://openfeature.dev
20 stars 15 forks source link

[Proposal] [flagD] CUE to generate upstream specifications #17

Closed AlexsJones closed 2 years ago

AlexsJones commented 2 years ago

As flagD evolves, it will be presented on different types of services with a variety of protocols. Many of these protocols have client libraries that can be generated for convenience. For example, we use openapi to enable us to create a golang server implementation. This is great because a) it's fast and easy b) it matches the specification files promises exactly - validation/matching for free.

Given we are looking to now expand to offer new generated server support for gRPC, we are in a predicament about whether to a) derive the implementation from OpenAPI b) have a new source of truth as .proto files c) have some sort of composite between the two.

My proposal here would be to choose a new top-level DSL to define the specifications required to drive generated server code implementations. As such, cue offers integration with YAML/JSON & protobuf directly. This would mean we could use a single file(s) and toolchain to generate all of the automatically created code for flagD. The benefit here would be a single source of truth, ease of contribution, ease of extension and simplified build process.

image

By switching to CUE, we can use a single build chain to produce the OpenAPI spec as a generated file that is convenient to users who want to build against flagD ( possible code-generating their own client libraries). Though we would not need to use the generated OpenAPI spec through any further generator. Instead, the protoc-http tool will allow for generation from protobuf files as per @James-Milligan initial investigation.

Next steps:

If we have agreement on this, I would propose looking at a top level CUE file(s) to replace the current OpenAPI YAML file that is generated from. At this point we will be able to prove it can become the new top-of-chain build step, enabling us to then replace the downstream components.

AlexsJones commented 2 years ago

By generating the Kubernetes CRD, we could consume that in Open Feature Operator.

AlexsJones commented 2 years ago

Here is an example of converting this to cue

#Spec: {
    // flagd Flag Configuration
    //
    // Defines flags for use in flagd, including typed variants and
    // rules
    @jsonschema(schema="http://json-schema.org/draft-07/schema#")
    flags?: {
        {[=~"^.{1,}$" & !~"^()$"]: #booleanFlag | #stringFlag | #numberFlag | #objectFlag}
    }

    #flag: null | bool | number | string | [...] | {
        // Indicates whether the flag is functional. Disabled flags are
        // treated as if they don't exist
        state: "ENABLED" | "DISABLED"

        // Default variant
        //
        // The variant to serve if no dynamic targeting applies
        defaultVariant: string

        // Targeting Logic
        //
        // JsonLogic expressions to be used for dynamic evaluation. The
        // "context" is passed as the data. Rules must resolve one of the
        // defined variants, or the "defaultVariant" will be used.
        targeting?: {
            ...
        }
        ...
    }

    #booleanVariants: {
        variants?: null | bool | number | string | [...] | {
            {[=~"^.{1,}$" & !~"^()$"]: bool}
        } | *{
            on:  true
            off: false
            ...
        }
        ...
    }

    #stringVariants: {
        variants?: null | bool | number | string | [...] | {
            {[=~"^.{1,}$" & !~"^()$"]: string}
        }
        ...
    }

    #numberVariants: {
        variants?: null | bool | number | string | [...] | {
            {[=~"^.{1,}$" & !~"^()$"]: number}
        }
        ...
    }

    #objectVariants: {
        variants?: null | bool | number | string | [...] | {
            {[=~"^.{1,}$" & !~"^()$"]: {
                ...
            }}
        }
        ...
    }

    #$comment: _

    #booleanFlag: #flag & #booleanVariants

    #stringFlag: #flag & #stringVariants

    #numberFlag: #flag & #numberVariants

    #objectFlag: #flag & #objectVariants
    ...
}

    flags: #Spec & {
        myBoolFlag: {
            state: "ENABLED", variants: {
                on:  true
                off: false
            }
            defaultVariant: "on"
        }
        myStringFlag: {
            state: "ENABLED", variants: {
                key1: "val1", key2: "val2"
            }
            defaultVariant: "key1"
        }
        myNumberFlag: {
            state: "ENABLED", variants: {
                one: 1
                two: 2
            }
            defaultVariant: "two"
        }
        myObjectFlag: {
            state: "ENABLED", variants: {
                object1: key: "val", object2: key: true
            }
            defaultVariant: "object1"
        }
    }

back into json...

{
    "flags": {
        "myBoolFlag": {
            "state": "ENABLED",
            "variants": {
                "on": true,
                "off": false
            },
            "defaultVariant": "on"
        },
        "myStringFlag": {
            "state": "ENABLED",
            "variants": {
                "key1": "val1",
                "key2": "val2"
            },
            "defaultVariant": "key1"
        },
        "myNumberFlag": {
            "state": "ENABLED",
            "variants": {
                "one": 1,
                "two": 2
            },
            "defaultVariant": "two"
        },
        "myObjectFlag": {
            "state": "ENABLED",
            "variants": {
                "object1": {
                    "key": "val"
                },
                "object2": {
                    "key": true
                }
            },
            "defaultVariant": "object1"
        }
    }
}
AlexsJones commented 2 years ago

https://github.com/open-feature/research/blob/main/003-OFEP-CUE-upstream.md

This will be closed and reopened as a PR against the CUE state