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

어노테이션과 메서드

규칙이 생성 코드에 하는 일의 대부분은 "어디에 무엇을 붙이거나 뺀다" 입니다. 모두 ctx(spec) 이 돌려주는 지금 만드는 파일(SourceCode)에 말합니다. import 정리, 줄 배치, Java/Kotlin 문법은 생성기가 합니다.

어디에​

어디에붙이기빼기
클래스ctx(e).ensureClassAnnotation(anno)ctx(e).removeClassAnnotation("a.b.C")
필드ctx(e).field(f).ensure(anno)ctx(e).field(f).removeAnnotation("a.b.C")
메서드 (이름으로)ctx(e).method("name").ensure(anno)-
필드 자체-ctx(e).removeField(f)
파일 자체-ctx(e).dontMake()

빼는 쪽은 기본 규칙이 붙인 것도 뺍니다. 팀 규칙은 항상 기본 규칙 뒤에 돌기 때문입니다.

    /** Takes the @DynamicUpdate the built-in rules put on every entity back off. */
@Rule("ps-no-dynamic-update")
void noDynamicUpdate(EntitySpec e) {
ctx(e).removeClassAnnotation("org.hibernate.annotations.DynamicUpdate");
}

펫스토어에서 이 규칙은 생성기 파일(Customer.java, PurchaseOrderBase.java …)뿐 아니라 나뉜 루트의 개발자 클래스 (PurchaseOrder.java, OrderLine.java)에서도 @DynamicUpdate 를 뺐습니다. 생성기는 개발자 클래스의 테이블 어노테이션 (@Entity, @Table, @DynamicUpdate …)을 계속 DSL 에 맞추고, 본문과 개발자가 붙인 다른 어노테이션은 그대로 둡니다.

필드: ensure, add, merge, replace​

메서드같은 어노테이션이 이미 있으면없으면언제
ensure(anno)아무것도 안 함붙임대부분
add(anno)하나 더 붙임붙임반복 가능한 어노테이션
merge(anno)속성만 더함붙임기본 규칙이 붙인 @Schema, @Column 에 속성 하나 더하기
replace(anno)이것으로 바꿈붙임기본 규칙의 값을 팀 값으로

DTO 필드에는 ElastiCORE 가 이미 @Schema(description = ...) 를 붙이므로, hidden = true 를 더하려면 merge 입니다. ensure 로 쓰면 아무 일도 일어나지 않습니다 - 대상 파일의 예.

    /** A field marked @pii in the DSL is accepted in requests but never sent back in responses. */
@Rule(value = "ps-pii", target = "dto")
void pii(EntitySpec dto, FieldSpec f) {
if (f.hasDsl("pii")) {
ctx(dto).field(f).ensure(AnnoCode.of("com.fasterxml.jackson.annotation.JsonProperty")
.valRaw("access", "JsonProperty.Access.WRITE_ONLY"));
}
}
CustomerDTO.java
	@Schema(description = "email address"  )
@Size(max=100)
@Email(message = "Email format invalid")
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY) // ps-pii
private String email;

실행해서 확인: 회원 가입(POST /api/petstore/customer)에 email 을 보내면 저장되고, 응답 JSON 에는 email 이 없습니다.

{"customerId":3,"firstName":"Rule","homeAddress":{…},"lastName":"Demo","login":"rulesdemo3","role":"U"}

AnnoCode - 어노테이션 만들기​

어노테이션을 문자열로 이어 붙이지 마세요("@Column(name = \"" + n + "\")"). AnnoCode 가 따옴표, import, Java 의 {} 와 Kotlin 의 [] 를 맡습니다. 그래서 규칙에 if (kotlin) 이 필요 없습니다.

코드결과
AnnoCode.of("jakarta.validation.constraints.Size").val("min", 2)@Size(min = 2)
.val("name", "user_id")name = "user_id" - 문자열은 따옴표가 붙음
.val("hidden", true)hidden = true
.valRaw("access", "JsonProperty.Access.WRITE_ONLY")그대로 - 상수, 클래스 리터럴
.valRaw("length", "%d", 200)포맷
.value("CreditCheckPort")이름 없는 속성 - @ExternalSystem("CreditCheckPort")
AnnoCode.of("org.jmolecules.ddd.annotation.%s", "Entity").qualified()@org.jmolecules.ddd.annotation.Entity - jakarta.persistence.Entity 와 이름이 겹칠 때 전체 이름으로

어노테이션 클래스가 생성기 클래스패스에 있을 필요는 없습니다. 규칙은 이름만 쓰고, 그 클래스는 애플리케이션이 컴파일할 때 필요합니다(implementation 'org.jmolecules:jmolecules-ddd:…').

메서드에 붙이기 - method("이름")​

생성되는 파일의 메서드에 이름으로 붙입니다. repository.yml 의 쿼리 메서드, port.yml 의 외부 호출 메서드처럼 DSL 에 이름이 있는 메서드에 씁니다. 예제는 대상 파일.

