Repository navigation
Releases: xsko/ElastiCORE
Releases · xsko/ElastiCORE
Release list
ElastiCORE 3.0.261022-rc1
같은 요구사항(도서·회원·대출 API)을 에이전트가 ElastiCORE 로 만들게 하고 어디서 Java 를 손으로 쓰는지 보았다. 네 군데였다:
서버가 정하는 값(반납일)을 클라이언트가 보내도 막을 수 없었고, "아직 반납하지 않은" 검색을 DSL 로 쓸 수 없었고,
ISBN 13자리 같은 정규식 검사는 @pattern 이 컴파일되지 않는 코드를 냈고, member: Member! 는 키(memberId)가 아니라
중첩 객체를 요구했다. 아래 네 항목이 그 자리를 DSL 로 메운다. 네 항목 모두 그 문법을 쓰지 않는 프로젝트의 생성 결과는
바뀌지 않고, 2.x 생성기와 2.x 와 같은 출력을 내는 jpa-springboot 번들은 그대로다(elcoreRulesCheck -Pstrict 150 MATCH /
0 DIFF / 2 ACCEPTED, elcoreTreeCheck 0 differ / 0 missing / 0 extra).
New Features (DSL - 요구사항을 손코딩 없이)
필드 @readonly 는 서버가 정하는 값 - v3 매퍼가 DTO 에서 엔티티로 쓰지 않는다
- 필드
@readonly(별칭@noinput)는 지금까지 DTO 에@JsonProperty(access=READ_ONLY)만 붙였다. JSON 요청의 값은 Jackson
이 버렸지만, v3 매퍼는 서버 코드가 DTO 에 넣은 값(dto.setX(..)후service.save(dto))을 그대로 엔티티에 썼고,!가
붙은@readonly필드는 DTO 의@NotNull이 남아 그 값을 보낼 수 없는 생성 요청을 모두 400 으로 거절했다. DDD 의 나뉜
루트에서는 매퍼가 요청 값을to.creation()::setX로 새 루트에 넣었다 - 이제 v3 번들(
jpa-springboot-v3·DDD·헥사고날·Kotlin)에서@readonly/@noinput필드는- 매퍼의 DTO -> 엔티티 방향(
toEntity,mapping(dto, entity, ...), EntityManager 판 포함)이 쓰지 않는다. 엔티티 -> DTO
방향은 그대로라 응답에 나간다. 엔티티 필드에 붙이면 그 엔티티의 모든 DTO 에,dto.yml의 DTO 필드에 붙이면 그 DTO 의
매핑에만 적용된다.@id에 붙은@readonly는 식별자 쓰기를 그대로 둔다 - DTO 의
@NotNull·@NotBlank를 뺀다. 엔티티 컬럼(nullable = false)은 그대로라 서버가 채운다 - DTO 의
@Schema에accessMode = Schema.AccessMode.READ_ONLY를 더한다. springdoc 은@JsonProperty만으로도
readOnly: true를 냈다 -@Schema만 읽는 도구를 위해 적는다 - 나뉜 루트에서는 여전히 잠긴 필드다:
protectedsetter 와creation()setter 가 남아 서버 코드가 새 루트에 넣는다
- 매퍼의 DTO -> 엔티티 방향(
- v3 동작 변경: 필드에
@readonly를 붙이고 서버 코드에서 DTO 에 값을 넣어 저장하던 코드는 그 값이 더는 저장되지 않는다.
엔티티에 넣는다(확장 서비스의beforeSave, 루트의 메서드). HTTP 동작은 같다 - JSON 의 값은 이전에도 버려졌다. 이유:
@readonly는 "요청으로 받지 않는다" 는 선언인데 매퍼가 그 선언을 무시해, 서비스가creation().setX(null)같은 우회를
쓰고 있었다(새 루트에서는 아무 일도 하지 않는다).@readonly가 없는 프로젝트의 v3 출력은 바뀌지 않는다 - 가이드: annotation guide 에
@readonly / @noinput절(생성물 표,@behavior와의 비교,creation().setX(null)은 새 루트에서
아무 일도 하지 않는다는 것), §2.8 감사 필드, jsonview 표,@behavior는 나뉜 루트에서만 효과가 있다는 것. dsl guide 필드 표와
@behavior행, rule guide 의 잠긴 루트 표와 '새 루트의 null' - 확인: 규칙 단위 테스트(Java·Kotlin 매퍼, DTO, 2.x 호환 출력, 나뉜 루트,
@editable루트), hexagonal Java·Kotlin 도서관 모델을
RulesGenRunner로 생성해 매퍼·DTO·LoanBase를 읽는 테스트.elasticore-template-ddd의 catalogBook에shelfCode: string(10)! @readonly와 그 값을 채우는beforeSave를 임시로 넣고 Spring Boot 3.3.2 에서 실행해 확인했다: 클라이언트가
값을 보낸 POST·PUT 은 200 이고beforeSave의 값이 남는다.service.save(dto)도 DTO 에 넣은 값 대신beforeSave의 값을
저장한다./v3/api-docs는readOnly: true이다. 변경 전 출력에서는 POST 가 400(shelfCode: must not be null)이었고,
DTO 에 넣은 값이 저장되었다 - 남은 것: DTO 의
@ref(...)필드에 붙인@readonly는 아직 관계 쓰기(reverseRefPass)를 막지 않는다. 나뉜 루트의 필드
@behavior는@JsonPropertyREAD_ONLY 만 붙고@Schema accessMode는 붙지 않는다 - 관련 파일:
WriteGuardRules.java,DtoBaseRules.java,MapperBaseRules.java,ServerSetFieldTest.java,SealedRootTest.java,
ServerSetFieldGenerationTest.java
@search(isnull) - 값이 비어 있는지로 검색한다
- "아직 반납하지 않은 대출", "아직 입고된 적 없는 상품"처럼 값이 비어 있는지로 찾으려면 Service 에
Q.Loan.returnedDate(Op.IS_NULL, null)를 손으로 쓰거나 Repository 에 JPQL 을 따로 두어야 했다.@search(isnull)은
2.x 와 규칙 엔진 모두에서 경고 없이 같은 값 비교(c.equal)로 만들어졌고, 가이드는ISNULL/ISNOTNULL을 미구현으로
적고 있었다 - 이제
returnedDate: date @search(isnull)은 검색 DTO 에Boolean returnedDateEmpty하나를 만든다. 보내지 않거나
null이면 조건 없음,true면c.isNull(r.get("returnedDate")),false면c.isNotNull(...).@Schema설명은
... Field is empty (true) or set (false). field:returnedDate이고, 보내는 값이 날짜가 아니므로@DateTimeFormat·
@JsonFormat·example은 붙지 않는다.@ref(order.shippedDate)와 쓰면r.get("order").get("shippedDate")를
검사하고, 쉼표 뒤 두 번째 값은 다른 조건처럼 엔티티 필드 이름이다(@search(isnull,open)->openEmpty,r.get("open")) - Kotlin(
val xEmpty = searchDTO.xEmpty,if(xEmpty!!),Specification<E> { r,q,c -> c.isNull(...) })과
TypeScript(xEmpty?: boolean)도 같은 규칙에서 나온다 - 필드 하나에 조건 하나:
isnull을 적은 필드는between의 From/To 를 만들지 않는다.@expose의 타입별 자동
할당(날짜 ->between)은 바뀌지 않는다 - v3 번들(
jpa-springboot-v3·DDD·헥사고날·Kotlin·ts-model) 전용이다. 2.x 생성기와 2.x 호환 번들(jpa-springboot)은
이전처럼 같은 값 비교를 만들고@Schema문구도 그대로다 -StringUtils.getOperatorDescription(op)는 바뀌지 않고 새
문구는getOperatorDescription(op, true)에만 있다 - v3 번들에서 이미
@search(isnull)을 적어 둔 필드는 검색 속성이<필드>(필드 타입, 같은 값 비교)에서
<필드>Empty(Boolean)로 바뀐다. 미구현으로 문서화된 키워드가 조용히 같은 값 비교가 되던 것을 바로잡는 것이며,
저장소의 블루프린트 중 쓰는 곳은 없다 - 확인: 단위 테스트로 Java·Kotlin·TypeScript 출력과 2.x 호환 출력이 그대로인 것을 확인했다.
elasticore-template-ddd
의Product.lastRestocked에 잠시@search(isnull)을 붙여 생성·컴파일하고, H2 위에서
POST /api/v1/shop/product/search가true는 입고일이 없는 상품만,false는 있는 상품만, 생략과null은 둘 다
돌려주는 것을 확인했다.elasticore-template-kt의Project.endDate로 Kotlin 을 생성해compileKotlin이 통과하고
TypeScript 에endDateEmpty?: boolean;이 나오는 것을 확인했다(샘플 변경은 되돌렸다) - 남은 것: List 필드나
!필드에isnull을 적어도 경고하지 않는다. v3 의 별칭 구분자는 쉼표인데 가이드 §3.1 은$로
적고 있다(별개 버그) - 관련 파일:
SearchDtoBaseRules.java,MapperSpecRules.java,StringUtils.java,SearchDtoTest.java,
MapperSpecTest.java,TypeScriptModelTest.java,OperatorDescriptionTest.java,
elasticore-annotation-guide.md(3.2, 3.4.1, 3.8, 8.5),elasticore-dsl-guide.md,elasticore-dev-guide.md
Bug Fixes (생성기)
@pattern 이 컴파일되는 @Pattern 을 쓰고, @min 처럼 값 다음에 메시지를 받는다
- 규칙 엔진의 v3·Kotlin 번들에서
@pattern(regexp="...", message="...")이 컴파일되지 않았다. 네 줄짜리
@Pattern( regexp="...", msg="..." )를 썼는데 Jakarta@Pattern에는msg속성이 없다. 이제 한 줄
@Pattern(regexp = "...", message = "...")을 쓴다 - 위치 형식
@pattern('^[0-9]{13}$', 'ISBN must be 13 digits'),@pattern('^[A-Z]+$')도 읽는다. 이전에는 아무것도
생성하지 않았다. 메시지는 따옴표와 괄호 밖의 첫 쉼표 뒤라서{10,13}은 정규식에 남는다. 메시지를 생략하면 TypeScript
클라이언트와 같은"<라벨> is not in the expected form"을 쓴다. TS 의validate...()도 같은 값을 읽는다.
이전에 아무 검사도 없던 필드(위치 형식, 메시지 없는 이름 형식)에 요청 검사(400)가 새로 생긴다 - Kotlin 은 문자열 템플릿이 되는
$(영문자·_·{앞)를\$로 쓴다.@Pattern은 문자열 필드에만 붙는다. 날짜의
@pattern(yyyyMMdd)는 이전처럼 날짜 형식이다. clean 출력은 문자열 필드의@pattern을 Swaggerexample로 쓰지 않는다
(위치 형식에서는^[0-9]{13}$', 'ISBN must be 13 digits같은 깨진 값이 나왔다) - 정규식은 Java 문자열 리터럴의 내용으로 그대로 들어가므로 백슬래시는
\\d처럼 두 번 쓴다.\d처럼 하나만 쓰면
규칙 엔진이 DSL 경고를 낸다(2.x 추출기는 경고에 실패하므로 규칙 엔진에서만) - 2.x 생성기(
elcore)와 2.x 호환 번들(jpa-springboot)의 출력은 그대로다(msg=, 이름 형식에 메시지가 있을 때만) -
2.x 의@pattern은 여전히 컴파일되지 않는 코드를 내며, 2.x 호환을 위해 손대지 않았다 - 확인: 도서관 모델(
isbn: string(13)! @id @pattern('^[0-9]{13}$', 'ISBN must be 13 digits'))을 layered·Kotlin·TS 로
생성했다. Java DTO 는 javac, Kotlin DTO 는 kotlinc 1.9.24 로 컴파일했고, Hibernate Validator 8 이12345를
"ISBN must be 13 digits" 로 거부하는 것을 확인했다. TS 의validateBook()이 node 에서 같은 메시지를 내는 것도 확인했다 - 남은 것:
=가 든 정규식은 이름 형식으로만 쓸 수 있다. 따옴표 하나로 감싼 이름 형식의 값은 공백에서 잘린다(기존 파서).
DDD 루트의 저장 시점 검사(@min처럼)는 하지 않는다 - 관련 파일:
FieldSpec.java,DtoBaseRules.java,FieldDoc.java,TypeScriptDtoFileRules.java,AbstractModelLoader.java,
PatternAnnotationTest.java,DtoGapCoverageTest.java,elasticore-annotation-guide.md
필수 관계(post: Post!)는 저장 요청에서 FK id 필드로 요구한다
@expose/@service엔티티가 자동 생성하는 DTO 에서!가 붙은 to-one 관계는 중첩 DTO(PostDTO post)에@NotNull·
requiredMode=REQUIRED가 붙고 FK id 필드(postPostId)에는 붙지 않았다. 매퍼는 저장/수정할 때postPostId만 읽으므로
(em.getReference), 키만 보낸 요청은@Valid검증에서 거절되고 중첩 객체만 보낸 요청은 검증을 통과해도 관계를 연결하지
못했다. 요청은 둘 다 보내야 했다- 이제 v3·Kotlin·TypeScript 생성(
jpa-springboot-v3·DDD·헥사고날·Kotlin·TypeScript 번들)에서는 FK id 필드가@NotNull·
requiredMode=REQUIRED이고, 응답용 중첩 DTO(accessMode = READ_ONLY)는 선택이다. TypeScript 는post?: PostDTO;·
postPostId: number;로 선언하고validateXxx가post를 요구하지 않는다.ddd-reference를 쓰는 번들은 ID 필드(memberId)가
이미 필수였고, 중첩 DTO 만 선택이 된다. 키 이름 규칙(필드 + 키, 키가id면topicId)은 그대로다 - 동작 변경 (v3·Kotlin·TypeScript 사용자): 중첩
post만 보내던 클라이언트는 이제@Valid검증에서 거절된다 - FK id 필드를
보내야 한다(그 요청은 이전에도 관계를 연결하지 못했다).postPostId를 함께 보내던 클라이언트는 그대로 동작한다. OpenAPI 의
required 가post에서postPostId로 옮겨 간다 - 바뀌지 않는 것: 2.x 생성기(
elcore)와 2.x 와 같은 출력을 내는jpa-springboot번들(중첩 DTO 에@NotNull그대로), 검색 DTO
(XxxSrchDTO의postPostId는 검색 조건이라 필수가 아니다), Entity(@JoinColumn(nullable = false)), 매퍼,dto.yml에 직접
선언한 DTO.!가 붙은 엔티티 참조가 없는 프로젝트의 생성 결과는 바뀌지 않는다 - 확인:
Post·Comment(post: Post!) 게시판을 v3 layered·Kotlin·TypeScript·호환 모드·2.x 로,Member·Book·Loan
(member: Member!,book: Book!, Book 의 키isbn)을 헥사고날로 생성해, v3CommentDTO의postPostId에@NotNull·REQUIRED
가 붙고post에는 없는 것, 키가id인topic: Topic!의topicId가 필수인 것, 선택 관계의postPostId와CommentSrchDTO의
postPostId가 그대로인 것, 헥사고날LoanDTO의memberId·bookIsbn이 필수이고member·book이 선택인 것, 호환 모드 출력이
2.x 와 같은 것을 테스트로 확인했다. DTO 와 검색 DTO 가 함께 쓰는 키 필드는 복사본에만@notnull을 붙여, 어느 DTO 를 먼저
만들어도 검색 DTO 가 바뀌지 않는 것도 확인했다 - 남은 것: TypeScript 의
XxxFields는 FK id 필드를 여전히readOnly: true로 표시하고validateXxx가 검사하지 않는다 - 관련 파일:
SpecFactory.java,RulesGenRunner.java,RulesVerifyRunner.java,RequiredReferenceDtoTest.java,
elasticore-annotation-guide.md,elasticore-dsl-guide.md,elasticore-rule-guide.md
Improvements (런타임 - 손으로 쓴 규칙의 409)
ConflictException - 손으로 쓴 규칙이 409 를 자기 메시지로 낸다
- "빌려줄 권수가 없다", "이미 반납한 대출이다" 같은 규칙을
beforeSave나 루트 메서드에 쓸 때 409 로 답할 예외가 없었다.
DataIntegrityViolationException을 던지면 409 는 나가지만detail이 데이터베이스 제약 문구("A unique value is already
taken, or the row is still referenced by another.")로 바뀌어 이유를 알 수 없었다 io.elasticore.springboot3.validation.ConflictException(BadRequestException과 같은 자리)을 던지면 생성된
XxxApiErrors가 409,code: conflict, 던진 메시지를detail로 답한다. Java 와 Kotlin 의apiErrors템플릿에 처리기를
더했다. 2.x 와jpa-springboot번들에는 API 오류 처리기가 없으므로 바뀌지 않는다XxxApiErrors.java는 한 번만 생성되는 파일이라 이미 생성한 프로젝트는 그 처리기 다섯 줄을 손으로 더하거나 파일을 지우고
다시 생성한다. 저장소의 샘플에는 더해 두었다- 확인: 도서 대출 프로젝트의
LoanService.beforeSave와 반납 메...