개요
SxG 프로젝트에서 FE / BE / Batch 개발 진행하면서, 얻은 인사이트, 지식, 패턴 등을 기록합니다.
Front-end
프론트 엔드는 크게 고객용 App과 관리자 화면으로 나누어 개발이 진행되었습니다.
기술셋은 App의 경우, Typescript, React Framework이며, 관리자 페이지의 경우, Typescript, React Admin Framework가 사용되었습니다.
Back-end
백엔드는 크게 고객용 App API와 관리자용 API로 나누어 개발이 진행되었습니다.
CRUD API 개발 시, 특정 케이스에 어떤 식으로 해결했는지를 정리하도록 하겠습니다.
API의 요청 객체에 대한 Validation 체크는 jakarta.validation.constraints 패키지의 Annotation 등을 사용하였습니다.
// 사용한 모든 Validation 목록
@NotNull (526건)
- Date, List<T>, Array<T>, Integer, 사용자정의 클래스 컬럼을 대상으로, Null을 비허용하는 요건을 추가
@NotBlank (817건)
- String 형식의 컬럼을 대상으로, Null도 비허용하고 1글자 이상의 공백 아닌 텍스트를 입력해야하는 요건을 추가
(주로 String 형식의 필수값 항목에 쓰인다. @Size(max = 최대글자수)와 함께 쓰는 것이 권장된다.)
- @NotNull이 포함되어 있으므로, 같이 쓸 필요가 없다.
@NotEmpty (104건)
- List<T>, Array<T>, Map<T> 컬럼을 대상으로, Null도 비허용하고, 리스트의 길이도 1개 이상이어야 하는 요건 추가
- String 형식의 컬럼을 대상으로, Null도 비허용하고, 1글자 이상의 공백 포함 텍스트를 입력해야하는 요건 추가
(주로 List 형식의 필수값 항목에 쓰인다. @Size(max = 최대 개수)와 함께 쓰는 것이 권장된다.)
- @NotNull이 포함되어 있으므로, 같이 쓸 필요가 없다.
@Pattern(regexp="^[YN]$") (88건)
- String 형식의 컬럼을 대상으로, ID, 코드, 날짜 텍스트, Yn 텍스트의 특정 형식의 텍스트만 허용하도록 요건 추가
- Null을 Valid하게 인식하므로, 필수값일 경우 @NotBlank(또는 @NotEmpty 또는 @NotNull)을 같이 써야한다.
@Size(min = 최소글자수, max = 최대글자수) (294건)
- String 형식의 컬럼을 대상으로, 코드의 길이 제한, 이름, 제목, 설명의 최대허용길이 요건을 필수적으로 추가
(고정 길이를 원하는 경우, min과 max 값을 동일하게 설정하는 방식으로 정의)
- List<T> 형식의 컬럼을 대상으로, 리스트의 항목개수 제한 요건을 필수적으로 추가
- Null을 Valid하게 인식하므로, 필수값일 경우 @NotNull(또는 @NotEmpty) 을 같이 써야한다.
@Min(최소값) (12건)
- Integer 형식의 컬럼을 대상으로, ID, 건수, 번호 등과 같이 최소값 요건이 존재하는 경우에, 선택적으로 추가
- Null을 Valid하게 인식하므로, 필수값일 경우 @NotNull을 같이 써야 한다.
@Positive (6건)
- Integer 형식의 컬럼을 대상으로, 크기값과 같이 자연수가 포함되어야 하는 경우에, 선택적으로 추가
- Null을 Valid하게 인식하므로, 필수값일 경우 @NotNull을 같이 써야 한다.
@PositiveOrZero (15건)
- Integer 형식의 컬럼을 대상으로, ID, 정렬순서, 포인트 등과 같이 양의 정수가 포함되어야 하는 경우에 추가
- Null을 Valid하게 인식하므로, 필수값일 경우 @NotNull을 같이 써야 한다.
@FutureOrPresent (29건)
- LocalDateTime 형식의 컬럼을 대상으로, 등록일시, 수정일시 제약조건에 선택적으로 추가
- Null을 Valid하게 인식하므로, 필수값일 경우 @NotNull을 같이 써야 한다.
// org.hibernate.validator.constraints 패키지에서, 아래 Annotation을 예외적으로 사용
@Length(min = 최소글자수, max = 최대글자수) (30건)
- String 형식의 컬럼을 대상으로, 코드의 길이 제한, 이름, 제목, 설명의 최대허용길이
- @Size와 동일한 역할을 하는 듯...
Q. startDate, endDate와 같이 기간에 대한 검증이 필요한 경우는 어떻게 검증 처리를 하는게 일반적일까?
// Controller 단에서, 검증 로직을 추가할 수 있겠지만 ConstraintValidator를 활용하여, Custom Validation Annotation을 선언 후, DTO 객체에 선언해주는 것이 가장 세련되고 일반적인 방법이기도 하다.
@DateOrder(begin = "시작날짜 또는 일시 컬럼명", end = "종료날짜 또는 일시 컬럼명", type = ValueType.LOCAL_DATE or DATE_TIME)
요청/응답 객체의 선언은 Lombok 라이브러리를 사용하여, 아래와 같은 Annotation을 활용하여 관리하였습니다.
Q. 요청/응답 객체는 VO가 맞을까? DTO가 맞을까?
요청/응답 객체는 데이터 전송을 목적으로 하는 객체이므로, getter만 가진 VO(값 객체)가 아니라, getter/setter를 가지는 DTO(데이터 전송 객체)가 더 적합한 명칭입니다. 그러나, 해당 프로젝트에서는 VO라는 suffix를 붙이도록 가이드되었습니다.
Q. DTO 객체에 어떤 Annotation의 조합을 사용하는 것이 일반적일까?
- 주로 아래 4개의 Annotation을 사용하는 것이 일반적입니다. getter, setter가 필요한 DTO 객체에 반복적으로 정의해야하는 모든 생성자 및 빌더 함수들을 자동으로 작성해줍니다.
(@Data는 클래스에 대한 기본적인 메소드들을 자동으로 생성하므로 필수적입니다.)
@Data // @ToString, @EqualsAndHashCode, @Getter, @Setter, @RequiredArgsConstructor를 한 번에 적용
@Builder // Builder 패턴으로 값을 입력하여 생성할 수 있도록 관련 함수 적용
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
[@EqualsAndHashCode(callSuper = false)] // 상속 고려안하고, 동등 비교 필요한 경우 선택적으로 적용
public class NewsCreateRequestDTO { // 경우에 따라서, PagingVO, SessionVO를 상속 받아 사용
...
}
요청/응답 DTO 객체에 대한 명명 규칙은 아래와 같이 적용하였습니다.
DTO 객체에 대한 일반적인 명명규칙은 순서대로 아래와 같습니다.
Q. Create와 Update 관련 요청 객체를 구분해서 쓰는게 일반적인가? 같이 쓰는게 일반적인가?
- 구분해서 쓰는게 일반적이지만, 필요에 따라서 합쳐서 쓰기도 한다.
- NewsDetailCreateReqDTO와 같이 "Detail"과 "Create" 등을 함께 사용하는 것은 지양한다.
일반적인 리소스 Create 요청 객체명
- NewsCreate[Request 또는 Req]DTO
- CreateNews[Request 또는 Req]DTO
일반적인 리소스 Update 요청 객체명
- NewsUpdate[Request 또는 Req]DTO
- UpdateNewsRequestDTO
일반적인 리소스 Delete 요청 객체명
- NewsUpdate[Request 또는 Req]DTO
- UpdateNewsRequestDTO
일반적인 리소스 List 목록 Get 요청 객체명
- NewsList[Request 또는 Req]DTO (30건)
- NewsGet[Request 또는 Req]DTO (25건)
- NewsSearch[Request 또는 Req]DTO (20건)
일반적인 리소스 상세 Get 요청 객체명
- NewsDetail[Request 또는 Req]DTO (16건)
(대부분 PathParam으로 받지만, 상황에 따라서 QueryParam으로 받아야 할 때)
Q. Upsert 역할을 하는 API일 경우, CreateRequestDTO, UpdateRequestDTO 둘 중 무엇이 더 적합한가?
- Upsert 자체가 ID 정보가 필요하므로, CreateRequestDTO가 더 적합하다.
Q. 리스트 반환할 때의 객체와 상세 단건을 반환할 때의 객체를 구분하여 사용하는게 일반적인가? 같이 쓰는게 일반적인가?
- 둘다 일반적인 방법이지만, 분리하여 사용하는 것이 더 일반적입니다.
일반적인 목록 리소스 응답 객체명
- News[Response 또는 Res] DTO
일반적인 상세 리소스 응답 객체명
- NewsDetail[Response 또는 Res] DTO
일반적인 목록&상세 리소스 응답 객체명(함께 쓰는 경우)
- News [Response 또는 Res] DTO
- 목록에서 일부 필드값들(상세에서만 표시)이 Null로 표시되는 것을 감안해야 함
(별도로 내부에서 분기 처리해주기도 함)
Q. CUD API가 없고, 단순 조회/응답만 존재하는 경우는 더 축약해서 사용하는 게 일반적인가?
- CUD API가 없으므로, 아래와 같이 쓰이긴 하지만, 추후라도 생길 수 있으므로 위와 같은 형식을 맞추어 주는 것이 좋다.
- News[Request 또는 Req]DTO
- News[Response 또는 Res]DTO
Swagger를 사용해 API Document를 대용하였으므로, io.swagger.v3.oas.annotations 패키지의 Annotation 도 사용되었습니다.
@Schema (description = "속성 설명", example="예시값" [, nullable=true])
- 요청, 응답 객체 VO에서 각 필드마다 선언하며, 입출력 항목에 대한 swagger 설명을 기록합니다.
(버전 2.x의 경우 ApiModelProperty로 사용됨)
- 요청, 응답 객체 VO 최상위에 선언하여, 입출력 객체에 대한 설명을 기록합니다.
@Operation (
summary = "게시글 전체 조회",
description = "게시글 목록을 전체 조회합니다.",
tags = {"게시글"})
- RestController에서 API 함수마다 선언하여, API에 대한 swagger 설명을 기록합니다.
@Tag (name = "게시글", description = "게시글 API입니다.")
- RestController에서 최상단에 선택적으로 적용하며, Controller에 대한 swagger tag 설명을 기록합니다.
로그인 및 인증 관리는 아래와 같습니다.
Login 인증 없이 접근 및 호출할 수 있는 API가 존재하는 경우
- Spring Boot Security 에서 제공하는 SecureConfig의 securityFilterChain 함수로 method별 허용 가능한 목록을 정의하여, 예외처리하는 방식으로 개발되었습니다.
API 접근 권한 관리는 org.springframework.boot:spring-boot-starter-security 패키지에 포함된org.springframework.security.access 패키지의 @Secured Annotation을 활용하여, 수행하였습니다.
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
주된 API 의 유형과 구현 사항은 아래와 같습니다.
1. 리소스 Create API
- HTTP 메소드명 : POST
- API 구성 : 리소스명
1-1. 복합 ID Key로 단건 수정 API 구현 시 (Restful 함)
- API 구성 : 리소스명/{상위 ID}[/...] (2건) // 자동생성 ID 제외
2. 리소스 Update API
- HTTP 메소드명 : PUT
- API 구성 : 리소스명/{ID}
2-1. 복합 ID Key로 단건 수정 API 구현 시 (Restful 함)
- API 구성 : 리소스명/{상위 ID}/{하위 ID}[/...] (2건)
3. 리소스 Delete API
- HTTP 메소드명 : DELETE
- API 구성 : 리소스명/{ID}
3-1. 복합 ID Key로 단건 삭제 API 구현 시 (Restful 함)
- API 구성 : 리소스명/{상위 ID}/{하위 ID}[/...]
3-2. 복합 ID Key로 단건 삭제 API 구현 시 (Restful 함)
- API 구성 : 리소스명/{상위 ID}/{하위 ID}[/...]
4. 일반적인 리소스 List 조회 API
- HTTP 메소드명 : GET
5. 일반적인 리소스 Detail 조회 API
- HTTP 메소드명 : GET
Batch
배치는
기술셋은 Spring Batch
최근댓글