메서드 더하기 - addMethod​

규칙이 손으로 쓴 메서드를 클래스 끝(닫는 중괄호 앞)에 더합니다. 모든 루트에 같은 도우미 메서드가 필요할 때:

    /** Every root entity class says who it is in an audit log: "Customer#42". */
@Rule("ps-audit-label")
void auditLabel(EntitySpec e) {
if (!e.aggregateRoot() || e.hasMeta("abstract") || e.pkFields().size() != 1) return;
FieldSpec id = e.pkFields().get(0);
ctx(e).addMethod(String.format(
"\t/** Who this is in an audit log: %s#<id>. */%n"
+ "\tpublic String auditLabel() {%n"
+ "\t\treturn \"%s#\" + get%s();%n"
+ "\t}", e.getName(), e.getName(), id.capitalName()));
}
Customer.java
	/** Who this is in an audit log: Customer#<id>. */
public String auditLabel() {
return "Customer#" + getCustomerId();
}
}
  • 메서드 본문은 규칙이 쓴 그대로 들어가므로 언어를 가립니다. Kotlin 프로젝트에서도 쓰려면 ctx(e).syntax() 로 나누거나 Syntax 에 물어 쓰세요.
  • 엔티티처럼 생성기가 매번 다시 쓰는 파일에 씁니다. 개발자 파일(컨트롤러, XxxService)은 처음 만들어질 때만 규칙이 적용됩니다.
  • 두 파일로 나뉜 루트(PurchaseOrderBase + 개발자의 PurchaseOrder)에서는 생성기 쪽 PurchaseOrderBase 에만 들어가고, 개발자 클래스는 그것을 상속받습니다. 개발자 클래스에만 더하려면 @Rule(target = "behavior") 로 쓰세요 - 처음 생성될 때만 적용됩니다.
  • enum(target = "enum")에도 더할 수 있습니다. 상수 목록과 생성자 뒤에 들어갑니다.
  • 3.0.261022-rc1 에는 메서드가 하나도 없는 클래스(필드만 있는 엔티티)에서 addMethod 가 버려지고, 나뉜 루트의 개발자 클래스에도 한 번 더 들어가는 버그가 있습니다. 다음 릴리스에서 고쳐졌습니다. rc1 에서는 식별자 필드 뒤의 멤버로 쓰면 됩니다: ctx(e).field(id).after("public String auditLabel() {").after("\treturn …;").after("}") (필드 규칙에서).

엔드포인트 빼기 - excludeMethod​

DSL 에 이미 @restExclude(deleteById), @readonly 가 있습니다. 팀만의 단어로 같은 일을 하고 싶을 때, 생성되는 컨트롤러의 엔드포인트를 이름으로 뺍니다.

이름엔드포인트
createPOST /
readGET /{id}
searchPOST /search
update, batchUpdatePUT /, PUT /batch
deleteById, deleteBatchDELETE /{id}, POST /delete
schemaInfoGET /schema/info

deleteById 를 빼면 그것을 부르는 deleteBatch 도 함께 빠지고, update 를 빼면 batchUpdate 도 빠집니다.

    /** meta @nodelete: the REST API has no delete endpoints. */
@Rule(value = "ps-no-delete", target = "control")
void noDelete(EntitySpec e) {
if (e.hasMeta("nodelete")) ctx(e).excludeMethod("deleteById");
}
model/entity.yml
  Category:
meta: entity @root @editable @expose(20) @access(read=public) @nodelete

생성된 CategoryController 에는 @DeleteMapping 과 POST /delete 가 없고, 실행하면 DELETE /api/petstore/category/1000 과 POST /api/petstore/category/delete 가 405 입니다.

컨트롤러에만

팀 규칙의 excludeMethod 는 컨트롤러(target = "control")에 쓰세요. 헥사고날의 유스케이스 포트(CategoryUseCase)는 팀 규칙보다 먼저 만들어지므로, 서비스(service)에서 deleteById 를 빼면 CategoryService implements CategoryUseCase 가 컴파일되지 않습니다. 서비스와 포트에서까지 빼려면 DSL 의 @restExclude(deleteById) 를 쓰세요 - 기본 규칙이 직접 읽어 모든 파일에서 함께 뺍니다.

컨트롤러는 개발자 파일입니다. 이미 고친 컨트롤러에는 적용되지 않고, 한 번도 고치지 않은 컨트롤러는 다음 생성에 맞춰집니다.

필드의 선언 바꾸기​

FieldCode 는 어노테이션 외에도 선언을 바꿉니다. 대부분은 DSL 로 하는 편이 낫고, 팀 전체의 관례일 때만 규칙으로 하세요.

메서드하는 일
type("String")선언 타입
rename("x")이름
initial("0")초기값
comment("…")필드 주석
before("…")선언 위의 줄
after("…")선언 뒤의 멤버 (여러 번 부르면 줄이 이어짐)