Skip to main content
ElastiCORE 3.0 is a release candidate: 3.0.261022-rc1 on Maven Central. Projects on the 2.2 line: see the 2.2 documentation.
Version: 3.0

The rule engine

The 3.0 generator has two parts:

DecidesWhere
Ruleswhat is written - does this field get @Column(length=200)Java classes (CodeRules)
Skeletonswhere it goes - the outline of a file.tmpl files

Rules read the model and decide; how a string is written - semicolons, imports, property declarations - is answered by a language seam (Syntax). That separation is why the same rules generate both Java and Kotlin.

model (DSL) ──▶ Spec ──▶ rules ──▶ SourceCode ──▶ skeleton ──▶ file
│
Syntax (language)

Bundles​

A bundle is a named list of rulesets. Two words in build.gradle choose one:

architecturelanguage = 'java'language = 'kotlin'
layeredjpa-springboot (2.x-compatible output); -Pv3: jpa-springboot-v3jpa-springboot-kt
dddddd-springbootddd-springboot-kt
hexagonalhexagonal-springboothexagonal-springboot-kt
BundleWhat it generates
jpa-springbootthe same output as the 2.x pipeline (apart from the 2.x bug fixes the history lists)
jpa-springboot-v3a cleaned-up output - only the imports in use, class annotations written by rules
jpa-springboot-ktKotlin output
ddd-springbootv3 + aggregate boundaries - only roots get a repository, service and controller; immutable value objects; domain events
hexagonal-springbootddd-springboot + an inbound port (<Entity>UseCase) per exposed root
ddd-springboot-kt, hexagonal-springboot-ktthe same readings in Kotlin
ts-modelTypeScript - DTOs, enums, search DTOs and a client that calls the controllers

The two words are not a table in the plugin: the plugin passes them as written and the generator turns the pair into one bundle and one language switch, so they cannot disagree.

Opt-in, and the 2.x output kept​

  • Declare nothing and nothing changes. A build whose elasticore { } block sets no architecture, language or rules runs the 2.x generator. A default written as layered would look the same and would not be: the plugin sends nothing when nothing was declared.
  • Checked on every pull request: CI regenerates the reference project with 2.x's generator, which must leave it exactly as committed, then compares the rule engine's output with 2.x's - per artifact (150 MATCH / 0 DIFF / 2 ACCEPTED) and over all 229 files 2.x generates for the reference project (0 differ / 0 missing / 0 extra: the four files a 2.x bug fix changes are accepted by name, one more differs only in import order).
  • New capabilities switch on only when you use them - a DSL keyword, a setting, a bundle.

Choosing rules in env.yml​

Most projects never need this - the two words in build.gradle choose the bundle. A domain that differs from the rest of the project names its rules:

blueprint/<domain>/env.yml
elasticore:
config:
publisher:
rules: [hexagonal-springboot, ossie-export] # replaces the default for this domain
suppress: [jpa-comment] # turns one rule off; accumulates
KeyMeans
rulesthe rulesets to use - replaces the default. A name is a bundle, a ruleset, or a fully qualified CodeRules class
suppressrule ids to turn off - accumulates
rules.ktKotlin's (wins over rules when present)
rules.tsTypeScript's - TypeScript never inherits the server's rules

Order of precedence: the domain's env.yml, then the project (the system property elasticore.publisher.rules of the generation task), then the built-in default. publisher.rules is read by the rule engine only; the 2.x annotation processor does not read it.

Listing and adding rules​

./gradlew elcore3 -PlistRules     # per domain: the rules selected, per file kind, in order (writes nothing)

Team rules are added after the selected bundle, from build.gradle:

build.gradle
elasticore {
architecture = 'hexagonal'
rules = ['com.acme.rules.AcmeRules'] // added to the bundle
}
dependencies {
elasticore project(':acme-rules') // the generator's classpath, not the application's
}

Writing them: Team rules.

Two entry points, kept apart​

The rule engine (elcore3) and the 2.x annotation processor (@ElastiCore) generate the same files by different rules. A project that uses bundles uses the task only - do not put @ElastiCore on the application class. The plugin registers the tasks; without the plugin they can be configured by hand (see the rule guide in blueprint/elasticore-rule-guide.md).