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

Team rules (rulesets)

"Our annotation on every root entity", "personal data never in a response", "a command without an event fails the build" - team rules as code, plugged into generation. Write the rule once instead of hand-editing generated files, and every entity gets it on every regeneration. ElastiCORE's own Java, Kotlin, TypeScript, DDD and hexagonal generation are rules of exactly this kind.

acme-rules/src/main/java/com/acme/rules/AcmeRules.java
public class AcmeRules implements CodeRules {

/** A field marked @secret in the DSL is accepted in requests but never sent back. */
@Rule(value = "acme-secret", target = "dto")
void secret(EntitySpec entity, FieldSpec field) {
if (field.hasDsl("secret")) {
ctx(entity).field(field).ensure(AnnoCode.of("com.fasterxml.jackson.annotation.JsonProperty")
.valRaw("access", "JsonProperty.Access.WRITE_ONLY"));
}
}

/** A root that takes commands must declare its events - otherwise the build fails. */
@Rule("acme-commands-need-events")
void commandsNeedEvents(EntitySpec entity) {
if (!entity.commands().isEmpty() && entity.events().isEmpty()) {
ModelCheck.fail(entity.getName() + " has commands but no @event.");
}
}
}
build.gradle
elasticore { rules = ['com.acme.rules.AcmeRules'] }
dependencies { elasticore project(':acme-rules') }
      bizNo: string(12)! @secret      # a marker of your own in the DSL

The elasticore configuration is the generator's classpath, not the application's; the module is compiled before generation. Rules named in rules are added after the bundle the build selected.

The shape of a rule​

CodeRules is a marker interface: every method with @Rule is a rule.

@Rule attributeDefaultMeans
value(required)the rule id - for tracing and suppress
target"entity"the kind of file it contributes to: entity, dto, srchdto, enum, repository, service, control, mapper, port, Q ...
order0lower runs first
phaseHEADHEAD before the field rules, TAIL after

The signature decides when it runs: (EntitySpec e) once per entity, (EntitySpec e, FieldSpec f) once per field, (DomainSpec d) once per domain, (EnumSpec e) per enum.

Reading the modelWriting code
f.hasDsl("secret"), f.dslValue("length"), f.required(), f.isEntityRef()ctx(e).field(f).ensure(anno), .add, .merge, .replace, .removeAnnotation(fqcn)
e.getName(), e.pkg("dto"), e.fields(), e.hasMeta("expose")AnnoCode.of("...Column").val("name", "user_id").valRaw("length", "%d", 200)
e.aggregateRoot(), e.commands(), e.events()ctx(e).removeField(f), ctx(e).dontMake(), ModelCheck.fail(...)

Never build an annotation by concatenating strings - AnnoCode writes it, and the language seam (Syntax) decides whether it is Java's {} or Kotlin's []. A rule has no if (kotlin): the same rule serves both languages.

Turning a rule off, seeing what runs​

env.yml
    publisher:
suppress: [jpa-comment] # no more @Comment annotations
./gradlew elcore3 -PlistRules   # per domain, per file kind, the rules in order - with their ids

Going further​

Two guides are copied into every project's blueprint/ folder (in Korean):

  • elasticore-rule-dev-guide.md - writing a first rule step by step: adding and removing annotations, a YAML marker of your own, build checks, tests.
  • elasticore-ddd-customize-guide.md - turning generated files off, marking every class with its DDD role (jMolecules), a DSL word of your own that makes a new file, model checks with team settings, testing rules with and without an application, Kotlin.

The rule API in full is in elasticore-rule-guide.md, chapters 3 to 6.