들어가면서
안녕하세요. 요즘에 플러피(fluffy)라는 온라인 시험 문제 제작 및 관리 서비스를 만들고 있습니다.
플러피에서는 단답형, 서술형, 객관식 단일 선택, 객관식 복수 선택, True/False 등 다양한 문제 유형을 지원하고 있습니다.
이에 따라 요청 데이터에도 다양한 형태의 문제 데이터를 받아야 합니다.
예를 들어, 단답형 문제와 객관식 단일 선택의 경우 질문은 동일하게 필요하지만, 옵션 여부나 정답 형태가 다릅니다.
다음의 코드를 참고해주세요.
// 단답형 문제 요청 데이터
{
"text": "1 + 1 = ?",
"type": "SHORT_ANSWER",
"correctAnswer": "2"
}
// 객관식 단일 선택 문제 요청 데이터
{
"text": "1 + 1 = ?",
"type": "SINGLE_CHOICE",
"options": [
{
"text": "1",
"isCorrect": false
},
{
"text": "2",
"isCorrect": true
},
{
"text": "3",
"isCorrect": false
}
]
}
요청에서 다형성을 지원하는 방법
다형성을 지원하기 위한 방법으로 Jackson 라이브러리에서 제공하는 @JsonTypeInfo
와 @JsonSubTypes
를 활용할 수 있습니다.@JsonTypeInfo
는 직렬화 및 역직렬화 시에 타입 정보를 사용할지 여부를 설정하며, @JsonSubTypes
는 다형성을 지원하는 서브 타입을 정의합니다.
@JsonTypeInfo
@JsonTypeInfo는 use, property, include,visible 속성을 제공합니다.
use는 타입 정보를 어떻게 사용할지를 지정합니다.
JsonTypeInfo.Id.CLASS
: 클래스 이름을 사용합니다.JsonTypeInfo.Id.MINIMAL_CLASS
: 클래스 이름의 마지막 부분만 사용합니다.JsonTypeInfo.Id.NAME
: 이름을 사용합니다.JsonTypeInfo.Id.CUSTOM
: 사용자 정의 타입 정보를 사용합니다.JsonTypeInfo.Id.NONE
: 타입 정보를 사용하지 않습니다.
property는 타입 정보를 포함할 프로퍼티 이름을 지정합니다.
include는 타입 정보를 어디에 포함할지를 지정합니다.
JsonTypeInfo.As.PROPERTY
: 프로퍼티에 포함합니다. 기본값입니다.JsonTypeInfo.As.WRAPPER_OBJECT
: 객체로 감싸서 포함합니다.JsonTypeInfo.As.WRAPPER_ARRAY
: 배열로 감싸서 포함합니다.JsonTypeInfo.As.EXTERNAL_PROPERTY
: 외부 프로퍼티에 포함합니다.JsonTypeInfo.As.EXISTING_PROPERTY
: 이미 존재하는 프로퍼티에 포함합니다.
visible은 타입 정보를 포함할지 여부를 지정합니다. 기본값은 false입니다.
@JsonSubTypes
@JsonSubTypes는 value와 name 속성을 제공합니다.
value는 매핑할 하위 클래스를 지정하며, name은 해당 하위 클래스에 대한 이름을 지정합니다.
@JsonTypeInfo와 @JsonSubTypes 사용 예시
예시를 통해서 사용 방법을 알아보겠습니다.
먼저, @JsonTypeInfo와 @JsonSubTypes를 사용한 QuestionRequest 인터페이스를 정의하겠습니다.
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME, // 이름을 사용한다.
property = "questionType", // questionType을 기준으로 구분한다.
include = As.EXISTING_PROPERTY, // 이미 존재하는 questionType 프로퍼티를 사용한다.
visible = true // false로 설정하면 직렬화 시에 questionType이 포함되지 않는다.
)
@JsonSubTypes({
@JsonSubTypes.Type(value = ShortAnswerQuestionRequest.class, name = "SHORT_ANSWER"),
@JsonSubTypes.Type(value = MultipleChoiceQuestionRequest.class, name = "MULTIPLE_CHOICE")
})
public interface QuestionRequest {
String questionText();
String questionType();
}
다음으로, QuestionRequest 인터페이스를 구현한 ShortAnswerQuestionRequest와 MultipleChoiceQuestionRequest 클래스를 정의하겠습니다.
public record ShortAnswerQuestionRequest(
String questionText,
String questionType,
String correctAnswer
) implements QuestionRequest {
}
public record SingleChoiceQuestionRequest(
String questionText,
String questionType,
List<Choice> options
) implements QuestionRequest {
public record Choice(
String text,
boolean isCorrect
) {
}
}
위에서 정의한 클래스들을 사용하여 다음과 같이 직렬화 및 역직렬화를 수행할 수 있습니다.
public class Main {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
// 직렬화
String shortAnswerJson = objectMapper.writeValueAsString(
new ShortAnswerQuestionRequest("1 + 1 = ?", "SHORT_ANSWER", "2")
);
System.out.println(shortAnswerJson);
String singleChoiceJson = objectMapper.writeValueAsString(
new SingleChoiceQuestionRequest("1 + 1 = ?", "SINGLE_CHOICE",
List.of(
new SingleChoiceQuestionRequest.Choice("1", false),
new SingleChoiceQuestionRequest.Choice("2", true),
new SingleChoiceQuestionRequest.Choice("3", false)
)
)
);
System.out.println(singleChoiceJson);
// 역직렬화
QuestionRequest shortAnswerQuestionRequest = objectMapper.readValue(shortAnswerJson, QuestionRequest.class);
System.out.println(shortAnswerQuestionRequest);
QuestionRequest singleChoiceQuestionRequest = objectMapper.readValue(singleChoiceJson, QuestionRequest.class);
System.out.println(singleChoiceQuestionRequest);
}
}
위 코드를 실행하면 다음과 같이 출력되는 것을 확인할 수 있습니다.
{"questionText":"1 + 1 = ?","questionType":"SHORT_ANSWER","correctAnswer":"2"}
{"questionText":"1 + 1 = ?","questionType":"SINGLE_CHOICE","options":[{"text":"1","isCorrect":false},{"text":"2","isCorrect":true},{"text":"3","isCorrect":false}]}
ShortAnswerQuestionRequest[questionText=1 + 1 = ?, questionType=SHORT_ANSWER, correctAnswer=2]
SingleChoiceQuestionRequest[questionText=1 + 1 = ?, questionType=SINGLE_CHOICE, options=[SingleChoiceQuestionRequest.Choice[text=1, isCorrect=false], SingleChoiceQuestionRequest.Choice[text=2, isCorrect=true], SingleChoiceQuestionRequest.Choice[text=3, isCorrect=false]]]
실제 상황 사용 예시
실제 상황에서 활용할 때, 요청받은 QuestionRequest를 Question과 같은 도메인 객체로 변환하여 사용합니다.
그 경우 QuestionRequest에 있는 type(여기서는 questionType)을 사용하여 적절한 Question 객체로 변환하면 됩니다.
public Question toQuesiton(QuestionRequest request) {
QuestionType type = QuestionType.from(request.questionType());
return switch (type) {
case SHORT_ANSWER -> toQuestion((ShortAnswerQuestionRequest) request);
case SINGLE_CHOICE -> toQuestion((MultipleChoiceQuestionRequest) request);
...
};
}
이렇게 하면 요청 데이터에 다형성을 적용할 수 있으며, 요청 데이터에 따라 적절한 도메인 객체로 변환할 수 있습니다.
응답 데이터 다형성 적용 방법
다양한 문제 유형을 요청하는 경우가 있다면, 다양한 문제 유형을 응답하는 경우도 있을 것입니다.
이런 경우에는 QuestionResponse와 같은 인터페이스를 정의하여 다형성을 지원할 수 있습니다.
public interface QuestionResponse {
String questionText();
String questionType();
}
그리고 QuestionResponse를 구현한 ShortAnswerQuestionResponse와 MultipleChoiceQuestionResponse 클래스를 정의하겠습니다.
public record ShortAnswerQuestionResponse(
String questionText,
String questionType,
String correctAnswer
) implements QuestionResponse {
}
public record SingleChoiceQuestionResponse(
String questionText,
String questionType,
List<Choice> options
) implements QuestionResponse {
public record Choice(
String text,
boolean isCorrect
) {
}
}
이렇게 하면 응답 데이터에 다형성을 적용할 수 있습니다.
마찬가지로 type(여기서는 questionType)을 사용하여 적절한 응답 객체로 변환할 수 있습니다.
public QuestionResponse toResponse(Question question) {
QuestionType type = question.getType();
return switch (type) {
case SHORT_ANSWER -> toShortAnswerResponse(question);
case MULTIPLE_CHOICE -> toMultipleChoiceResponse(question);
};
}
마치면서
지금까지 요청 데이터에 다형성을 지원하기 위해 Jackson 라이브러리에서 제공하는 @JsonTypeInfo
와 @JsonSubTypes
를 활용하는 방법과 응답 데이터에 다형성을 지원하는 방법에 대해 알아보았습니다.
좋은 하루 보내세요 ⛅️
'웹 백엔드' 카테고리의 다른 글
무중단 배포(블루/그린 배포)로 서비스 중단 없이 배포하기 (0) | 2025.01.09 |
---|---|
Git Submodule(서브모듈)을 통해서 Spring 설정(yml) 관리하기 (0) | 2025.01.05 |
Flyway를 통한 데이터베이스 마이그레이션을 알아보자 (1) | 2025.01.03 |
Spring JPA를 사용할 때, OneToMany에서 Fetch Join 사용 시 문제점을 알아보자 (0) | 2024.12.23 |
Spring JPA를 사용할 때 findByXxxId의 문제점을 알아보자 (0) | 2024.12.23 |