첫 룰셋 만들기
펫스토어 프로젝트(헥사고날, 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/ … ← 애플리케이션
include 'ps-rules'
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. 생성기에 연결하기
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. 첫 규칙
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
알림대로 한 번 직접 붙이면 됩니다. 규칙을 넣은 뒤 새로 만들어지는 루트에는 처음부터 붙어서 나옵니다.
import org.jmolecules.ddd.annotation.AggregateRoot;
@AggregateRoot
@Entity
@Table(name = "ps_purchase_order")
public class PurchaseOrder extends PurchaseOrderBase {
다음
- 어노테이션을 빼고, 속성을 더하고, 메서드에 붙이고, 메서드를 더하기: 어노테이션과 메서드
@pii처럼 팀만의 DSL 단어와 빌드 검사: DSL 단어와 모델 검사- 매번 빌드하지 않고 규칙만 테스트하기: 테스트