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

대상 파일 (target)

YAML 의 엔티티 하나(PurchaseOrder)에서 파일이 여러 개 만들어집니다. 파일 종류마다 이름이 있고, 그 이름이 target 입니다. 규칙은 @Rule(target = "...") 으로 어느 파일에 기여할지 정합니다. 적지 않으면 entity 입니다.

어떤 파일이 어떤 target 인지는 생성 로그 각 줄의 첫 단어입니다:

dto       src/main/java/io/elasticore/sample/petstore/application/dto/CustomerDTO.java  WROTE
srchdto src/main/java/io/elasticore/sample/petstore/application/dto/CustomerSrchDTO.java WROTE

YAML 파일과 target, 그리고 메서드 모양​

YAML만들어지는 파일target규칙 메서드규칙이 읽는 것
entity.ymlCustomer.javaentity (기본)(EntitySpec e), (EntitySpec e, FieldSpec f)엔티티, 필드
entity.yml, dto.ymlCustomerDTO.java, ItemDTO.javadto(EntitySpec dto, FieldSpec f)DTO 이름, 필드
필드의 @search(...)CustomerSrchDTO.javasrchdto(EntitySpec s, FieldSpec f)검색 필드
repository.ymlPurchaseOrderRepository.javarepository(EntitySpec e)e.queries()
port.ymlCreditCheckPort.javaport(PortSpec p)p.operations()
enum.ymlOrderStatus.javaenum(EnumSpec en)en.constants()

dto.yml 의 DTO 도 EntitySpec 으로 받습니다. 엔티티와 같은 방법으로 이름과 필드를 읽습니다.

누가 그 파일을 관리하나​

규칙은 생성기가 쓰는 파일에 매번 적용됩니다. 개발자가 채워 넣는 파일은 처음 한 번만 생성되고 그 뒤로는 개발자 것이라, 규칙은 처음 만들어질 때만 적용됩니다.

파일누구 것규칙을 나중에 더하면
entity, dto, srchdto, enum, repository, port, mapper, XxxCoreService, 포트·유스케이스생성기다음 생성에 반영
나뉜 루트의 개발자 클래스 (PurchaseOrder.java, target behavior)개발자빠진 클래스 어노테이션을 생성 로그로 알려 줌 - 직접 붙임
control, XxxService(service_extend), factory, 도메인 서비스개발자알림 없음. 한 번도 고치지 않은 파일이면 다시 써 줌

생성기 것을 손으로 고치면 다음 생성 때 건너뛰고 알려 줍니다(-Pforce 로 덮어씀). 바꾸고 싶은 것은 규칙으로 하세요.

DDD·헥사고날에서 더 생기는 파일​

target예 (펫스토어)누구 것
entityCustomer.java, 나뉜 루트는 PurchaseOrderBase.java생성기
behaviorPurchaseOrder.java개발자
eventPurchaseOrderPlacedEvent.java생성기
repositoryPort, queryPort, usecaseCustomerRepositoryPort.java, CustomerUseCase.java생성기
invariants, specifications, repositoryAdapterCustomerRepositoryAdapter.java …생성기
service / service_extendCustomerCoreService.java / CustomerService.java생성기 / 개발자
controlCustomerController.java개발자
mapper, apiErrorsPetstoreMapper.java, PetstoreApiErrors.java생성기 / 개발자 (도메인 단위)

target = "entity" 인 팀 규칙은 나뉜 루트의 두 파일 모두에 적용됩니다. 나뉘었는지 신경 쓰지 않아도 됩니다. 전체 목록은 ./gradlew elcore3 -PlistRules 가 그 프로젝트의 것을 보여 줍니다.

파일 단위(once)로 만들어지는 도메인 파일의 규칙은 (DomainSpec d) 를 받습니다. (EntitySpec e) 로 쓰면 돌지 않고 경고가 나옵니다.

dto.yml - 내부용 필드를 API 문서에서 숨기기​

model/dto.yml
  ItemDTO:
meta: dto @template(Item)
fields:
productName: string @ref(product.name) @internal # 우리 표시
    /** dto.yml: a field marked @internal is left out of the API docs. */
@Rule(value = "ps-internal", target = "dto")
void internal(EntitySpec dto, FieldSpec f) {
if (f.hasDsl("internal")) {
ctx(dto).field(f).merge(AnnoCode.of("io.swagger.v3.oas.annotations.media.Schema")
.val("hidden", true));
}
}

