본문으로 건너뛰기
ElastiCORE 3.0 은 릴리스 후보(RC)입니다: Maven Central 의 3.0.261022-rc1. 2.2 라인 프로젝트는 2.2 문서를 보세요.
버전: 3.0

첫 룰셋 만들기

펫스토어 프로젝트(헥사고날, Java)에 팀 규칙 모듈 ps-rules 를 붙입니다. 이 장을 마치면 모든 애그리거트 루트에 jMolecules 의 @AggregateRoot 가, 모든 식별자 필드에 @Identity 가 생성됩니다.

1. 규칙은 별도 모듈에 둡니다​

./gradlew build 는 애플리케이션을 컴파일하기 전에 코드를 생성합니다. 그래서 규칙이 애플리케이션 소스 안에 있으면 생성기가 볼 수 없습니다. 규칙은 먼저 컴파일되는 별도 모듈에 둡니다.

elasticore-petstore/
├── settings.gradle ← include 'ps-rules'
├── build.gradle ← 규칙을 생성기에 연결
├── ps-rules/ ← 새 모듈 (이름은 자유)
│ ├── build.gradle
│ └── src/
│ ├── main/java/com/example/rules/PetstoreRules.java
│ ├── main/resources/elasticore-template/v3/ ← 새 파일의 스켈레톤 (5장)
│ └── test/java/com/example/rules/PetstoreRulesTest.java
└── src/ … ← 애플리케이션
settings.gradle
include 'ps-rules'
ps-rules/build.gradle
plugins {
id 'java-library'
}

java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }

repositories {
mavenCentral()
}

dependencies {
// The generator runs these rules; it is not part of the application.
// The same version as the io.elasticore plugin.
compileOnly 'io.elasticore:elasticore-base:3.0.261022-rc1'

testImplementation 'io.elasticore:elasticore-base:3.0.261022-rc1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2'
}

tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' }
tasks.named('test') { useJUnitPlatform() }

elasticore-base 버전은 플러그인(id 'io.elasticore' version '…')과 같게 적습니다. 규칙이 컴파일된 API 와 생성기가 다르면 생성 중에 NoSuchMethodError 가 납니다. compileOnly 인 이유: 실행할 때는 생성기가 자기 elasticore-base 를 갖고 있습니다.

2. 생성기에 연결하기​

build.gradle
elasticore {
architecture = 'hexagonal'
language = 'java'
springBoot = '4'
rules = ['ossie-export', 'com.example.rules.PetstoreRules'] // 번들 뒤에 더해짐
}

dependencies {
// The team rules: on the generator's classpath only, not in the application.
elasticore project(':ps-rules')
// What the team rules put on the classes (@AggregateRoot, @ValueObject, @Identity).
implementation 'org.jmolecules:jmolecules-ddd:1.10.0'
// ... 기존 의존성 그대로
}
줄뜻
rules = [...]룰셋 클래스의 전체 이름. architecture·language 가 고른 번들 뒤에 더해집니다 - 번들을 다시 적지 않습니다. 펫스토어는 이미 ossie-export 를 쓰고 있어 그 뒤에 붙였습니다
elasticore project(':ps-rules')elasticore 는 생성기의 클래스패스입니다. 애플리케이션 jar 에는 들어가지 않고, 이 모듈은 생성보다 먼저 컴파일됩니다
jmolecules-ddd규칙이 붙이는 어노테이션은 애플리케이션이 컴파일할 때 필요합니다

architecture 를 꼭 함께 쓰세요. 없으면 2.x 호환 생성기로 생성되어 규칙 엔진이 돌지 않습니다.

3. 첫 규칙​

ps-rules/src/main/java/com/example/rules/PetstoreRules.java
package com.example.rules;

import io.elasticore.base.model.pub.rules.AnnoCode;
import io.elasticore.base.model.pub.rules.CodeRules;
import io.elasticore.base.model.pub.rules.EntitySpec;
import io.elasticore.base.model.pub.rules.FieldSpec;
import io.elasticore.base.model.pub.rules.Rule;

import static io.elasticore.base.model.pub.rules.Ctx.ctx;

