KlumAST

KlumAST visual lockup: a stylised fashion figure steps through a luminous portal onto a diagonal catwalk, beside the KlumAST wordmark and 4.0 release identity.

What is KlumAST?

KlumAST turns annotated model classes into concise, statically checked Groovy DSLs. It is designed for a Schema Developer to describe a model once, a Model Writer to configure it through a readable DSL, and client code to consume the completed model without generated mutation methods.

The Terms and role guide defines the core terms โ€” especially Schema and Model โ€” along with these responsibilities and the value kinds used throughout the documentation.

Why models as code?

A useful model-as-code approach should be easy to author, clear to change, and safe to verify:

Typical validation output names the rule that emitted it:

- ERROR #ConnectivityChecks.portMustBeInRange(): port must be between 1 and 65535

Construction paths retain the source context, which is especially useful when a model is split across scripts. For example, an illustrative failure from models/production.groovy could read:

<root>.service($/Deployment.From:file(models/production.groovy)/service):
- ERROR #port: Field 'port' must be set

Generated factories configure a mutable Builder graph and return completed, structurally immutable DSL Objects. Lifecycle work through POST_TREE runs on Builders; materialization happens before validation. See Basics, Model Phases, and the Builder First Migration guide. Construction paths describe model-source locations in detail.

These capabilities make KlumAST especially useful for GitOps and other *aC (anything-as-code) workflows. Git can record a configuration's structure, but a checked-in structure is not necessarily a verified model. Read why *aC needs model-level tests before treating configuration in Git as the end of the story.

Example

Given the following config classes:

@DSL
class Config {
    Map<String, Project> projects
    boolean debugMode
    List<String> options
}

@DSL
class Project {
    @Key String name
    String url
    MavenConfig mvn
}

@DSL
class MavenConfig {
    List<String> goals
    List<String> profiles
    List<String> cliOptions
}

A config object can be created with the following dsl:

def github = "http://github.com"


def config = Config.Create.With {

    debugMode true
    
    options "demo", "fast"
    option "another"
    
    projects {
        project("demo") {
            url "$github/x/y"
            
            mvn {
                goals "clean", "compile"
                profile "ci"
                profile "!developer"
                
                cliOptions "-X -pl :abc".split(" ")
            }
        }
        project("demo2") {
            url "$github/a/b"
            
            mvn {
                goals "compile"
                profile "ci"
            }
        }
    }
}

The projects closure is optional syntactic sugar; project entries can also appear directly in the Config callback.

Since complete objects are created, using the configuration is simple:

if (config.debugMode) println "Debug mode is active!"

config.projects.each { name, project ->
    println "Running $name with '${project.mvn.goals.join(' ')}'"
}

def projectsWithoutClean = config.projects.findAll { !it.value.mvn.goals.contains("clean")}.values()

if (projectsWithoutClean) {
    println "WARNING: The following projects do not clean before build:"
    projectsWithoutClean.each {
        println it    
    }
}

For the recommended 4.0 Gradle setup and generated-source-mirror workflow, start with Gradle Onboarding.