Relations and value objects
References
A field whose type is an entity is a reference; @ManyToOne is applied by default.
fields:
parent: ParentEntity # @ManyToOne (default)
children: List<Child> @OneToMany(mappedBy="parent")
profile: Profile @OneToOne
tags: List<Tag> @ManyToMany
comment: Comment @column(parent_comment_id) # @JoinColumn(name="parent_comment_id")
What a request sends: the key field
The DTO of an @expose / @service entity renders a to-one reference as two fields: the nested
DTO for answers (READ_ONLY in OpenAPI) and the key field for saving. The key field's name is the
field name plus the target's key (batch with key batchId gives batchBatchId; a key named id
gives batchId). In the DDD bundles, a reference to another root uses the target's key name when that
name starts with the reference's name (member -> memberId, item -> itemCode), otherwise the
reference plus the key (book, key isbn -> bookIsbn). If the entity already has a field of that
name, the build warns and keeps the object reference.
A required reference: post: Post!
! makes the column NOT NULL and, in the v3, Kotlin and TypeScript output (from 3.0.261022-rc1),
the key field required and the nested DTO optional:
Comment:
meta: entity @root @expose(20) -- comment
fields:
commentId: long @id @sequence
post: Post! -- parent post
@Schema(description = "parent post" , accessMode = Schema.AccessMode.READ_ONLY )
private PostDTO post; // for answers - optional in a request
@Schema(description = "postPostId" , requiredMode=Schema.RequiredMode.REQUIRED )
@NotNull
private Long postPostId; // required in a save request
post?: PostDTO;
postPostId: number;
A save request sends {"postPostId": 1, ...}; without it, 400. The 2.x generator and the
jpa-springboot output are unchanged (there the nested post carries @NotNull, so a request sends
both). A DTO you declare in dto.yml writes ! on its own field: postId: long! @ref(post.postId).
A reference that does not exist: 404
In the DDD bundles a key that names no row ("productId": 99) answers 404
Product 99 does not exist (code reference.OrderLine.product), checked before saving
(requireReferences) - do not look it up yourself.
References between aggregates (DDD bundles)
A to-one reference to another root becomes an id field with a read-only join on the same column;
the aggregate cannot write through the other root. See DDD. Write
@objectref to keep it an object reference. @updatable(false) on the reference keeps the stored
id on updates - a review stays on its item.
@ref - a value across a reference
dto:
OrderLineDTO:
meta: dto @template(OrderLine)
fields:
itemName: string @ref(item.name) # the item's name in the answer
On the v3 bundles a @ref into an entity that decides who may write it (@access, @owner,
@password, a field's @access(admin)) is read-only, and a @ref that reads a @password is
left out with a DSL warning. A @ref to the reference's key (@ref(assignee.memberId)) still
changes the row the reference points at.
@fk - foreign key metadata without a relation
code: string @fk(Target) (or @fk(Target.field), @fk(Target.field, group) for composite keys)
records the foreign key on a plain column without generating @ManyToOne - for legacy databases or
to avoid lazy loading.
Value objects: @embeddable
entity:
Address:
meta: entity @embeddable -- address
fields:
street1: string(50)!
city: string(50)!
zipcode: string(10)!
countryCode: string(2)!
CreditCard:
meta: entity @embeddable -- payment card
fields:
creditCardNumber: string(30)! @writeonly # accepted, never sent back
creditCardType: CreditCardType!
creditCardExpDate: string(5)! # MM/YY
Customer:
fields:
homeAddress: Address! @Embedded # required value
PurchaseOrder:
fields:
deliveryAddress: Address @Embedded # optional value
creditCard: CreditCard! @Embedded
- A value object's columns live in the table of the entity that holds it.
- In the v3 bundles the DTO carries the value object as a nested object, with the value object's
checks and JSON rules on its fields (
@NotNull,@Size,@JsonProperty(access = WRITE_ONLY)for@writeonly) and@Validon the DTO's field. - In the DDD bundles a value object is immutable: no setters, a
withXxx()per field for a changed copy (address.withCity("Rotterdam")). - Optional values (
deliveryAddress: Address @Embedded, no!): from 3.0.261022-rc1 the value object's!fields do not make NOT NULL columns in that table. When the value is sent its required fields are checked (400); a row without the value is stored. Before, an order without a delivery address failed withNULL not allowed for column "ZIPCODE". - A value object is replaced as a whole. Read one with a
@writeonlyfield and save it back, and that field comes back empty - use@writeonlyfor values taken only when created. - Typed id wrappers (an
OrderIdkey class) are not supported.