-
Notifications
You must be signed in to change notification settings - Fork 5
Home
복잡한 인하대의 졸업요건을 손쉽게 검사하자.
I-CURRICULUM은 학생의 수강내역을 입력받아, 복잡한 졸업요건을 대신 검사해주고, 결과를 손쉽게 알려주는 웹 기반 서비스입니다.
기능 목록
- 졸업 요건 검사
- SW-AI 영역 검사
- 창의 영역 검사
- 핵심교양 영역 검사
- 주전공-전공필수 영역 검사
- 주전공-전공선택 영역 검사
- 주전공-교양필수 영역 검사
- 교양선택 영역 검사
- 기타 요구사항
- SW-AI 영역은 영역 대체과목이 존재한다.
- 핵심교양 영역은 영역 지정과목, 영역 대체과목이 존재한다.
- 과목에 대한 대체과목을 잘 처리한다.
- 교양필수는 영어과목을 잘 처리해야한다.
- 21학번 이후는 영어기초 과목들 중 1개를 듣는다.
- 20학번 이전은 영어기초, 영어심화를 각각 1개씩 듣는다.
회원(Member): 이름과 입학년도, 역할을 가진다.
수강(Take): 카테고리(영역), 전공상태(ex. 주전공), 이수시기, 성적, 수업(Course or CustomCourse), 회원을 가진다. CustomCourse과 Course 중 단 하나만 가지고 있어야 한다.
수업(Course): 학수번호, 수업명, 학점을 가진다. 크롤러를 통해서 가져온 수업 정보들이다. code(학수번호)를 기준으로 unique한 제약조건을 가진다.
회원전공상태(MemberMajor): 전공상태(ex.주전공), 학과, 회원을 가진다.
학과(Department): 학과명을 DepartmentName이라는 enum 상태로 보관한다.
졸업요건(Curriculum)
-
CurriculumDecider
를 기준으로 unique한 제약 조건을 가진다. - 졸업요건에 관한 상세정보들이 내부 객체로 보관 되어있다.
핵심교양(Core)
-
isAreaFixed
(영역이 지정되어 있는지),requiredCredit
(필수 이수학점),requiredAreaSet
(들어야하는 영역)을 보관한다. - 영역이 지정되어 있지 않은 경우
requiredAreaSet
은 비어있어야 한다. - 영역지정과목과 영역대체과목을 보관가능하다.
- 추가적으로 특수 과를 위한 정보를
addtionalInfoMap
에 저장할 수 있다. - 필수값은
isAreaFixed
,requiredCredit
이다. -
isAreaFixed
가true
일 시,requiredAreaSet
값이not empty
여야 한다. -
isAreaFixed
가false
일 시,requiredAreaSet
값이empty
여야 한다.
SwAi(SwAi)
-
approvedCodeSet
에 SwAi로 인정될 수 있는 과목코드를 저장한다. - 각 학과마다 들을 수 있는
approvedCodeSet
이 정해져 있고, 그에 맞는 코드들을 여기에 넣으면 된다. - 영역대체과목을 저장할 수 있다.
- 필요이수학점을 보관한다.
- 추가적으로 특수 과를 위한 정보를
addtionalInfoMap
에 저장할 수 있다. - 필수값은
requiredCredit
이다. -
requiredCredit
이 0이면,approvedCodeSet
은empty
여야 한다. -
requiredCredit
이 0이 아니면,approvedCodeSet
은not empty
여야 한다.
창의(Creativity)
-
approvedCodeSet
에 창의로 인정될 수 있는 과목코드를 저장한다. 보통 프론티어대학 학부 교육과정을 따를 것이다. 그에 맞는 코드들을 여기에 넣으면 된다. - 필요이수학점을 보관한다.
- 추가적으로 특수 과를 위한 정보를
addtionalInfoMap
에 저장할 수 있다. - 필수값은
requiredCredit
이다. -
requiredCredit
이 0이면,approvedCodeSet
은empty
여야 한다. -
requiredCredit
이 0이 아니면,approvedCodeSet
은not empty
여야 한다.
전공필수(MajorRequired)
-
CodeSet
에 전공필수 과목코드를 저장한다. 크롤러를 기준으로 저장된다. - 크롤러로 가져온 정보가 정확하지 않으면 수정해야한다.
- 추가적으로 특수 과를 위한 정보를
addtionalInfoMap
에 저장할 수 있다. - 필수값은
CodeSet
이다.
전공선택(MajorSelect)
-
CodeSet
에 전공선택 과목코드를 저장한다. 크롤러를 기준으로 저장된다. - 크롤러로 가져온 정보가 정확하지 않으면 수정해야한다.
- ex. 컴퓨터공학과 예전 학번은 오픈소스SW개론과 같은 신설과목이 포함 되어있지 않다.
- 추가적으로 특수 과를 위한 정보를
addtionalInfoMap
에 저장할 수 있다. - 필수값은
CodeSet
이다.
교양필수(GeneralRequired)
-
CodeSet
에 교양필수 과목코드를 저장한다. 크롤러를 기준으로 저장된다. - 크롤러의 정보가 정확하지 않으면 수정 해야한다.
- ex. 컴퓨터공학과 예전 학번은 정수론 듣지 않아도 되지만, 크롤러로 가져오면 포함 되어있다.
- 추가적으로 특수 과를 위한 정보를
addtionalInfoMap
에 저장할 수 있다. - 필수값은
CodeSet
이다.
필요학점(RequiredCredit)
- 총필요학점, 단일전공필요학점, 다중전공필요학점, 부전공필요학점을 저장한다.
- 모든 값이 필수값이다.
대체과목(AlternativeCourse)
- 과목에 대한 대체과목을 저장한다.
- 바뀐과목(새로운 과목) 코드를 Key로 넣고,
Curriculum
에 존재하는 과목코드를 Value로 넣는다.- ex. 사용자는 Curriculum에 저장된 필수과목들을 이수 해야한다. Curriculum은 크롤러로 가져와서 컴퓨터공학입문이 옛날 학수번호(CSE1102)로 저장되어 있다. 따라서 사용자가 새로운 학수번호(CSE1112)로 바뀐 컴퓨터공학입문을 들었을 때, 옛날 학수번호(CSE1102)를 들었다고 인정되어야 한다.
- 따라서 데이터를 이렇게 넣으면 된다.
- Key -> “CSE1112”, Value -> Set.of("CSE1102")
- key로 새로 바뀐 과목, value로 Curriculum에 저장되어있는 과거 과목
- 고급대학영어, 실용영어도 학수번호가 바뀌어 마찬가지이다. 모든 영역에 존재하는 “과목에 대한 대체과목”들은 해당 클래스에 저장해서 사용한다.
ApiResponse 클래스 정의
public record ApiResponse<T>(
Boolean isSuccess,
String code,
String message,
T result) {
...
- Record 타입: 이 클래스는 Java 14의 Record 타입으로 정의되었습니다. Record는 불변 데이터 클래스로 주로 데이터 전송 객체(DTO)를 단순하게 생성할 때 사용됩니다.
- Generics 사용 (): 응답이 다양한 데이터 타입을 가질 수 있도록 제네릭 타입 매개변수 T를 사용하고 있습니다.
-
필드 설명:
-
Boolean isSuccess
: 요청의 성공 여부를 나타냅니다. -
String code
: 응답 상태를 나타내는 코드입니다. 응답 코드를 담습니다. -
String message
: 응답에 대한 메시지입니다. 주로 응답에 대한 설명을 저장합니다. -
T result
: 응답의 결과 데이터를 포함합니다. 결과는 제네릭 타입으로 지정되어 있어 다양한 유형의 데이터를 담을 수 있습니다.
-
상수 및 메서드
public static final ApiResponse<Void> OK = new ApiResponse<>(true, SuccessStatus.OK.getCode(), SuccessStatus.OK.getMessage(), null);
-
OK 상수: OK는 성공적인 응답을 나타내는 정적 상수로, 아무런 결과 데이터(result)가 없을 때 사용됩니다.
- 성공 시, 전달하고 싶은 데이터가 없을 때 Controller에서
return ApiResponse.OK
작성합니다.
- 성공 시, 전달하고 싶은 데이터가 없을 때 Controller에서
public static <T> ApiResponse<T> onSuccess(T result) {
return new ApiResponse<>(true, SuccessStatus.OK.getCode(), SuccessStatus.OK.getMessage(),
result);
}
-
onSuccess 메서드: 성공적인 응답을 생성하기 위한 정적 팩토리 메서드입니다.
- 매개변수: 성공적인 결과 데이터 (result).
- 반환 값: isSuccess가 true이고, 상태 코드는 성공 상태 (SuccessStatus.OK)인 ApiResponse 객체를 반환합니다.
public static <T> ApiResponse<T> onFailure(ErrorStatus errorStatus, T data) {
return new ApiResponse<>(false, errorStatus.getCode(), errorStatus.getMessage(), data);
}
-
onFailure 메서드: 오류 응답을 생성하기 위한 정적 팩토리 메서드입니다.
- 매개변수: 오류 상태 (errorStatus)와 추가적인 결과 데이터 (data).
- 반환 값: isSuccess가 false이고, 오류 상태 코드 및 메시지를 가지는 ApiResponse 객체를 반환합니다.
- 보통 ExceptionHandler에서 사용하게 될 메서드 입니다.
⠀
@Getter
@RequiredArgsConstructor
public enum ErrorStatus {
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."),
...
private final HttpStatus httpStatus;
private final String code;
private final String message;
}
- ErrorStatus 클래스는 애플리케이션의 오류 상태를 관리하기 위한 **열거형(enum)**입니다.
-
필드
-
HttpStatus httpStatus
: HTTP 상태 코드를 나타냅니다. 새로 생성할 시, 컨벤션에 맞춰 400으로 통일합니다. -
String code
: 커스텀 오류 코드를 정의합니다. -
String message
: 사용자에게 표시할 오류 메시지입니다.
-
-
사용법
- 비즈니스적 오류가 발생하여 런타임 예외를 개발자가 발생시키고 싶거나, 체크 예외를 catch해서 런타임예외로 바꾸고 싶다면 ErrorStatus 클래스 내에 새로운 데이터를 정의하고 이를 GeneralException 클래스에 담아서 throw 합니다.
@Getter
public class GeneralException extends RuntimeException {
private final ErrorStatus errorStatus;
private final Object data;
public GeneralException(ErrorStatus errorStatus) {
super(errorStatus.getMessage());
this.errorStatus = errorStatus;
this.data = null;
}
public GeneralException(ErrorStatus errorStatus, Object data) {
super(errorStatus.getMessage());
this.errorStatus = errorStatus;
this.data = data;
}
public GeneralException(ErrorStatus errorStatus, Throwable cause) {
super(errorStatus.getMessage(), cause);
this.errorStatus = errorStatus;
this.data = null;
}
public GeneralException(ErrorStatus errorStatus, Object data, Throwable cause) {
super(errorStatus.getMessage(), cause);
this.errorStatus = errorStatus;
this.data = data;
}
}
- GeneralException 클래스는 애플리케이션에서 발생할 수 있는 예외를 처리하기 위해 정의된 사용자 정의 예외 클래스입니다. 이 클래스는 **런타임 예외 (RuntimeException)**를 상속받아, 런타임에서 발생하는 오류를 다룹니다. 클래스 구성은 다음과 같습니다:
필드
private final ErrorStatus errorStatus;
private final Object data;
-
ErrorStatus errorStatus
: 발생한 오류의 상태를 나타내는 ErrorStatus 객체입니다. -
Object data
: 예외 발생 시 추가적인 정보를 전달할 수 있는 데이터입니다.
생성자
public GeneralException(ErrorStatus errorStatus) {
super(errorStatus.getMessage());
this.errorStatus = errorStatus;
this.data = null;
}
public GeneralException(ErrorStatus errorStatus, Object data) {
super(errorStatus.getMessage());
this.errorStatus = errorStatus;
this.data = data;
}
public GeneralException(ErrorStatus errorStatus, Throwable cause) {
super(errorStatus.getMessage(), cause);
this.errorStatus = errorStatus;
this.data = null;
}
public GeneralException(ErrorStatus errorStatus, Object data, Throwable cause) {
super(errorStatus.getMessage(), cause);
this.errorStatus = errorStatus;
this.data = data;
}
- 첫 번째 생성자: ErrorStatus만 받아서 예외를 생성하며, 추가 데이터는 null로 설정합니다.
- 두 번째 생성자: ErrorStatus와 함께 추가 데이터를 받아 예외를 생성합니다.
- 세 번째 생성자: ErrorStatus와 **발생 원인 (cause)**을 받아 예외를 생성합니다. 예외 발생 원인을 포함하여 추적이 가능하도록 합니다.
- 네 번째 생성자: ErrorStatus, 에러 데이터 (data), 그리고 발생 원인 (cause)를 모두 받아 예외를 생성합니다.
단순한 오류 상태만 사용하여 예외 생성
throw new GeneralException(ErrorStatus.BAD_REQUEST);
추가 데이터와 함께 예외 생성
throw new GeneralException(ErrorStatus.MEMBER_MAJOR_NOT_FOUND, this);
- 비즈니스적 에러가 발생 시 해당 방식을 권장합니다.
발생 원인 (cause)과 함께 예외 생성
try {
...
} catch (Exception e) {
throw new GeneralException(ErrorStatus.INTERNAL_SERVER_ERROR, e);
}
- 발생 원인을 포함하여 GeneralException을 던져 예외 추적이 가능합니다.
- 체크 예외를 언체크 예외로 전환할 때, 반드시 예외를 포함해서 던지도록 합니다.
발생 원인 (cause), 데이터와 함께 예외 생성
try {
...
} catch (Exception e) {
throw new GeneralException(ErrorStatus.INTERNAL_SERVER_ERROR, this, e);
}
⠀
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(GeneralException.class)
public ResponseEntity<ApiResponse<Object>> handleGeneralException(GeneralException ex) {
log.error("Error Code: {}, Message: {}, Data: {}",
ex.getErrorStatus().getCode(),
ex.getErrorStatus().getMessage(),
ex.getData() != null ? ex.getData() : "No additional data",
ex
);
return ResponseEntity
.status(ex.getErrorStatus().getHttpStatus())
.body(ApiResponse.onFailure(
ex.getErrorStatus(),
ex.getData()
));
}
}
-
로깅
- Error Code: 예외의 상태 코드 (ErrorStatus에 정의된 코드).
- Message: 예외의 메시지.
- Data: 추가적인 오류 데이터, 없을 경우 "No additional data"로 표시.
- ex: 스택 트레이스가 함께 기록되어 예외 발생 경로와 원인까지 확인할 수 있습니다.
-
응답생성
- 예외의 상태 코드를 사용하여 HTTP 응답 상태를 설정합니다. 저희 프로젝트 컨벤션에 맞춰
ErrorStatus
를 400으로 생성하면, 항상 상태코드는 400으로 클라이언트에게 전달됩니다. -
ApiResponse.onFailure()
: 오류 응답을 생성하는 정적 메서드를 사용하여, 실패 상태의 응답 객체를 생성합니다.
- 예외의 상태 코드를 사용하여 HTTP 응답 상태를 설정합니다. 저희 프로젝트 컨벤션에 맞춰
-
추후 메소드 추가 가능성
- GeneralException 이외에도, Spring에서 정의한 예외를 잡기 위해, 해당 GlobalExceptionHandler 내에서 메소드를 추가하여 처리하도록 확장할 수 있습니다.
BaseMongoEntity는 MongoDB의 엔터티에서 공통 필드를 제공하기 위한 추상 클래스입니다.
필드 설명
-
createdBy (String)
: 엔터티를 생성한 사용자 정보를 저장합니다. (@CreatedBy 사용) -
lastModifiedBy (String)
: 엔터티를 마지막으로 수정한 사용자 정보를 저장합니다. (@LastModifiedBy 사용) -
createdAt (LocalDateTime)
: 엔터티가 생성된 시간을 저장합니다. (@CreatedDate 사용) -
updatedAt (LocalDateTime)
: 엔터티가 마지막으로 수정된 시간을 저장합니다. (@LastModifiedDate 사용)
Curriculum 데이터는 매우 소중합니다. 추후 DocumentDB(aws) 로 데이터베이스를 이전 시켰을 때, 해당 데이터를 누가 만들었고, 누가 수정하였는지 확실하게 기록하기 위해서 엔터티를 생성, 수정한 사람의 정보를 기록합니다. 본인이 맡은 과의 데이터만 수정하도록 주의합시다!
BaseRDBEntity는 RDB 엔터티에서 공통 필드를 제공하기 위한 추상 클래스입니다.
필드 설명
-
createdAt (LocalDateTime)
: 엔터티가 생성된 시간을 저장하며, 업데이트되지 않습니다. (@CreatedDate, @Column(updatable = false) 사용) -
updatedAt (LocalDateTime)
: 엔터티가 마지막으로 수정된 시간을 저장합니다. (@LastModifiedDate 사용)
⠀어노테이션
- @MappedSuperclass: 이 클래스는 공통 필드를 자식 클래스가 상속받도록 합니다. 데이터베이스 테이블에 직접 매핑되지 않습니다.
- @EntityListeners(AuditingEntityListener.class): JPA Auditing 기능을 활성화하여 엔터티의 생성 및 수정 정보를 자동으로 기록합니다.
- @CreatedDate, @LastModifiedDate: 엔터티의 생성 및 수정 시간을 자동으로 기록합니다.
- @Column(updatable = false): createdAt 필드는 한 번 설정되면 수정되지 않도록 제한합니다.
Curriculum 클래스는 학과, 전공 유형, 입학 연도를 기준으로 특정 학과의 졸업 요건을 정의하는 엔터티 클래스입니다. MongoDB의 curriculums 컬렉션에 저장됩니다.
- @Document(collection = "curriculums"): 해당 클래스가 MongoDB의 컬렉션과 매핑됨을 나타냅니다.
- @NoArgsConstructor(access = PROTECTED): 기본 생성자를 생성하며, 외부에서 직접 생성하지 못하도록 제한합니다.
- @CompoundIndexes & @CompoundIndex: decider 필드(전공 유형, 학과 이름, 입학 연도)에 유니크 제약 조건을 부여하여 중복된 졸업 요건을 허용하지 않도록 합니다.
- CurriculumDecider decider: 해당 졸업 요건의 결정 요소(전공 유형, 학과 이름, 입학 연도)를 정의합니다.
-
졸업 요건 관련 필드
- Core core: 핵심 교양 요건
- SwAi swAi: SW-AI 관련 졸업 요건
- Creativity creativity: 창의 관련 졸업 요건
- MajorRequired majorRequired: 전공 필수 과목
- MajorSelect majorSelect: 전공 선택 과목
- GeneralRequired generalRequired: 교양 필수 과목
- RequiredCredit requiredCredit: 필요한 학점 정보
- AlternativeCourse alternativeCourse: 대체 가능한 과목 정보
빌더를 통한 객체 생성
@Builder
private Curriculum(
CurriculumDecider decider,
Core core, SwAi swAi,
Creativity creativity, MajorRequired majorRequired,
MajorSelect majorSelect, GeneralRequired generalRequired,
RequiredCredit requiredCredit, AlternativeCourse alternativeCourse
) {
...
validate();
}
- @Builder: 빌더 패턴을 사용하여 객체를 생성합니다. 빌더를 제외하고 다른 방식으로 생성하지 못하도록 private 생성자를 사용합니다.
- validate() 호출: 생성 시 필드들이 모두 채워져 있는지 확인하여, 필드 누락으로 인한 NullPointerException을 방지합니다. 올바르지 않는 데이터도 검증합니다.
참고: 컬렉션 필드는
new
를 사용하여 미리 초기화한 이유 DB에서 Curriculum을 조회해올 때, Spring은 빌더를 사용하여 객체를 생성하지 않습니다. 리플렉션 기술을 사용하여 필드에 데이터를 주입하여 객체를 생성합니다. 따라서validate()
메소드는 DB에서 가져올 때는 사용되지 않는 메소드입니다. DB에서 Curriculum을 조회해올 때,NullPointException
을 막기 위해 컬렉션과 같은 경우 미리new
를 사용하여 필드에 객체를 초기화 해둡니다.
검증 메서드 (validate)
public void validate() {
if (필수 필드들이 null인 경우) {
throw new GeneralException(ErrorStatus.CURRICULUM_MISSING_VALUE, this);
}
// 각 내부 객체도 검증
decider.validate();
core.validate();
swAi.validate();
creativity.validate();
majorRequired.validate();
majorSelect.validate();
generalRequired.validate();
requiredCredit.validate();
}
- 필수 필드 검증: 필수 필드가 null일 경우, GeneralException을 발생시켜 에러 메시지와 현재 객체 정보를 제공합니다.
- 내부 객체 검증: 내부 객체들도 각각의 검증 메서드를 호출하여 유효성을 검사합니다.
⠀
CurriculumService는 특정 학과의 졸업 요건을 조회하고, 조회된 데이터를 검증하는 기능을 제공합니다.
getCurriculumByMemberMajor(MemberMajor memberMajor)
public Curriculum getCurriculumByMemberMajor(MemberMajor memberMajor) {
CurriculumDecider decider = convertToDecider(memberMajor);
Curriculum curriculum = repository.findByDecider(decider)
.orElseThrow(() -> new GeneralException(ErrorStatus.CURRICULUM_NOT_FOUND));
curriculum.validate(); // 중요
return curriculum;
}
- 기능: MemberMajor 객체를 기반으로 Curriculum을 조회합니다.
- convertToDecider() 호출: MemberMajor 객체를 CurriculumDecider로 변환합니다.
- 레포지토리 조회: 레포지토리에서 Curriculum을 조회하며, 존재하지 않으면 GeneralException을 발생시킵니다.
-
검증 (validate()): 조회된 Curriculum 객체의 필수 필드와 내부 객체들을 검증하여 데이터 무결성을 유지합니다.
- 추후, Curriculum을 DB에서 조회하는 새로운 메소드를 만든다면, 반드시
curriculum.validate();
코드를 호출하여 가져온 데이터를 검증하도록 합시다.
- 추후, Curriculum을 DB에서 조회하는 새로운 메소드를 만든다면, 반드시
- 데이터 무결성 보장: 필수 필드 검증 및 올바른 데이터 형식 확인을 통해 데이터의 정합성을 유지하고, 필요 시 NullpointException을 방지합니다.
- 각 영역별 내부 객체에 있는
additionalInfoMap
필드를 통해 데이터 확장성을 부여합니다. -
Curriculum
관련 코드는 절대 수정하지 않습니다. 내부 객체의 메소드를 사용하여 데이터를 가져오도록 합니다.
- 특정학과의 특정 영역을 판단할 때, 학과별 특이사항 존재 시 새로운 구현체 생성으로만 해결이 가능하면 additionalInfoMap에 새로운 데이터를 추가하지 않아도 됩니다.
- 그러나 새로운 객체를 만들어서 특이 데이터를 저장해야 한다면, 영역별 내부 객체(ex.
SwAI
,Creativity
… )에 존재하는additionalInfoMap
에 데이터를 추가하고, 새로운 데이터와 새로운 구현체를 통해 학과별 특이사항을 해결합니다.
예시
AlternativeCourse
@NoArgsConstructor(access = PROTECTED)
@ToString
public class AlternativeCourse {
@JsonProperty("대체과목코드")
private Map<String, Set<String>> alternativeCourseCodeMap = new HashMap<>();
@Builder
private AlternativeCourse(Map<String, Set<String>> alternativeCourseCodeMap) {
this.alternativeCourseCodeMap = (alternativeCourseCodeMap != null) ?
alternativeCourseCodeMap : new HashMap<>();
}
public Set<String> getAlternativeCodeSet(String code) {
return alternativeCourseCodeMap.getOrDefault(code, Collections.emptySet());
}
}
getAlternativeCodeSet
메소드를 통해서만 대체과목코드를 조회하도록 만들어 두었습니다.
그 이유는 alternativeCourseCodeMap
를 getter로 외부에 노출시키고,alternativeCourseCodeMap.get(“특정과목 코드”)
를 호출하여 Set<String>
을 가져올 시 NullpointException이 발생할 확률이 매우 높기 때문입니다.
혹시 누군가가 @Getter
를 추가하여, 내부 필드인 alternativeCourseCodeMap를 직접 가져오는 작업을 하면 안됩니다. 우선 Curriculum 관련 코드는 절대 수정하지 않고, 수정이 꼭 필요하다고 생각하면 회의를 통해서 수정하도록 합시다.
Take 클래스는 특정 학생(Member)이 수강한 과목 정보를 저장하는 엔터티 클래스입니다.
- @Enumerated(STRING) Category category: 수강 과목의 카테고리 (영역)를 나타냅니다. 우리만의 로직으로 take의 category가 1차 판별되고, 이 후 사용자가 직접 수정가능합니다. 졸업요건은 이 category를 기준으로 검사를 수행합니다.
- Course course: 수강한 과목이 기존 Course 테이블에 존재할 경우를 나타냅니다.
- @Embedded CustomCourse customCourse: Course Table에 존재하지 않는 커스텀 수업 정보입니다. 이 때, CustomCourse의 code는 반드시 “CUSTOM” 입니다.
- @Enumerated(STRING) MajorType majorType: 수강한 수업이 어떤 전공 상태로 들은 수업인지 구분합니다. 해당 정보가 없으면, 사용자의 전공상태가 여러 개일 경우 졸업요건이 잘못 검사될 확률이 높습니다.
- checkValidTake()
private void checkValidTake() {
if ((course == null && customCourse == null) || (course != null && customCourse != null)) {
throw new GeneralException(ErrorStatus.TAKE_HAS_ABNORMAL_COURSE, this);
}
}
기능
-
course와 customCourse 중 하나만 값이 설정되어 있어야 하며, 둘 다 설정되거나 둘 다 비어 있으면 GeneralException을 발생시킵니다. 빌더를 통해 take를 생성할 때 호출됩니다.
-
getEffectiveCourse()
public Course getEffectiveCourse() {
checkValidTake();
if (course != null) {
return course;
}
if (customCourse != null) {
return Course.builder()
.code(customCourse.getCode())
.name(customCourse.getName())
.credit(customCourse.getCredit())
.build();
}
throw new GeneralException(ErrorStatus.TAKE_HAS_ABNORMAL_COURSE, this);
}
기능
- Take 객체에서 수강 과목 정보를 가져올 때 호출됩니다. course와 customCourse 중 유효한 값을 반환하며, customCourse의 경우 Course 객체로 변환하여 반환합니다.
CustomCourse 클래스는 사용자가 직접 입력한 수업 정보를 저장하기 위한 임베디드 객체입니다.
- String code: 수업 코드를 저장합니다. -> 반드시 “CUSTOM” 입니다.
- String name: 수업 이름을 저장합니다.
- Integer credit: 학점 정보를 저장합니다.
TakeService는 특정 학생의 수강 내역을 조회하고 반환하는 서비스 클래스입니다.
getTakeListByMemberAndMajorType(Member member, MajorType majorType)
public LinkedList<Take> getTakeListByMemberAndMajorType(Member member, MajorType majorType) {
List<Take> takeList = repository.findByMemberAndMajorType(member, majorType);
return new LinkedList<>(takeList);
}
기능
- 특정 학생의 특정 전공 상태에 대한 수강 내역을 조회하여 LinkedList 형태로 반환합니다. 메모리에서 삭제가 빈번하게 일어나야 하기 때문에 Collection LinkedList를 활용합니다.
-
주전공 졸업요건을 검증하고 결과를 반환합니다.
MainGraduationService
는 학생의 수강 과목과 전공상태에 따른 졸업 요건을 기반으로 졸업 가능 여부를 판단하는 기능을 제공합니다.
MainGraduationService
는 주전공 졸업요건을 확인하여 졸업 여부를 판단하는 클래스입니다. 학생의 수강 내역을 기반으로 졸업에 필요한 각 영역에 대해 프로세서(Processor)를 통해 요건을 판단합니다.
- TakeService takeService: 특정 학생의 수강 내역을 조회합니다.
- MemberMajorService memberMajorService: 학생의 전공 상태(주전공, 복수전공 등)를 조회합니다.
- CurriculumService curriculumService: 특정 학과의 졸업요건 정보를 조회합니다.
- CourseService courseService: 과목 정보를 조회합니다.
- Map<ProcessorCategory, Processor, ?>> processorMap: 프로세서 목록을 관리하는 맵으로, 특정 카테고리에 따른 프로세서를 가져오는 데 사용됩니다.
execute(Member member)
-
기능
- 주어진 학생의 졸업 요건을 확인하고, Curriculum을 기준으로 검증 결과를 반환합니다.
-
프로세서 실행
- 각 졸업 요건 영역에 대해 Processor를 실행하여 검증 결과를 수집합니다.
executeProcessor()
@SuppressWarnings("unchecked")
private <R, T, P> R executeProcessor(
ProcessorCategory category,
Class<P> payloadClazz,
T data,
AlternativeCourse alternativeCourse,
DepartmentName departmentName,
Integer joinYear,
LinkedList<Take> allTakeList
) {
try {
P payload = payloadClazz.getConstructor(data.getClass(), AlternativeCourse.class, DepartmentName.class, Integer.class)
.newInstance(data, alternativeCourse, departmentName, joinYear);
return (R) ProcessorUtils.get(processorMap, category).execute(payload, allTakeList);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new GeneralException(ErrorStatus.INTERNAL_SERVER_ERROR, e);
}
}
-
기능
- 특정 Processor를 실행하여 졸업 요건 검증을 수행합니다.
-
동적 객체 생성
- 리플렉션을 통해 payload 객체를 생성하고, 해당 프로세서를 호출합니다.
createMajorRequiredData() & createGeneralRequiredData()
-
createMajorRequiredData(MajorRequired majorRequired):
- 기능: 전공필수 과목 리스트를 조회하여, 전공필수 요건 검사에 필요한 객체를 생성합니다.
-
createGeneralRequiredData(GeneralRequired generalRequired):
- 기능: 교양필수과목 리스트를 조회하여, 교양필수 요건 검사에 필요한 객체를 생성합니다.
validateRemainTakeListGeneralSelect()
- 기능: 졸업 요건 검증 후 남아있는 수강 내역이 모두 교양 선택인지 확인합니다.
- 검증 실패 시 예외 발생: 남아있는 수강 과목이 교양 선택이 아니면 GeneralException을 발생시킵니다.
SwAiProcessor
와 이를 위한 전략 패턴 인터페이스(SwAiStrategy
) 와 구현체(CommonSwAiStrategy
) 클래스에 대해 설명합니다. 이 클래스들은 SW-AI 영역 졸업 요건을 검증하고 처리하기 위해 설계되었습니다.
모든 영역에서 SwAi와 동일하게 전략 패턴을 사용하여, 특이사항이 있는 경우 구현체를 만들고 주입 시킬 수 있는 방식을 채택 하였습니다. 전략 패턴에 대한 설명은 SwAi에서만 하고, 이후에는 각 영역별로 구현되어있는 일반적인 학과의 구현체만 설명합니다.
- SwAiProcessor: SW-AI 졸업 요건 검증을 위한 프로세서 클래스입니다.
- SwAiStrategy: SW-AI 검증 로직을 추상화한 전략 인터페이스입니다.
- CommonSwAiStrategy: 일반적인 학과의 SW-AI 졸업 요건을 처리하기 위한 전략 구현 클래스입니다.
- SwAiStrategyConfig: 학과별 전략을 설정하기 위한 Configuration 클래스입니다.
SwAiProcessor
는 SW-AI 졸업 요건 검증을 수행하는 프로세서 클래스입니다. 특이 학과는 다른 전략(SwAiStrategy
)을 주입하여 유연한 실행이 가능합니다.
- Map<DepartmentName, SwAiStrategy> swAiStrategyMap: 학과별로 SW-AI 검증 로직을 수행하는 전략을 맵핑한 필드입니다.
execute(SwAiDTO request, LinkedList allTakeList)
@Override
public ProcessorResponse.SwAiDTO execute(SwAiDTO request, LinkedList<Take> allTakeList) {
return swAiStrategyMap.get(request.departmentName())
.execute(request, allTakeList);
}
- 기능: 주어진 요청(SwAiDTO)과 수강 과목 리스트(allTakeList)를 기반으로 학과별 SW-AI 전략을 실행합니다.
-
학과별 전략 사용:
swAiStrategyMap
에서 학과명(DepartmentName
)을 기반으로 해당 학과의 전략을 가져와 검증을 수행합니다.
SwAiStrategy
는 SW-AI 졸업 요건 검증을 위한 전략 인터페이스입니다. 이 인터페이스를 구현하여 특이 사항이 존재할 시 다른 구현체를 주입시켜 실행시킬 수 있습니다.
execute(SwAiDTO request, LinkedList allTakeList)
SwAiStrategyConfig
는 학과별 SW-AI 전략을 설정하는 Configuration 클래스입니다. 학과에 따라 다른 전략을 적용할 수 있도록 설정합니다.
swAiStrategyMap()
@Bean
public Map<DepartmentName, SwAiStrategy> swAiStrategyMap(List<SwAiStrategy> strategyList) {
Map<DepartmentName, SwAiStrategy> strategyMap = new EnumMap<>(DepartmentName.class);
for (SwAiStrategy strategy : strategyList) {
if (strategy instanceof CommonSwAiStrategy) {
strategyMap.put(DepartmentName.컴퓨터공학과, strategy);
continue;
}
// 다른 학과별 전략이 추가될 경우 여기에 설정
}
return strategyMap;
}
- 기능: 학과별로 사용할 SW-AI 전략을 설정하여 맵핑합니다.
현재 CommonSwAiStrategy는 컴퓨터공학과에 잘 적용됩니다. strategyMap에strategyMap.put(DepartmentName.컴퓨터공학과, strategy);
코드를 통해 컴퓨터공학과 일 경우 CommonSwAiStrategy 전략을 사용할 수 있도록 하였습니다.
각자 맡은 과가 CommonSwAiStrategy 전략을 통해 잘 수행될 수 있다면 아래 코드와 같이 추가합니다.
for (SwAiStrategy strategy : strategyList) {
if (strategy instanceof CommonSwAiStrategy) {
strategyMap.put(DepartmentName.컴퓨터공학과, strategy);
strategyMap.put(DepartmentName.전기공학과, strategy);
strategyMap.put(DepartmentName.기계공학과, strategy);
strategyMap.put(DepartmentName.인공지능공학과, strategy);
...
continue;
}
// 다른 학과별 전략이 추가될 경우 여기에 설정
}
- 모든 영역에 대해 특수한 조건이 있는 학과인 경우 아래와 같은 흐름으로 작업합니다.
-
SwAiStrategy
인터페이스에 맞춰 새로운 구현체를 작성합니다.-
데이터의 확장성: 만약 특수한 조건을 DB에 저장하여 사용하고 싶은 경우,
SwAi
객체의additionalInfoMap
에 추가하면, 저장된 데이터를 새로운 구현체에서 가져와서 사용할 수 있습니다.
-
데이터의 확장성: 만약 특수한 조건을 DB에 저장하여 사용하고 싶은 경우,
- 새로운 구현체에 대한 테스트 코드를 반드시 작성합니다.
- 구현체를
SwAiStrategyConfig
에 bean으로 등록합니다. -
swAiStrategyMap(...)
메소드에 특수 구현체를 특수학과와 매핑합니다.
1. SwAiStrategy 인터페이스에 맞춰 새로운 구현체를 작성합니다.
- strategy 패키지 내에
SwAiStrategy
인터페이스를 받아서 구현체를 추가합니다. - 구현체의 이름은 (특수학과명-한글로)SwAiStrategy 로 합니다. 참고로 만약
기계공학과SwAiStrategy
를 구현한다면, 기계공학과 학생은 학번과 상관없이 해당 구현체를 사용하게 되니, 이를 명심하도록 합니다. - 필요하다면
SwAi
객체 내에addtionalInfoMap
에 새로운 데이터를 추가하고 이를 이용해도 됩니다.
2. 새로운 구현체에 대한 테스트 코드를 반드시 작성합니다.
- 테스트 코드를 작성하지 않을 시, 절대 Merge 불가합니다.
- 정확도가 가장 중요한 프로젝트이기 때문에, 최대한 다양한 예외 케이스를 생각해서 테스트합니다.
- 실제 사람들의 수강내역을 받아서 테스트 해보는 것이 사실 중요합니다.
3. 구현체를 SwAiStrategyConfig에 bean으로 등록합니다.
...
@Bean
public SwAiStrategy commonSwAiStrategy() {
return new CommonSwAiStrategy();
}
@Bean
public SwAiStrategy 기계공학과SwAiStrategy() {
return new 기계공학과SwAiStrategy();
}
...
}
4. swAiStrategyMap 메소드에 특수 구현체를 특수학과와 매핑합니다.
@Bean
public Map<DepartmentName, SwAiStrategy> swAiStrategyMap(
List<SwAiStrategy> strategyList
) {
Map<DepartmentName, SwAiStrategy> strategyMap = new EnumMap<>(DepartmentName.class);
for (SwAiStrategy strategy : strategyList) {
if (strategy instanceof CommonSwAiStrategy) {
strategyMap.put(DepartmentName.컴퓨터공학과, strategy);
...
continue;
}
/*
* 만약 로직이 달라진다면, 학과별 구현 클래스 매핑
*/
if (strategy instanceof 기계공학과SwAiStrategy) {
strategyMap.put(DepartmentName.기계공학과, strategy)
}
}
return strategyMap;
}
이렇게 하면 swAiStrategyMap
를 주입받은 SwAiProcessor
는 학생의 전공이 기계공학과일 경우 기계공학과SwAiStrategy
구현체를 실행시키게 됩니다.
CommonSwAiStrategy는 일반적인 학과의 SW-AI 졸업 요건을 검증하는 구현 클래스입니다. SwAiStrategy 인터페이스를 구현하여 공통된 검증 로직을 제공합니다.
execute(SwAiDTO request, LinkedList allTakeList)
@Override
public ProcessorResponse.SwAiDTO execute(
ProcessorRequest.SwAiDTO request,
LinkedList<Take> allTakeList
) {
SwAiResult result = new SwAiResult();
// 영역 대체 과목
Set<Course> areaAltCourseSet = handleAreaAlternative(allTakeList, request.swAi(),
request.alternativeCourse(), result);
handleResult(
request.swAi(),
allTakeList,
areaAltCourseSet,
result
);
return ProcessorConverter.to(result);
}
- 기능: 주어진 request(SwAiDTO)와 수강 과목 리스트를 기반으로 SW-AI 영역의 검증을 수행합니다.
- 영역 대체 과목 처리: handleAreaAlternative() 메서드를 사용하여 영역 대체 과목을 처리합니다.
- 응답 생성: handleResult() 메서드를 사용하고, Converter를 사용해 검증 결과(SwAiDTO)를 생성합니다.
handleAreaAlternative()
private Set<Course> handleAreaAlternative(
LinkedList<Take> allTakeList,
SwAi swAi,
AlternativeCourse alternativeCourse,
SwAiResult result
) {
Set<Course> areaAltCourseSet = new HashSet<>();
Iterator<Take> iterator = allTakeList.iterator();
while (iterator.hasNext()) {
Take take = iterator.next();
if (GraduationUtils.isApproved(take, swAi.getAreaAlternativeCodeSet())) {
result.update(take, iterator, true);
areaAltCourseSet.add(take.getEffectiveCourse());
continue;
}
if (GraduationUtils.isCodeAlternative(
take,
swAi.getAreaAlternativeCodeSet(),
alternativeCourse)
) {
result.update(take, iterator, true);
areaAltCourseSet.add(take.getEffectiveCourse());
}
}
return areaAltCourseSet;
}
-
기능: 영역 대체 과목을 처리합니다. 대체 과목은 수강 과목 리스트에서 삭제되지 않고,
areaAltCourseSet
에 담겨 리턴됩니다. - 대체 과목 집합 반환: 처리된 대체 과목을 집합(Set) 형태로 반환하여, 해당 과목은 이후 SwAi 일반 검증에서 확인하지 않도록 합니다.
handleResult()
private void handleResult(
SwAi swAi,
LinkedList<Take> allTakeList,
Set<Course> areaAltCourseSet,
SwAiResult result
) {
Iterator<Take> iterator = allTakeList.iterator();
while (iterator.hasNext()) {
Take take = iterator.next();
if (GraduationUtils.isAlreadyCheckedByAreaAlt(take, areaAltCourseSet)) {
continue;
}
if (GraduationUtils.isApprovedCategory(take, Category.SW_AI)) {
result.update(take, iterator, false);
}
}
result.setRequiredCredit(swAi);
result.checkIsClear();
}
- 영역 대체 과목
Set<Course> areaAltCourseSet
이면 건너뜁니다. - take의 영역이
Category.SW_AI
라면 수강내역에서 삭제하고result
에 기록합니다.- 따라서 사용자가 take 영역을
Category.SW_AI
로 인위적으로 설정하면, SW_AI 영역 과목으로 인정되게 됩니다.
- 따라서 사용자가 take 영역을
-
result
에 SW-AI 영역의 필수 이수 학점을 설정하고, 졸업 요건 충족 여부를 확인합니다.
참고: 모든 영역에 대한 Processor 들이 SW-AI 처럼 전략패턴을 사용하기 때문에, 앞으로는 실제 구현체에 대해서만 설명합니다.
CommonCreativityStrategy
는 일반적인 학과의 창의 영역 졸업 요건을 검증하는 구현 클래스입니다. CreativityStrategy
인터페이스를 구현하여 창의 과목의 이수 여부를 확인하고 졸업 요건을 검증합니다.
execute(CreativityDTO request, LinkedList allTakeList)
@Override
public ProcessorResponse.CreativityDTO execute(
ProcessorRequest.CreativityDTO request,
LinkedList<Take> allTakeList
) {
CreativityResult result = new CreativityResult();
handleResult(
allTakeList,
request.creativity(),
request.alternativeCourse(),
result
);
return ProcessorConverter.to(result);
}
기능
- 주어진 request(CreativityDTO)와 수강 과목 리스트(allTakeList)를 기반으로 창의 영역의 검증을 수행합니다.
검증 로직 처리
- handleResult() 메서드를 호출하여 창의 영역 과목을 처리하고 검증 결과를 도메인 객체(CreativityResult)에 저장합니다. Converter를 사용해 검증 결과(CreativityDTO)를 생성합니다.
handleResult()
private void handleResult(
LinkedList<Take> allTakeList,
Creativity creativity,
AlternativeCourse alternativeCourse,
CreativityResult result
) {
Iterator<Take> iterator = allTakeList.iterator();
while (iterator.hasNext()) {
Take take = iterator.next();
if (GraduationUtils.isApprovedCategory(take, Category.창의)) {
result.update(take, iterator);
continue;
}
if (GraduationUtils.isCodeAlternative(
take,
creativity.getApprovedCodeSet(),
alternativeCourse
)) {
result.update(take, iterator);
}
}
result.setRequiredCredit(creativity);
result.checkIsClear();
}
기능
- 주어진 창의 영역 과목과 대체 과목(과목에 대한) 정보를 사용하여 수강 과목 리스트에서 창의 요건을 충족하는 과목을 처리합니다.
처리 로직
- take의 카테고리가 창의인 경우
- 도메인 객체(CreativityResult)에 update 하고, 해당 과목을 수강 리스트에서 삭제합니다.
- 대체 과목(과목에 대한)으로 인정되는 경우
- 대체 과목(alternativeCourse)을 통해 인정되는 경우, 카테고리에 상관없이 해당 과목을 도메인 객체(CreativityResult)에 update 하고 수강 리스트에서 삭제합니다.
- 모든 TakeList의 확인이 끝나면, 창의 영역의 필수 이수 학점을 도메인 객체(CreativityResult)에 설정합니다.
- 필수 학점이 충족되었는지 확인합니다.
검증에 사용된 메소드 GraduationUtils.isApprovedCategory(take, Category.창의):
public static boolean isApprovedCategory(Take take, Category category) {
return take.getCategory() == category;
}
-
기능
- 특정 Take 객체가 주어진 Category와 일치하는지 확인합니다.
-
매개변수
-
Take take
: 검증할 수강 과목 객체입니다. -
Category category
: 검증 대상인 카테고리 값입니다.
-
-
반환값
- true: 수강 과목의 카테고리가 주어진 category와 일치하는 경우.
- false: 그렇지 않은 경우.
GraduationUtils.isCodeAlternative(take, approvedCodeSet, alternativeCourse):
public static boolean isCodeAlternative(
Take take,
Set<String> approvedCodeSet,
AlternativeCourse alternativeCourse
) {
String takenCode = take.getEffectiveCourse().getCode();
Set<String> takenAltCodeSet = alternativeCourse.getAlternativeCodeSet(takenCode);
for (String altCode : takenAltCodeSet) {
if (approvedCodeSet.contains(altCode)) {
return true;
}
}
return false;
}
설명
-
기능
- 특정 Take 객체의 대체과목이 인정되는 코드 집합(여기서는 창의CodeSet)에 속하는지 확인합니다.
-
매개변수
-
Take take
: 검증할 수강 과목 객체입니다. -
Set<String> approvedCodeSet
: 인정되는 과목 코드 -
AlternativeCourse alternativeCourse
: 수업에 대한 대체과목을 담고 있는 객체입니다.
-
-
반환값
- true: 과목의 대체과목이 인정되는 코드 집합에 속하는 경우
- false: 그렇지 않은 경우
CommonCoreStrategy
는 일반적인 학과의 핵심 교양(Core) 영역 졸업 요건을 검증하는 구현 클래스입니다. CoreStrategy
인터페이스를 구현하며, 공통된 검증 로직을 제공합니다.
execute(CoreDTO request, LinkedList allTakeList)
@Override
public ProcessorResponse.CoreDTO execute(
ProcessorRequest.CoreDTO request,
LinkedList<Take> allTakeList
) {
CoreResult result = new CoreResult();
result.initUncompletedArea(request.core());
// 영역 대체 과목
Set<Course> areaAltCourseSet = handleAreaAlternative(
allTakeList,
request.core(),
request.alternativeCourse(),
result
);
handleResult(
request.core(),
allTakeList,
areaAltCourseSet,
result
);
return ProcessorConverter.to(result);
}
기능
- 주어진 요청(CoreDTO)과 수강 과목 리스트(allTakeList)를 기반으로 핵심 교양 영역의 검증을 수행합니다.
수행 과정
- 영역 대체 과목 처리: handleAreaAlternative() 메서드를 호출하여 영역 대체 과목을 처리합니다.
- 핵심 교양 과목 처리: handleResult() 메서드를 호출하여 핵심 교양 과목을 처리합니다. 이후 Converter를 통해 응답 값을 반환합니다.
handleAreaAlternative( … )
private Set<Course> handleAreaAlternative(
LinkedList<Take> allTakeList,
Core core,
AlternativeCourse alternativeCourse,
CoreResult result
) {
Set<Course> areaAltCourseSet = new HashSet<>();
Iterator<Take> iterator = allTakeList.iterator();
while (iterator.hasNext()) {
Take take = iterator.next();
Set<Category> requiredAreaSet = getRequiredAreaSetWhenHandleAreaAlt(core);
for (Category area : requiredAreaSet) {
Set<String> areaAlternativeCodeSet = core.getAreaAlternativeCodeSet(area);
if (GraduationUtils.isApproved(take, areaAlternativeCodeSet)) {
result.update(take, iterator, area, true);
areaAltCourseSet.add(take.getEffectiveCourse());
continue;
}
if (GraduationUtils.isCodeAlternative(
take,
areaAlternativeCodeSet,
alternativeCourse)
) {
result.update(take, iterator, area, true);
areaAltCourseSet.add(take.getEffectiveCourse());
}
}
}
return areaAltCourseSet;
}
기능
- 영역 대체 과목을 처리합니다. 처리 로직
- 영역 대체 과목: 각 영역에 대해, take가 영역 대체 과목에 해당하는지 확인합니다.
-
응답 업데이트: 대체 과목에 해당하는 경우 도메인 객체(CoreResult)에 반영하고, 대체된 과목은 집합(Set) 형태로 저장합니다. 이 때는 리스트에서 Take를 삭제하지 않습니다. 영역 대체 과목은 다른 영역에도 영향을 주기 때문입니다.
- ex. 컴퓨터공학과 이산수학은 교양필수이자, 핵심교양6을 이수한 것으로 인정되어야 한다.
반환값
- 영역 대체 과목 집합(Set)을 반환하여, 이후 Core 로직에서 처리가 되지 않도록 합니다.
handleResult()
private void handleResult(
Core core,
LinkedList<Take> allTakeList,
Set<Course> areaAltCourseSet,
CoreResult result
) {
Iterator<Take> iterator = allTakeList.iterator();
while (iterator.hasNext()) {
Take take = iterator.next();
if (GraduationUtils.isAlreadyCheckedByAreaAlt(take, areaAltCourseSet)) {
continue;
}
if (isCategoryApprovedToCore(take.getCategory(), core)) {
result.update(take, iterator, take.getCategory(), false);
}
}
result.setRequiredCredit(core);
result.checkIsClear(core);
}
처리 로직
-
영역 대체 과목 건너뛰기
- 이미 영역 대체 과목으로 처리된 과목은 건너뜁니다.
-
핵심 교양 과목 검증
- take가 핵심 교양으로 인정되는 경우 도메인 객체(CoreResult)에 업데이트하고, 리스트에서 삭제합니다.
-
필수 학점 설정
- 핵심 교양의 필수 이수 학점을 도메인 객체(CoreResult)에 설정합니다.
-
졸업 요건 충족 여부 확인
- 영역이 상관없으면, 필요학점 이상 이후 했는지 확인합니다.
- 영역이 지정되어있으면, 미이수 영역이 empty 인지 확인합니다.
isCategoryApprovedToCore()
private boolean isCategoryApprovedToCore(Category category, Core core) {
if (!core.getIsAreaFixed()) {
return GraduationUtils.CORE_CATEGORYSET.contains(category);
}
return core.getRequiredAreaSet().contains(category);
}
기능
- 주어진 Category가 핵심 교양(Core) 영역으로 인정되는지 확인합니다.
처리 로직
-
영역이 고정되지 않은 경우
(isAreaFixed == false)
- GraduationUtils.CORE_CATEGORYSET에 해당하는지 확인합니다.
-
영역이 고정된 경우
(isAreaFixed == true)
- 주어진 Category가 핵심 교양의 필수 영역(
core.getRequiredAreaSet()
)에 포함되는지 확인합니다.
- 주어진 Category가 핵심 교양의 필수 영역(
반환값
- true: 주어진 카테고리가 핵심 교양으로 인정되는 경우
- false: 그렇지 않은 경우
CommonMajorRequiredStrategy
는 일반적인 학과의 주전공 전공 필수(MajorRequired) 영역 졸업 요건을 검증하는 구현 클래스입니다. MajorRequiredStrategy
인터페이스를 구현하며, 공통된 검증 로직을 제공합니다.
execute(MajorRequiredDTO request, LinkedList allTakeList)
@Override
public ProcessorResponse.MajorRequiredDTO execute(
ProcessorRequest.MajorRequiredDTO request,
LinkedList<Take> allTakeList
) {
MajorRequiredResult result = new MajorRequiredResult();
result.initMajorRequiredResult(
request.CourseListWithData().essentialCourseList()
);
handleResult(
allTakeList,
request.CourseListWithData(),
request.alternativeCourse(),
result
);
return ProcessorConverter.to(result);
}
기능
- 주어진 요청(
MajorRequiredDTO
)과 수강 과목 리스트(allTakeList
)를 기반으로 주전공 전공 필수 영역의 검사를 수행합니다.
수행 과정
- 응답 초기화
-
initMajorRequiredResult()
메서드를 호출하여 전공필수 과목 리스트를 기반으로 도메인 객체(MajorRequiredResult
)를 초기화합니다.
-
- 필수로 들어야하는 과목들을 메모리 상에 올려놓고, 들은 과목이 있으면 하나씩 제거하면서, 최종적으로 듣지 않은 필수과목들을 리턴하는 방식입니다.
- 전공 필수 과목 처리
-
handleResult()
메서드를 호출하여 전공 필수 과목을 처리하고 처리 결과를 반영합니다.
-
handleResult()
private void handleResult(
LinkedList<Take> allTakeList,
CourseListWithData<MajorRequired> courseListWithData,
AlternativeCourse alternativeCourse,
MajorRequiredResult result
) {
List<Course> majorRequiredCourseList = courseListWithData.essentialCourseList();
Set<String> majorRequiredCodeSet = courseListWithData.data().getCodeSet();
Iterator<Take> iterator = allTakeList.iterator();
while (iterator.hasNext()) {
Take take = iterator.next();
if (GraduationUtils.isApproved(take, majorRequiredCodeSet)) {
result.update(take, take.getEffectiveCourse().getCode(), iterator);
continue;
}
GraduationUtils.getAlternativeCode(take, majorRequiredCodeSet, alternativeCourse)
.ifPresent(
alternativeCode -> result.update(take, alternativeCode, iterator)
);
}
result.setRequiredCredit(majorRequiredCourseList);
result.setUncompletedCourseList();
result.checkIsClear();
}
-
기능
- 주어진 주전공 전공 필수 과목 리스트와 수강 리스트를 기반으로 로직을 수행하고, 검증 결과를 도메인 객체(
MajorRequiredResult
)에 반영합니다.
- 주어진 주전공 전공 필수 과목 리스트와 수강 리스트를 기반으로 로직을 수행하고, 검증 결과를 도메인 객체(
-
수행 과정
-
전공 필수 과목 코드로 인정
- 수강 과목의 코드가 전공 필수 과목 코드 집합(
majorRequiredCodeSet
)로 인정되면 도메인 객체(MajorRequiredResult
)에 업데이트하고, 리스트에서 삭제합니다.
- 수강 과목의 코드가 전공 필수 과목 코드 집합(
-
대체 과목 검증
-
GraduationUtils.getAlternativeCode()
를 통해 들은 수업의 대체과목이 전공 필수 과목 코드 집합(majorRequiredCodeSet
)로 인정되면 도메인 객체(MajorRequiredResult
)에 반영하고 리스트에서 삭제합니다.
-
-
전공 필수 과목 코드로 인정
-
응답 업데이트
-
필수 학점 설정
- 전공 필수 과목의 필수 이수 학점을 도메인 객체(
MajorRequiredResult
)에 설정합니다.
- 전공 필수 과목의 필수 이수 학점을 도메인 객체(
-
미이수 과목 리스트 설정
- 아직 이수되지 않은 과목 리스트를 설정합니다.
-
졸업 요건 충족 여부 확인
- 모든 검증 후 필수 과목이 모두 이수되었는지 확인합니다.
-
필수 학점 설정
CommonMajorSelectStrategy
는 일반적인 학과의 주전공 전공 선택(MajorSelect
) 영역 졸업 요건을 검증하기 위한 전략 구현 클래스입니다.
execute(MajorSelectDTO request, LinkedList allTakeList)
@Override
public ProcessorResponse.MajorSelectDTO execute(
ProcessorRequest.MajorSelectDTO request,
LinkedList<Take> allTakeList
) {
MajorSelectResult result = new MajorSelectResult();
result.initMajorSelectResult(request.creditWithData());
handleResult(
allTakeList,
request.creditWithData(),
request.alternativeCourse(),
result
);
return ProcessorConverter.to(result);
}
기능
- 주어진 요청(
MajorSelectDTO
)과 수강 과목 리스트(allTakeList
)를 기반으로 전공선택 영역의 검사를 수행합니다.
수행 과정
-
응답 초기화
-
initMajorSelectResult()
메서드를 호출하여 도메인 객체(MajorSelectResult
)를 초기화합니다.
-
-
전공필수를 얼마나 들었는지, 총 전공을 몇 학점 들어야 하는지 미리 셋팅합니다.
-
전공 선택 과목 처리
-
handleResult()
메서드를 호출하여 전공선택 검사를 수행하고, 도메인 객체(MajorSelectResult
)에 반영합니다.
-
handleResult()
private void handleResult(
LinkedList<Take> allTakeList,
CreditWithData creditWithData,
AlternativeCourse alternativeCourse,
MajorSelectResult result
) {
Set<String> majorSelectCodeSet = creditWithData.majorSelect().getCodeSet();
Iterator<Take> iterator = allTakeList.iterator();
while (iterator.hasNext()) {
Take take = iterator.next();
if (isCategoryGeneralRequired(take, majorSelectCodeSet)) {
result.update(take, iterator);
continue;
}
if (GraduationUtils.isCodeAlternative(
take,
majorSelectCodeSet,
alternativeCourse)
) {
result.update(take, iterator);
}
}
result.checkIsClear();
}
기능
- 주어진 전공선택 코드 집합와 수강 리스트를 기반으로 로직을 수행하고, 검증 결과를 도메인 객체(
MajorSelectResult
)에 반영합니다.
수행 과정
-
전공 선택 과목으로 인정
- 수강 과목의 카테고리가 전공 선택(
Category.전공선택
)이거나, 전공선택 코드 집합(majorSelectCodeSet
)에 해당하면 도메인 객체(MajorSelectResult
)를 업데이트하고 리스트에서 삭제합니다.
- 수강 과목의 카테고리가 전공 선택(
-
대체 과목 검증
-
GraduationUtils.isCodeAlternative()
를 통해 수강한 과목의 대체 코드가 전공 선택 과목 코드 집합(majorSelectCodeSet
)에 해당되면 도메인 객체(MajorSelectResult
)를 업데이트하고 리스트에서 삭제합니다.
-
-
응답 업데이트
-
졸업 요건 충족 여부 확인
- 모든 검증 후, 전공필수와 전공선택 이수학점을 합친 값과 전공 필요학점을 비교하여 도메인 객체(
MajorSelectResult
)에 반영합니다.
- 모든 검증 후, 전공필수와 전공선택 이수학점을 합친 값과 전공 필요학점을 비교하여 도메인 객체(
-
졸업 요건 충족 여부 확인
⠀
CommonGeneralRequiredStrategy
는 일반적인 학과의 교양 필수(GeneralRequired) 영역 졸업 요건을 검증하기 위한 전략 구현 클래스입니다. 영어 과목의 이수 여부에 따라 학점 계산과 졸업 요건 충족 여부가 달라질 수 있으므로, 입학년도(joinYear)에 따라 분기 처리를 수행합니다.
execute(GeneralRequiredDTO request, LinkedList allTakeList)
@Override
public ProcessorResponse.GeneralRequiredDTO execute(
ProcessorRequest.GeneralRequiredDTO request,
LinkedList<Take> allTakeList
) {
GeneralRequiredResult result = new GeneralRequiredResult();
result.initGeneralRequiredResult(
request.CourseListWithData().essentialCourseList()
);
handleResult(
allTakeList,
request.CourseListWithData(),
request.alternativeCourse(),
request.joinYear(),
result
);
return ProcessorConverter.to(result);
}
기능
- 주어진 요청(
GeneralRequiredDTO
)과 수강 과목 리스트(allTakeList
)를 기반으로 교양필수 영역의 검사를 수행합니다.
⠀수행 과정
-
응답 초기화
-
initGeneralRequiredResult()
메서드를 호출하여 필수 교양 과목 리스트를 기반으로 도메인 객체(GeneralRequiredResult
)를 초기화합니다.
-
- 필수로 들어야 하는 과목들을 메모리에 올려놓고, 학생이 수강한 과목이 있다면 하나씩 제거하며, 최종적으로 수강하지 않은 필수 과목들을 남깁니다.
-
교양필수 과목처리
-
handleResult()
메서드를 호출하여 교양필수 과목을 처리하고 처리 결과를 도메인 객체(GeneralRequiredResult
)에 반영합니다.
-
⠀
handleResult()
private void handleResult(
LinkedList<Take> allTakeList,
CourseListWithData<GeneralRequired> CourseListWithData,
AlternativeCourse alternativeCourse,
final int joinYear,
GeneralRequiredResult result
) {
List<Course> generalRequiredCourseList = CourseListWithData.essentialCourseList();
Set<String> generalRequiredCodeSet = CourseListWithData.data().getCodeSet();
Iterator<Take> iterator = allTakeList.iterator();
while (iterator.hasNext()) {
Take take = iterator.next();
if (GraduationUtils.isApproved(take, generalRequiredCodeSet)) {
result.update(take, take.getEffectiveCourse().getCode(), iterator, joinYear);
continue;
}
GraduationUtils.getAlternativeCode(take, generalRequiredCodeSet, alternativeCourse)
.ifPresent(
alternativeCode -> result.update(take, alternativeCode, iterator, joinYear)
);
}
result.setRequiredCredit(generalRequiredCourseList, joinYear);
result.setUncompletedCourseList();
result.checkIsClear(joinYear);
}
기능
- 주어진 교양 필수 과목 리스트와 수강 리스트를 기반으로 검증 로직을 수행하고, 검증 결과를 도메인 객체(
GeneralRequiredResult
)에 반영합니다.
수행 과정
-
교양 필수 과목 코드로 인정
- 수강 과목의 코드가 교양필수 코드집합(
generalRequiredCodeSet
)에 해당되면 도메인 객체(GeneralRequiredResult
)에 업데이트하고 리스트에서 삭제합니다.
- 수강 과목의 코드가 교양필수 코드집합(
-
대체 과목 검증
-
GraduationUtils.getAlternativeCode()
를 통해 수강한 과목의 대체 코드가 교양필수 과목 코드 집합(generalRequiredCodeSet
)에 해당되면 도메인 객체(GeneralRequiredResult
)에 반영하고 리스트에서 삭제합니다.
-
응답 업데이트
-
필수 학점
- 교양필수 과목의 필수 이수 학점을 도메인 객체(
GeneralRequiredResult
)에 설정합니다. - 영어로 인해 입학년도(joinYear)에 따라 분기처리하여 학점 계산이 달라질 수 있습니다.
- 교양필수 과목의 필수 이수 학점을 도메인 객체(
-
미이수 과목 리스트
- 아직 이수되지 않은 과목 리스트를 도메인 객체(
GeneralRequiredResult
)에 셋팅합니다. - 영어로 인해 입학년도(joinYear)에 따라 분기처리하여 로직을 수행합니다.
- 아직 이수되지 않은 과목 리스트를 도메인 객체(
-
졸업 요건 충족 여부 확인
- 모든 검증 후 필수과목이 모두 이수되었는지 확인하여 도메인 객체(
GeneralRequiredResult
)에 반영합니다. - 학생의 영어 과목의 이수 여부에 따라 미이수 과목 리스트에서 영어 과목을 제거할 수 있습니다.
- 모든 검증 후 필수과목이 모두 이수되었는지 확인하여 도메인 객체(