Java and Kotlin
language = 'kotlin' generates the same model as Kotlin entities, services and controllers in
src/main/kotlin, with the -kt edition of the chosen bundle (jpa-springboot-kt,
ddd-springboot-kt, hexagonal-springboot-kt). The rules are shared; only how each language says
it differs.
elasticore {
architecture = 'hexagonal'
language = 'kotlin' // java | kotlin
}
The same lifecycle in both languages
The Ticket of the Kotlin sample has two lifecycles, and its cancel command moves both:
Ticket:
meta: entity @root @expose(20) @owner(agentId) @access(read=owner, create=user, close=owner) @event(Closed) -- 작업
- Kotlin (generated)
- Java (the same rule)
open fun cancel() {
requireStatus("cancel", TicketStatus.OPEN)
requireBilling("cancel", BillingStatus.UNBILLED)
this.status = TicketStatus.CLOSED
this.billing = BillingStatus.VOID
raiseClosed()
}
/** Whether cancel may be given now: the status is OPEN and the billing is UNBILLED. */
fun canCancel(): Boolean = (this.status == TicketStatus.OPEN) && (this.billing == BillingStatus.UNBILLED)
public void cancel() {
requireStatus("cancel", ShipmentStatus.READY); // 1) every lifecycle is checked first
requireFee("cancel", FeeStatus.PAID);
this.status = ShipmentStatus.CANCELLED; // 2) all pass, then all move
this.fee = FeeStatus.REFUNDED;
raiseCancelled(); // 3) an event per target state, when declared
raiseRefunded();
}
What differs
| Java | Kotlin | |
|---|---|---|
| Event classes | fields + constructor + getters | vals of the primary constructor (data class) |
| Specifications | private constructor | object |
| Factories | private constructor + static | object + fun |
| Ports of domain services and process managers | @Autowired protected field on XxxBase | @Autowired protected lateinit var |
| Conditions | order.getItems() | order.items!! |
| Invariant listeners | @EntityListeners({A.class, B.class}) | @EntityListeners(A::class, B::class) |
| Value objects | immutable through Lombok | immutable through the language |
| Sealed setters | @Setter(AccessLevel.PROTECTED) | protected set / final var ... private set |
| Signatures | nullability matches the service (save(dto: OrderDTO): OrderDTO?) |
A rule that only one language has says so (requireSyntax) and stops with the reason when selected
for the other - Java never ends up silently inside a .kt file.
What a Kotlin build needs
The Kotlin plugins a Kotlin JPA project applies, as in elasticore-template-kt (the sample takes
the io.elasticore plugin from the repository itself; a project names the released version):
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.9.24'
id 'org.jetbrains.kotlin.plugin.allopen' version '1.9.24'
id 'org.jetbrains.kotlin.plugin.jpa' version '1.9.24'
id 'io.spring.dependency-management' version '1.1.6'
id 'io.elasticore' version '3.0.261022-rc1'
}
org.jetbrains.kotlin:kotlin-reflect- a Kotlin application on Spring needs it.- With Spring Boot 4: Kotlin 2.2 or later, and the Jackson Kotlin module is
tools.jackson.module:jackson-module-kotlin. - The generated code boots without the
kotlin-springplugin: extension services and repository adapters areopen class, and entity collections areMutableList<X>. mapping: xml(JPA annotations moved toorm.xml): registerio.elasticore.springboot3.entity.PersistentModelwithallOpenandnoArg.`in`- the hexagonal packageadapter.in.webis written with the keyword escaped.
Where each reaches
Most rules reach both languages. @search(like) is case-insensitive in every Kotlin build (and in
the v3 Java bundles); @restExclude removes methods from the Kotlin controller and use case while the
Kotlin service keeps them.
Sample: elasticore-template-kt -
generated, compiled and started on every build (ShopApiContractTest).