/** The pet store team's rules for the code ElastiCORE generates. */
public class PetstoreRules implements CodeRules {

private static final String JMOLECULES = "org.jmolecules.ddd.annotation.%s";

/** A root gets @AggregateRoot, an @embeddable gets @ValueObject; an @abstract base class gets neither. */
@Rule("ps-aggregate-root")
void aggregateRoot(EntitySpec e) {
if (e.hasMeta("abstract")) return;
if (e.hasMeta("embeddable")) {
ctx(e).ensureClassAnnotation(AnnoCode.of(JMOLECULES, "ValueObject"));
} else if (e.aggregateRoot()) {
ctx(e).ensureClassAnnotation(AnnoCode.of(JMOLECULES, "AggregateRoot"));
}
}

/** Every @id field gets @Identity. */
@Rule("ps-identity")
void identity(EntitySpec e, FieldSpec f) {
if (f.hasDsl("id")) {
ctx(e).field(f).ensure(AnnoCode.of(JMOLECULES, "Identity"));
}
}
}
코드뜻
implements CodeRules표식 인터페이스. 구현할 메서드는 없습니다. 클래스는 public 이고 인자 없는 생성자가 있어야 합니다
@Rule("ps-aggregate-root")이 메서드는 규칙. 괄호 안은 규칙 id - 팀 접두사(ps-)로 기본 규칙과 겹치지 않게
(EntitySpec e)엔티티 파일마다 한 번 불림
(EntitySpec e, FieldSpec f)필드마다 한 번 불림
e.aggregateRoot(), e.hasMeta("embeddable")YAML 의 @root, @embeddable 을 읽음
ctx(e)지금 만들고 있는 파일
ensureClassAnnotation(...)클래스 어노테이션. 이미 있으면 두 번 붙이지 않고, import 는 자동
ctx(e).field(f).ensure(...)그 필드에 어노테이션
AnnoCode.of(JMOLECULES, "Identity")붙일 어노테이션 - 전체 이름 (%s 포맷 가능)

target 을 적지 않았으므로 두 규칙 모두 엔티티 파일(entity)에 기여합니다.

4. 연결됐는지 보기​

./gradlew elcore3 -PlistRules

파일을 만들지 않고, 도메인마다 지금 도는 규칙을 실행 순서대로 보여 줍니다. 펫스토어에서 실제로 나온 것(일부):

[rules] petstore: hexagonal-springboot + ossie-export + com.example.rules.PetstoreRules
entity
0 head access-signin-check ServiceAccessRules
3 head ddd-reference-context DddReferenceRules
5 head v3-class-annotations JpaEntityAnnotationRules
6 head v3-lombok JpaEntityFileRules
7 head v3-auditing-listener JpaEntityAnnotationRules
8 head ddd-behavior-base DddBehaviorRules
0 head ps-aggregate-root PetstoreRules
5 field jpa-field-desc JpaEntityBaseRules
10 field jpa-id JpaEntityBaseRules
…
71 field ddd-state-initial DddStateRules
0 field ps-identity PetstoreRules
80 tail ddd-value-object DddValueObjectRules
82 tail ddd-reference DddReferenceRules
  • 첫 줄에 com.example.rules.PetstoreRules 가 보이면 연결된 것입니다.
  • 우리 규칙은 order 가 0 이어도 각 단계(head, field)에서 ElastiCORE 규칙이 모두 끝난 뒤에 돕니다. 그래서 기본 규칙이 붙인 것을 빼거나 그 위에 더할 수 있습니다.
  • 표의 칸: target(entity) · order · 단계(head/field/tail, 도메인·enum 단위는 once) · 규칙 id · 클래스.

5. 생성된 코드​

./gradlew build

Customer.java (생성기 파일, 매번 다시 씀):

@EntityListeners(AuditingEntityListener.class)
@AggregateRoot // ps-aggregate-root
public class Customer implements java.io.Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "customer_id")
@Identity // ps-identity
private Long customerId;

import org.jmolecules.ddd.annotation.AggregateRoot; 도 생성기가 넣습니다. Address(@embeddable)에는 @ValueObject 가 붙습니다.

개발자 파일에는 알림이 나옵니다​

PurchaseOrder 는 두 파일로 나뉜 루트입니다. 필드가 있는 PurchaseOrderBase.java 는 생성기 것이라 규칙이 그대로 적용되지만, 업무 로직을 쓰는 PurchaseOrder.java 는 개발자 것이라 생성기가 다시 쓰지 않습니다. 대신 생성 로그가 빠진 클래스 어노테이션을 알려 줍니다:

    behavior     src/main/java/io/elasticore/sample/petstore/domain/model/PurchaseOrder.java  SKIP(user-owned)
[elcore3] src/main/java/io/elasticore/sample/petstore/domain/model/PurchaseOrder.java: the rules also put these on the class, which this file does not have - it is yours, so add them by hand if they apply:
@AggregateRoot

알림대로 한 번 직접 붙이면 됩니다. 규칙을 넣은 뒤 새로 만들어지는 루트에는 처음부터 붙어서 나옵니다.

src/main/java/io/elasticore/sample/petstore/domain/model/PurchaseOrder.java
import org.jmolecules.ddd.annotation.AggregateRoot;

@AggregateRoot
@Entity
@Table(name = "ps_purchase_order")
public class PurchaseOrder extends PurchaseOrderBase {

다음​