실무 프로젝트로 배우는 Kotlin & Spring/자바 프로젝트(ToDo프로젝트) 코틀린으로 리팩토링 하기

Controller, Dto 리펙토링

webmaster 2022. 11. 5. 16:38
728x90

Controller

JAVA 컨트롤러

@RestController
@RequestMapping("/api/todos")
public class TodoController {

    private final TodoService todoService;

    public TodoController(TodoService todoService) {
        this.todoService = todoService;
    }

    @GetMapping
    public ResponseEntity<TodoListResponse> getAll() {
        List<Todo> todos = todoService.findAll();
        return ResponseEntity.ok(TodoListResponse.of(todos));
    }

    @GetMapping("/{id}")
    public ResponseEntity<TodoResponse> get(@PathVariable Long id) {
        Todo todo = todoService.findById(id);
        return ResponseEntity.ok(TodoResponse.of(todo));
    }

    @PostMapping
    public ResponseEntity<TodoResponse> create(@RequestBody TodoRequest request) {
        Todo todo = todoService.create(request);
        return ResponseEntity.ok(TodoResponse.of(todo));
    }

    @PutMapping("/{id}")
    public ResponseEntity<TodoResponse> update(@PathVariable Long id,
                                               @RequestBody TodoRequest request) {
        Todo todo = todoService.update(id, request);
        return ResponseEntity.ok(TodoResponse.of(todo));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        todoService.delete(id);
        return ResponseEntity.noContent().build();
    }

}

Kotlin 컨트롤러

@RestController
@RequestMapping("/api/todos")
class TodoController(
    private val todoService: TodoService,
) {
    @GetMapping
    fun getAll() = ok(TodoListResponse.of(todoService.findAll()))

    @GetMapping("/{id}")
    fun get(@PathVariable id: Long) =
        ok(TodoResponse.of(todoService.findById(id)))

    @PostMapping
    fun create(@RequestBody request: TodoRequest) =
        ok(TodoResponse.of(todoService.create(request)))

    @PutMapping("/{id}")
    fun update(@PathVariable id: Long, @RequestBody request: TodoRequest) =
        ok(TodoResponse.of(todoService.update(id, request)))

    @DeleteMapping("/{id}")
    fun delete(@PathVariable id: Long): ResponseEntity<Unit> {
        todoService.delete(id)
        return noContent().build()
    }
}
  • 자바 코드처럼 블록을 사용하지 않고 바로 리턴하도록 작성할 수 있다.
  • 생성자에 빈으로 주입받을 서비스를 쓴다.
  • delete 같은 경우 Java에서는 Void를 반환했지만, Kotlin에서는 Unit을 반환하면 된다.

Dto 

 TodoListReponse

JavaCode

@Data
public class TodoListResponse {

    private final List<TodoResponse> items;

    private TodoListResponse(List<TodoResponse> items) {
        this.items = items;
    }

    public int size() {
        return items.size();
    }

    public TodoResponse get(int index) {
        return items.get(index);
    }

    public static TodoListResponse of(List<Todo> todoList) {
        List<TodoResponse> todoListResponse = todoList.stream()
            .map(TodoResponse::of)
            .collect(Collectors.toList());

        return new TodoListResponse(todoListResponse);
    }

}

KotlinCode

data class TodoListResponse(
    val items: List<TodoResponse>,
) {
    val size: Int
        @JsonIgnore //Jackson Json에서 size를 프로퍼티로 내려주기 떄문에 해당 애노테이션을 추가해주어야 한다.
        get() = items.size //유틸리티성 프로퍼티를 만들어 함수를 만들지 않고 size를 반환한다.

    fun get(index: Int) = items[index]

    companion object {
        fun of(todoList: List<Todo>) =
            //TodoListResponse(todoList.map { TodoResponse.of(it) })
            TodoListResponse(todoList.map(TodoResponse::of))
    }
}
  • size 같은경우 프로퍼티로 만들어 커스텀 getter를 통해 원하는 값을 반환하도록 하는 것이 좋다.
    • Jackson의 Json에서 프로퍼티로 내려주기 때문에 @JsonIgnore 애노테이션을 통해 제외 처리를 해야 한다.
  • of 같은 경우 static 함수로 companion으로 동반 객체를 만들어 내부에 함수로 사용하고, 스트림 같은 경우 람다 레퍼런스와 인라인 함수(map)를 통해 JAVA보다 간결하게 작성할 수 있다.

TodoResponse

JavaCode

@Data
@Builder
public class TodoResponse {

    private Long id;

    private String title;

    private String description;

    private Boolean done;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;

    public static TodoResponse of(Todo todo) {
        Assert.notNull(todo, "Todo is null");

        return TodoResponse.builder()
            .id(todo.getId())
            .title(todo.getTitle())
            .description(todo.getDescription())
            .done(todo.getDone())
            .createdAt(todo.getCreatedAt())
            .updatedAt(todo.getUpdatedAt())
            .build();
    }

}

KotlinCode

data class TodoResponse(
    val id: Long,
    val title: String,
    val description: String,
    val done: Boolean,
    val createdAt: LocalDateTime,
    val updatedAt: LocalDateTime
) {
    companion object {
        fun of(todo: Todo?): TodoResponse {
            checkNotNull(todo) { "Todo is null" }
            return TodoResponse(
                id = todo.id,
                title = todo.title,
                description = todo.description,
                done = todo.done,
                createdAt = todo.createdAt,
                updatedAt = todo.updatedAt,
            )
        }
    }
}
  • Null이 올 수도 있기 때문에 ?연산자를 통해 Nullable임을 컴파일러에 알려준다.
  • checkNotNull 함수를 통해 해당 데이터가 널 인지 확인한다.

TodoRequest

JavaCode

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TodoRequest {

    private String title;

    private String description;

    private Boolean done = false;
}

KotlinCode

data class TodoRequest(
    val title: String,
    val description: String,
    val done: Boolean = false,
)
728x90