DTO 필드에는 ElastiCORE 가 이미 @Schema(description = ...) 를 붙입니다. ensure 는 같은 어노테이션이 있으면 아무것도 하지 않으므로(테스트로 확인), 속성을 더하려면 merge 를 씁니다. ItemDTO.java:

	@Schema(description = "productName", hidden = true)   // hidden 을 규칙이 더함
private String productName;

repository.yml - 조회 쿼리는 읽기 전용, SELECT * 금지​

model/repository.yml
repository:
PurchaseOrder:
methods:
- id: summarizeByStatus
return: List<Object[]>
query: "SELECT o.status, COUNT(o) FROM PurchaseOrder o GROUP BY o.status"
    /** repository.yml: a query that only reads runs read-only; "SELECT *" is refused. */
@Rule(value = "ps-queries", target = "repository")
void queries(EntitySpec e) {
for (QuerySpec query : e.queries()) {
if (query.sql() == null) continue; // Spring Data derives it from the method name
if (query.sql().toLowerCase().replaceAll("\\s+", " ").contains("select *")) {
ModelCheck.fail(e.getName() + "." + query.name() + ": name the columns instead of SELECT *");
}
if (query.isSelect()) {
ctx(e).method(query.name()).ensure(
AnnoCode.of("org.springframework.transaction.annotation.Transactional").val("readOnly", true));
}
}
}
    @Query(nativeQuery=false, value=" SELECT o.status, COUNT(o) FROM PurchaseOrder o GROUP BY o.status")
@Transactional(readOnly = true) // 규칙이 붙임
List<Object[]> summarizeByStatus();

ctx(e).method("이름") 은 그 이름의 메서드 모두에 붙입니다. 없는 이름이면 파일은 그대로 두고 생성 로그에 알려 줍니다:

[rule] method 'summarizeByState' is not declared in PurchaseOrderRepository (repository file); its annotations were not written

port.yml - 외부 호출에 표시 달기​

model/port.yml
port:
CreditCheckPort:
meta: http @url("https://credit.example.com/api")
methods:
checkCredit:
meta: method @get("/partners/{bizNo}/credit")
params:
bizNo: string
return: CreditResultDTO
    /** port.yml: every HTTP call is marked with the system and the endpoint it reaches. */
@Rule(value = "acme-external-call", target = "port")
void externalCall(PortSpec port) {
ctx(port).ensureClassAnnotation(AnnoCode.of("com.acme.support.ExternalSystem").value(port.getName()));
for (PortSpec.Operation op : port.operations()) {
if (op.url() != null) {
ctx(port).method(op.name()).ensure(AnnoCode.of("com.acme.support.ExternalCall")
.value(op.httpMethod() + " " + op.url()));
}
}
}
@ExternalService(protocol="http", id="erp.CreditCheckPort", url="https://credit.example.com/api", ...)
@ExternalSystem("CreditCheckPort") // 규칙
public interface CreditCheckPort {

@HttpEndpoint(url="/partners/{bizNo}/credit", method="GET", ...)
@ExternalCall("GET /partners/{bizNo}/credit") // 규칙
CreditResultDTO checkCredit(String bizNo);
}

@ExternalSystem, @ExternalCall 은 애플리케이션에 직접 만든 어노테이션입니다. 규칙은 어노테이션 이름만 알면 되고, 그 클래스가 생성기 클래스패스에 있을 필요는 없습니다.

enum.yml - API 문서에 enum 을 한 번만​

    /** enum.yml: every enum is described once in the API docs and referenced from there. */
@Rule(value = "ps-enum-ref", target = "enum")
void enumRef(EnumSpec enumeration) {
ctx(enumeration).ensureClassAnnotation(AnnoCode.of("io.swagger.v3.oas.annotations.media.Schema")
.val("enumAsRef", true));
}

펫스토어의 OrderStatus.java:

@Schema(enumAsRef = true)          // ps-enum-ref
@Getter
public enum OrderStatus {

enum 규칙은 enum 마다 한 번(once) 불리고, 필드 모양 (EnumSpec, FieldSpec) 은 없습니다. 값들은 enumeration.constants() 로 읽습니다 (c.name() → "PLACED", c.values() → 그 값의 열들).