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

Targets

One entity in the YAML (PurchaseOrder) makes several files. Each kind of file has a name, and that name is its target. A rule chooses the file it contributes to with @Rule(target = "..."). Without it, the target is entity.

Which file is which target is the first word of each line of the generation log:

dto       src/main/java/io/elasticore/sample/petstore/application/dto/CustomerDTO.java  WROTE
srchdto src/main/java/io/elasticore/sample/petstore/application/dto/CustomerSrchDTO.java WROTE

YAML files, targets and method shapes​

YAMLFile madetargetRule methodWhat the rule reads
entity.ymlCustomer.javaentity (default)(EntitySpec e), (EntitySpec e, FieldSpec f)the entity, its fields
entity.yml, dto.ymlCustomerDTO.java, ItemDTO.javadto(EntitySpec dto, FieldSpec f)the DTO name, its fields
a field's @search(...)CustomerSrchDTO.javasrchdto(EntitySpec s, FieldSpec f)the search fields
repository.ymlPurchaseOrderRepository.javarepository(EntitySpec e)e.queries()
port.ymlCreditCheckPort.javaport(PortSpec p)p.operations()
enum.ymlOrderStatus.javaenum(EnumSpec en)en.constants()

A DTO from dto.yml also comes in as an EntitySpec. Read its name and fields the same way as an entity's.

Who manages the file​

Rules apply every time to the files the generator writes. A file the developer fills in is generated only once and belongs to the developer from then on, so rules apply only when it is first created.

FileWhoseIf a rule is added later
entity, dto, srchdto, enum, repository, port, mapper, XxxCoreService, ports and use casesthe generator'sapplied on the next generation
The developer class of a split root (PurchaseOrder.java, target behavior)the developer'sthe generation log points out missing class annotations - add them by hand
control, XxxService (service_extend), factory, domain servicesthe developer'sno notice. A file never changed is rewritten

If you edit a generator's file by hand, the next generation skips it and tells you (-Pforce overwrites it). Do what you want changed with a rule.

Extra files in DDD and hexagonal​

targetExample (pet store)Whose
entityCustomer.java; for a split root, PurchaseOrderBase.javathe generator's
behaviorPurchaseOrder.javathe developer's
eventPurchaseOrderPlacedEvent.javathe generator's
repositoryPort, queryPort, usecaseCustomerRepositoryPort.java, CustomerUseCase.javathe generator's
invariants, specifications, repositoryAdapterCustomerRepositoryAdapter.java …the generator's
service / service_extendCustomerCoreService.java / CustomerService.javathe generator's / the developer's
controlCustomerController.javathe developer's
mapper, apiErrorsPetstoreMapper.java, PetstoreApiErrors.javathe generator's / the developer's (per domain)

A team rule with target = "entity" applies to both files of a split root. You do not have to care whether a root is split. ./gradlew elcore3 -PlistRules shows the full list for your project.

Rules for domain files, made once per file (once), take (DomainSpec d). Written as (EntitySpec e), they do not run, and a warning is printed.

dto.yml - hiding internal fields from the API docs​

model/dto.yml
  ItemDTO:
meta: dto @template(Item)
fields:
productName: string @ref(product.name) @internal # our marker
    /** dto.yml: a field marked @internal is left out of the API docs. */
@Rule(value = "ps-internal", target = "dto")
void internal(EntitySpec dto, FieldSpec f) {
if (f.hasDsl("internal")) {
ctx(dto).field(f).merge(AnnoCode.of("io.swagger.v3.oas.annotations.media.Schema")
.val("hidden", true));
}
}

ElastiCORE already puts @Schema(description = ...) on DTO fields. ensure does nothing when the same annotation is there (checked by a test), so adding an attribute takes merge. ItemDTO.java:

	@Schema(description = "productName", hidden = true)   // the rule adds hidden
private String productName;

repository.yml - no SELECT *, read-only queries​

model/repository.yml
repository:
PurchaseOrder:
methods:
- id: summarizeByStatus
return: List<Object[]>
query: "SELECT o.status, COUNT(o) FROM PurchaseOrder o GROUP BY o.status"
    /** repository.yml: a query that only reads runs read-only; "SELECT *" is refused. */
@Rule(value = "ps-queries", target = "repository")
void queries(EntitySpec e) {
for (QuerySpec query : e.queries()) {
if (query.sql() == null) continue; // Spring Data derives it from the method name
if (query.sql().toLowerCase().replaceAll("\\s+", " ").contains("select *")) {
ModelCheck.fail(e.getName() + "." + query.name() + ": name the columns instead of SELECT *");
}
if (query.isSelect()) {
ctx(e).method(query.name()).ensure(
AnnoCode.of("org.springframework.transaction.annotation.Transactional").val("readOnly", true));
}
}
}
    @Query(nativeQuery=false, value=" SELECT o.status, COUNT(o) FROM PurchaseOrder o GROUP BY o.status")
@Transactional(readOnly = true) // added by the rule
List<Object[]> summarizeByStatus();

ctx(e).method("name") annotates every method with that name. For a name that does not exist, the file is left as it is and the generation log says so:

[rule] method 'summarizeByState' is not declared in PurchaseOrderRepository (repository file); its annotations were not written

port.yml - marking external calls​

model/port.yml
port:
CreditCheckPort:
meta: http @url("https://credit.example.com/api")
methods:
checkCredit:
meta: method @get("/partners/{bizNo}/credit")
params:
bizNo: string
return: CreditResultDTO
    /** port.yml: every HTTP call is marked with the system and the endpoint it reaches. */
@Rule(value = "acme-external-call", target = "port")
void externalCall(PortSpec port) {
ctx(port).ensureClassAnnotation(AnnoCode.of("com.acme.support.ExternalSystem").value(port.getName()));
for (PortSpec.Operation op : port.operations()) {
if (op.url() != null) {
ctx(port).method(op.name()).ensure(AnnoCode.of("com.acme.support.ExternalCall")
.value(op.httpMethod() + " " + op.url()));
}
}
}
@ExternalService(protocol="http", id="erp.CreditCheckPort", url="https://credit.example.com/api", ...)
@ExternalSystem("CreditCheckPort") // the rule
public interface CreditCheckPort {

@HttpEndpoint(url="/partners/{bizNo}/credit", method="GET", ...)
@ExternalCall("GET /partners/{bizNo}/credit") // the rule
CreditResultDTO checkCredit(String bizNo);
}

@ExternalSystem and @ExternalCall are annotations written in the application itself. The rule only needs the annotation's name; the class does not have to be on the generator's classpath.

enum.yml - each enum once in the API docs​

    /** enum.yml: every enum is described once in the API docs and referenced from there. */
@Rule(value = "ps-enum-ref", target = "enum")
void enumRef(EnumSpec enumeration) {
ctx(enumeration).ensureClassAnnotation(AnnoCode.of("io.swagger.v3.oas.annotations.media.Schema")
.val("enumAsRef", true));
}

The pet store's OrderStatus.java:

@Schema(enumAsRef = true)          // ps-enum-ref
@Getter
public enum OrderStatus {

An enum rule is called once per enum (once); there is no field shape (EnumSpec, FieldSpec). Read the values with enumeration.constants() (c.name() → "PLACED", c.values() → that value's columns).