Your first project
Five steps from a Spring Boot project to a running API with access checks and validation. The files are the ones in the ElastiCORE README; the agent kit's CI builds this project (plus test dependencies) with the newest release every week.
① build.gradle
Use the settings.gradle and build.gradle of Installation -
architecture = 'hexagonal', language = 'java'.
② The blueprint: src/main/resources/blueprint/shop/
A domain is a folder under blueprint/. It holds its settings (env.yml) and its model
(model/*.yml).
elasticore:
enable: true
config:
domainName: shop
framework: springboot
mode: jpa
j2ee: jakarta
apiPath: "/api" # REST paths: /api/shop/...
namespace:
base: com.acme.shop # the whole package layout from this one line
entity:
Product:
meta: entity @root @editable @expose(20) @access(read=public) @sort(name+) -- product
fields:
productId: long @id @sequence
name: string(100)! @search(like)
price: money(10.2)! @min(0.01, 'price must be positive')
③ Generate
./gradlew elcore3
The code appears under src/main/java/com/acme/shop/:
domain/model/Product.java JPA entity
application/ProductCoreService.java CRUD, search, access checks (regenerated)
application/ProductService.java where your code goes (generated once, yours afterwards)
application/dto/ProductDTO.java request and response (+ 400 validation)
application/dto/ProductSrchDTO.java search: name LIKE, paging, sorting (by name unless asked otherwise)
adapter/in/web/ProductController.java REST API + OpenAPI docs (generated once too)
domain/repository/... repository port
infrastructure/persistence/... its JPA adapter
④ Your part - what the model does not say
-
An application class:
@SpringBootApplicationincom.acme.shopor a package above it, so that Spring finds the generated classes. Spring Initializr writes one in the package of its group and artifact (com.acmeandshopgivecom.acme.shop).src/main/java/com/acme/shop/ShopApplication.javapackage com.acme.shop;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
} -
Your business logic, in
ProductService.java. Like the controller it is generated once: it follows the model until you edit it, and from then on generating again leaves it alone (SKIP(user-owned)in the log). See Your logic in the owned service. -
Signing in. Changing a product needs the
ADMINrole (@access(read=public): anyone reads, everything else is for the administrator). Either put@signinon an entity with@loginand@passwordand the sign-in API is generated (Access and sign-in), or callSessionCurrentUser.signIn(request, userId, "ADMIN")from a sign-in of your own (packageio.elasticore.springboot3.security, on Boot 4 too); with Spring Security, register aCurrentUserbean instead.
⑤ Run
./gradlew bootRun
Open http://localhost:8080/swagger-ui.html: every operation the model exposes can be called from
there. For Product, under /api/shop/product:
| Request | Does |
|---|---|
POST / with the fields | create; answers the row with its key |
GET /{productId} | read one; 404 when there is none |
POST /search with {} or conditions | a page (content, totalElements, ...) - there is no GET for a list |
PUT / with the key in the body | update (the entity is @editable) |
DELETE /{productId} | delete |
What the rules answer:
POST /api/shop/product/search, GET /api/shop/product/{id} → 200 for anyone
POST /api/shop/product (not signed in) → 401 "Sign in to create Product"
POST /api/shop/product with price 0 → 400 "price must be positive"
Validation comes before the access check: even without signing in, a price of 0 gets the 400.
From here on a change starts in the model: ./gradlew build and bootRun run elcore3 before
compiling, so the code never lags behind the model.
Build commands
| Command | What it does |
|---|---|
./gradlew elcore3 | Generates from the DSL with the 3.0 rule engine (Java or Kotlin, per language) |
./gradlew elcore3ts | Writes the TypeScript client to src/main/ts (TypeScript) |
./gradlew compileJava / build / bootRun | Generate first, then compile (unless the block sets generateBeforeCompile = false) |
./gradlew elcore3 -Pdomain=shop | Generates one domain only |
./gradlew elcore3 -PlistRules | Lists the rules selected for each domain, per file kind, in order - writes nothing |
./gradlew elcore3 -Pforce | Overwrites generated files even when they were edited by hand - a decision, not a habit |
./gradlew elcore | The 2.x generator (projects without an elasticore { } architecture, language or rules) |
typescriptClient = true in the elasticore { } block makes the build run elcore3ts after
elcore3 and before compiling.
The rule engine prints one line per file and a summary such as
=== elcore3: 20 written / 0 unchanged / 2 skipped / 0 failed ===. Problems in the model are listed
under [elcore3] DSL validation warnings: and the build still succeeds - fix each one in the YAML.
A YAML file that does not parse (* LOAD ERROR: <file>) stops the build from 3.0.261022-rc1 on:
[elcore3] the model check failed.