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

Entities

entity:
EntityName:
meta: entity [@annotations] -- label
fields:
fieldName: fieldType [@annotations] -- comment
fieldName: fieldType [@annotations] -- label | description
states: ... # a lifecycle (DDD bundles) - see Lifecycle
commands: ... # business commands (DDD) - see Lifecycle

-- text becomes the label (@label(text)); -- label | description gives both a label and a description. The label becomes the column comment and the API description.

The meta line​

AnnotationMeans
@expose(n)REST controller + service + search DTO; n is the page size. Alone it gives create, read, search, paging and REST
@service(n)the same without the controller
@pageablepaging support
@rootan aggregate root (DDD bundles) - needed only on a root another root lists
@aggregate(Root)a member of that root's aggregate (usually inferred)
@editableDDD bundles: the root is plain data - public setters, PUT; without it a root is sealed
@sort(name+)the default order when a search names none: property + + ascending / - descending, comma-separated (@sort(orderDate-, orderId-))
@terms(order, purchase, 구매)what the business calls it - synonyms for the Ossie export; no effect on code
@access(...), @owner(field), @signinwho may do what - see Access and sign-in
@event(Placed(total), Shipped)domain events of a root - see Derived values and events
@expose(20, methods="read,search")the controller serves only the methods listed (create, read, search, update, delete); the client and the use case follow, the service keeps all - see API and error answers
@restExclude(update, deleteById)deprecated for methods: leaves methods out of the service, controller, use case and TypeScript client
@table(name) / @schema(name)the table name / the database schema
@Index(name="uk_member_email", columnList="email", unique=true)a table index; a unique one makes a duplicate answer 409
@abstractan abstract entity - inherited from, no table
@extend(Parent)inherits the parent's fields
@rollupcolumns rolled up into the parent table (single-table inheritance)
@embeddablea value object - see Relations and values
@auditedHibernate Envers auditing

@sort is a 3.0 rule-engine feature; the 2.x generator does not read it. A property the entity does not have is left out with a DSL warning (@sort(...): x is not a field of Product).

Example​

model/entity.yml
entity:
AuditEntity:
meta: entity @abstract
fields:
createdBy: string(20) @updatable(false) @jpa:org.springframework.data.annotation.CreatedBy
createDate: datetime @updatable(false) @jpa:org.springframework.data.annotation.CreatedDate

Product:
meta: entity @root @editable @expose(20) @access(read=public) @sort(name+) -- product (Angelfish, Bulldog ...)
fields:
productId: long @id @sequence
name: string(30)! @search(like)
description: string(3000)!
category: Category @ManyToOne

Rules of a root (DDD bundles)​

These need architecture = 'ddd' or 'hexagonal'.

@invariant(condition, 'message') - what the root always keeps true​

Order:
meta: entity @root @expose(30) @invariant(items.size > 0, 'an order has at least one line')

An OrderInvariants listener on the root checks it whenever the row is written (@PrePersist / @PreUpdate), on every path that saves. A violation answers 422. Write it more than once for more rules. A rule about one field belongs on the field (@min, @minsize), which in the DDD bundles is checked at save time too; @invariant is for rules that compare fields.

@specification(name, condition) - a named domain rule​

Order:
meta: entity @root @specification(highValue, totalAmount > 100) @specification(hasLines, items notEmpty)

Generates OrderSpecifications.highValue() - a Predicate<Order>, so rules combine: highValue().and(hasLines()). It chooses among rows already loaded; it is not a query. With the ts-model bundle, the TypeScript client gets the same functions.

@factory(place) - where a valid aggregate is made​

Generates OrderFactory.place(...), a generate-once stub whose parameters are the root's required fields (generated ids and collections left out). The body throws until you write it.

The condition grammar​

FormMeans
items.size > 0number of members of a to-many
totalAmount > 100a number compared (< too)
notes notNullthe field is set
items notEmptythe to-many holds something

Fields, comparisons and literals only. >=, <= and == cannot be written (the annotation parser reads = as a key-value list), and neither can : (the YAML scalar would turn into a mapping). A condition that cannot be read is left out - check the generated file.

@behavior - a field only the root's methods change​

On a field of a split root, @behavior keeps it out of requests (READ_ONLY) and out of creation(); a protected setter is used by your methods. A root is split into XxxBase and your Xxx without writing @behavior on the entity; on an entity that is not split, a field @behavior is a DSL warning - use @readonly there.

See DDD for sealed roots, @editable and creation().

Composite keys​

More than one @id field generates a KeyDTO class:

OrderItem:
meta: entity
fields:
orderId: string @id
itemSeq: int @id
quantity: int

Names to avoid​

The entity name becomes the table name, so avoid SQL words (Order, User, Group - use PurchaseOrder, Member, Team) and SQL words as field names (order, group, user, select, from, where, table, index, key, value).