-
Notifications
You must be signed in to change notification settings - Fork 0
Driving Adapter 와 UseCase 구현 #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 46 commits
2d39bb0
c80cd1f
5a93160
bca5627
2419069
cba79ca
747209f
9f8c1be
1d9c470
b6ef149
ed3750d
d2857ab
46362b7
96756de
0c4f906
81ae62d
5c07f42
bf819d6
6b14a72
e96cabe
2fe1b9c
44ae379
8d14bc6
85f4519
cc9f2be
2869742
a26fbfe
1ff1de5
19f9eeb
4df2745
a44c539
9675332
6482568
16be7a4
2fe8e09
b36855a
2c70e28
0291bea
93e03e5
14fad58
1195926
7e1f509
7e2f07a
665d31b
7f8057b
3fbc6df
4f1df36
68ae6d0
bda07b6
1a1d538
d549d9b
aeba058
2919162
9356e69
8fda352
5ade3f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
silberbullet marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package me.nettee.board.adapter.driving.web; | ||
|
||
import jakarta.validation.Valid; | ||
import lombok.RequiredArgsConstructor; | ||
import me.nettee.board.adapter.driving.web.dto.BoardCommandDto.BoardCommandResponse; | ||
import me.nettee.board.adapter.driving.web.dto.BoardCommandDto.BoardCreateCommand; | ||
import me.nettee.board.adapter.driving.web.dto.BoardCommandDto.BoardUpdateCommand; | ||
import me.nettee.board.adapter.driving.web.mapper.BoardDtoMapper; | ||
import me.nettee.board.application.usecase.BoardCreateUseCase; | ||
import me.nettee.board.application.usecase.BoardDeleteUseCase; | ||
import me.nettee.board.application.usecase.BoardUpdateUseCase; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.*; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎨 와일드카드 임포트(*, asterisk) 사용을 삼가는 것이 좋습니다.구글 Java 코드 스타일 가이드에서 제안하는 내용입니다. 다음은 최대한 사용을 삼가는 것이 좋습니다.
단, 정적 임포트( |
||
|
||
@RestController | ||
@RequestMapping("/api/v1/board") | ||
silberbullet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@RequiredArgsConstructor | ||
public class BoardCommandController { | ||
|
||
private final BoardCreateUseCase boardCreateUseCase; | ||
private final BoardUpdateUseCase boardUpdateUseCase; | ||
private final BoardDeleteUseCase boardDeleteUseCase; | ||
private final BoardDtoMapper boardDtoMapper; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💊 기호의 차이이지만, mapper로 명명하는 것을 제안드립니다.
따라서 복잡한 기능을 수행하는 다른 빈들에 비해 단순한 네이밍으로도 충분히 식별할 수 있습니다. 하지만 취향의 차이이기 때문에, 생각하시는 것과 다르다면 어떤 부분에서 다르게 생각하시는지 등 편하게 말씀 부탁드립니다! |
||
|
||
@PostMapping | ||
@ResponseStatus(HttpStatus.CREATED) | ||
public BoardCommandResponse createBoard(@RequestBody @Valid BoardCreateCommand boardCreateCommand) { | ||
// Map to Domain | ||
var board = boardDtoMapper.toDomain(boardCreateCommand); | ||
|
||
return BoardCommandResponse.builder() | ||
.board(boardCreateUseCase.createBoard(board)) | ||
.build(); | ||
} | ||
|
||
@PatchMapping("/{id}") | ||
@ResponseStatus(HttpStatus.OK) | ||
public BoardCommandResponse updateBoard(@PathVariable("id") Long id, | ||
@Valid @RequestBody BoardUpdateCommand boardUpdateCommand) { | ||
// Map to Domain | ||
var board = boardDtoMapper.toDomain(id, boardUpdateCommand); | ||
|
||
return BoardCommandResponse.builder() | ||
.board(boardUpdateUseCase.updateBoard(board)) | ||
.build(); | ||
} | ||
|
||
@DeleteMapping("/{id}") | ||
@ResponseStatus(HttpStatus.NO_CONTENT) | ||
public void deleteBoard(@PathVariable("id") Long id) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💊 Path Variables에서 기본 자료형을 사용해 NOT NULL을 표현할 수 있습니다.래퍼 클래스의 사용은 필요할 때만 사용하는 것이 고전적으로 권장되어 왔습니다.
이때는 파라미터로 바로 받고 있기 때문에, 원시 타입 개인적으로 이 계층만 생각했을 때는 |
||
boardDeleteUseCase.deleteBoard(id); | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💊 클래스 이름을
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package me.nettee.board.adapter.driving.web; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import me.nettee.board.adapter.driving.web.dto.BoardQueryDto.BoardDetailResponse; | ||
import me.nettee.board.adapter.driving.web.dto.BoardQueryDto.BoardSummaryResponse; | ||
import me.nettee.board.adapter.driving.web.mapper.BoardDtoMapper; | ||
import me.nettee.board.application.domain.type.BoardStatus; | ||
import me.nettee.board.application.usecase.BoardReadByStatusesUseCase; | ||
import me.nettee.board.application.usecase.BoardReadUseCase; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.web.bind.annotation.*; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎨 와일드카드 임포트를 삼가는 것이 좋습니다.자세한 이유는 다른 리뷰 항목에 포함하였습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @merge-simpson 좋은 의견 감사합니다 👍 |
||
import java.util.Set; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/boards") | ||
@RequiredArgsConstructor | ||
public class BoardQueryController { | ||
private final BoardReadUseCase boardReadUseCase; | ||
private final BoardDtoMapper boardDtoMapper; | ||
private final BoardReadByStatusesUseCase boardReadByStatusesUseCase; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎨 필드 선언 순서에서 mapper가 usecase들 사이로 침투해 있습니다.도메인중심적인 배치와 계층적인 배치에 대해 다음 전략을 선호하고 있습니다.
따라서 다음과 같은 배치를 제안드립니다. private final BoardReadUseCase boardReadUseCase;
private final BoardReadByStatusesUseCase boardReadByStatusesUseCase;
private final BoardDtoMapper boardDtoMapper; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @merge-simpson 좋은 의견 감사합니다 👍 |
||
|
||
@GetMapping("/{boardId}") | ||
public BoardDetailResponse getBoard(@PathVariable("boardId") long boardId) { | ||
var board = boardReadUseCase.getBoard(boardId); | ||
|
||
return boardDtoMapper.toDtoDetail(board); | ||
} | ||
|
||
@GetMapping | ||
public Page<BoardSummaryResponse> getBoardsByStatuses(@RequestParam Set<BoardStatus> statuses, Pageable pageable) { | ||
var board = boardReadByStatusesUseCase.findByStatuses(statuses, pageable); | ||
|
||
return board.map(boardDtoMapper::toDtoSummary); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package me.nettee.board.adapter.driving.web.dto; | ||
|
||
import com.fasterxml.jackson.annotation.JsonRootName; | ||
import jakarta.validation.constraints.NotBlank; | ||
import jakarta.validation.constraints.Size; | ||
import lombok.Builder; | ||
import me.nettee.board.application.domain.Board; | ||
|
||
public final class BoardCommandDto { | ||
|
||
private BoardCommandDto() { | ||
} | ||
|
||
public record BoardCreateCommand( | ||
@NotBlank(message = "제목을 입력하십시오.") | ||
@Size(min = 3, message = "제목은 세 글자 이상 입력하세요.") | ||
String title, | ||
@NotBlank(message = "본문을 입력하십시오") | ||
@Size(min = 3, message = "제목은 세 글자 이상 입력하세요.") | ||
String content | ||
) { | ||
} | ||
|
||
public record BoardUpdateCommand( | ||
@NotBlank(message = "제목을 입력하십시오.") | ||
@Size(min = 3, message = "제목은 세 글자 이상 입력하세요.") | ||
String title, | ||
@NotBlank(message = "본문을 입력하십시오") | ||
@Size(min = 3, message = "제목은 세 글자 이상 입력하세요.") | ||
String content | ||
) { | ||
} | ||
Comment on lines
+14
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💊 테스트 편의성을 위해 요청 DTO에도
|
||
|
||
@Builder | ||
@JsonRootName("board") | ||
public record BoardCommandResponse( | ||
Board board | ||
) { | ||
} | ||
Comment on lines
+34
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💊 이 조건에서
|
||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❗️ 애플리케이션·도메인 계층의 작업자들과 논의하여 ReadModel 등의 설계가 필요해 보입니다.BoardSummaryResponse도 필요하다면 생성할 수 있습니다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package me.nettee.board.adapter.driving.web.dto; | ||
|
||
import com.fasterxml.jackson.annotation.JsonRootName; | ||
import lombok.Builder; | ||
import me.nettee.board.application.domain.type.BoardStatus; | ||
|
||
import java.time.Instant; | ||
|
||
public final class BoardQueryDto { | ||
private BoardQueryDto() {} | ||
|
||
|
||
@Builder | ||
public record BoardSummaryResponse( | ||
Long id, | ||
String title, | ||
BoardStatus status, | ||
Instant createdAt | ||
){} | ||
|
||
|
||
@Builder | ||
@JsonRootName("board") | ||
public record BoardDetailResponse( | ||
Long id, | ||
String title, | ||
String content, | ||
BoardStatus status, | ||
Instant createdAt, | ||
Instant updatedAt | ||
){} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💊 유지보수성을 위해
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package me.nettee.board.adapter.driving.web.mapper; | ||
|
||
import me.nettee.board.adapter.driving.web.dto.BoardCommandDto.BoardCreateCommand; | ||
import me.nettee.board.adapter.driving.web.dto.BoardCommandDto.BoardUpdateCommand; | ||
import me.nettee.board.adapter.driving.web.dto.BoardQueryDto.BoardDetailResponse; | ||
import me.nettee.board.adapter.driving.web.dto.BoardQueryDto.BoardSummaryResponse; | ||
import me.nettee.board.application.domain.Board; | ||
import me.nettee.board.application.model.BoardReadDetailModel; | ||
import me.nettee.board.application.model.BoardReadSummaryModel; | ||
import org.mapstruct.BeanMapping; | ||
import org.mapstruct.Mapper; | ||
import org.mapstruct.Mapping; | ||
|
||
@Mapper(componentModel = "spring") | ||
public interface BoardDtoMapper { | ||
|
||
@BeanMapping(ignoreByDefault = true) | ||
@Mapping(target = "title", source = "title") | ||
@Mapping(target = "content", source = "content") | ||
Board toDomain(BoardCreateCommand command); | ||
|
||
@BeanMapping(ignoreByDefault = true) | ||
@Mapping(target = "id", source = "id") | ||
@Mapping(target = "title", source = "command.title") | ||
@Mapping(target = "content", source = "command.content") | ||
Board toDomain(Long id, BoardUpdateCommand command); | ||
|
||
BoardDetailResponse toDtoDetail(BoardReadDetailModel board); | ||
|
||
BoardSummaryResponse toDtoSummary(BoardReadSummaryModel board); | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package me.nettee.board.application.model; | ||
|
||
import me.nettee.board.application.domain.type.BoardStatus; | ||
|
||
import java.time.Instant; | ||
|
||
public record BoardReadDetailModel( | ||
Long id, | ||
String title, | ||
String content, | ||
BoardStatus status, | ||
Instant createdAt, | ||
Instant updatedAt | ||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package me.nettee.board.application.model; | ||
|
||
import me.nettee.board.application.domain.type.BoardStatus; | ||
|
||
import java.time.Instant; | ||
|
||
public record BoardReadSummaryModel( | ||
Long id, | ||
String title, | ||
String content, | ||
BoardStatus status, | ||
Instant createdAt, | ||
Instant updatedAt | ||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,16 @@ | ||
package me.nettee.board.application.port; | ||
|
||
import java.util.List; | ||
import me.nettee.board.application.domain.Board; | ||
import me.nettee.board.application.domain.type.BoardStatus; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.data.domain.Pageable; | ||
|
||
import java.util.Optional; | ||
|
||
public interface BoardQueryPort { | ||
|
||
Page<Board> findAll(Pageable pageable); | ||
|
||
Optional<Board> findById(Long id); | ||
|
||
Page<Board> findByStatusesList(Pageable pageable, List<BoardStatus> statuses); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package me.nettee.board.application.service; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import me.nettee.board.application.domain.Board; | ||
import me.nettee.board.application.port.BoardCommandPort; | ||
import me.nettee.board.application.usecase.BoardCreateUseCase; | ||
import me.nettee.board.application.usecase.BoardDeleteUseCase; | ||
import me.nettee.board.application.usecase.BoardUpdateUseCase; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class BoardCommandService implements BoardCreateUseCase, BoardUpdateUseCase, BoardDeleteUseCase { | ||
|
||
private final BoardCommandPort boardCommandPort; | ||
|
||
public Board createBoard(Board board) { | ||
return boardCommandPort.create(board); | ||
} | ||
|
||
public Board updateBoard(Board board) { | ||
return boardCommandPort.update(board); | ||
} | ||
|
||
public void deleteBoard(Long id) { | ||
boardCommandPort.delete(id); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package me.nettee.board.application.service; | ||
|
||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import me.nettee.board.application.domain.Board; | ||
import me.nettee.board.application.domain.type.BoardStatus; | ||
import me.nettee.board.application.port.BoardQueryPort; | ||
import me.nettee.board.application.usecase.BoardReadByStatusesUseCase; | ||
import me.nettee.board.application.usecase.BoardReadUseCase; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class BoardQueryService implements BoardReadUseCase, BoardReadByStatusesUseCase { | ||
|
||
private final BoardQueryPort boardQueryPort; | ||
|
||
@Override | ||
public Board getBoard(Long id) { | ||
return boardQueryPort.findById(id).orElseThrow( | ||
() -> new IllegalArgumentException("게시글을 찾을 수 없습니다.")); | ||
} | ||
|
||
@Override | ||
public Page<Board> findByStatuses(Pageable pageable, List<BoardStatus> statuses) { | ||
return boardQueryPort.findByStatusesList(pageable, statuses); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package me.nettee.board.application.usecase; | ||
|
||
import me.nettee.board.application.domain.type.BoardStatus; | ||
import me.nettee.board.application.model.BoardReadSummaryModel; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.data.domain.Pageable; | ||
import java.util.Set; | ||
|
||
public interface BoardReadByStatusesUseCase { | ||
|
||
Page<BoardReadSummaryModel> findByStatuses(Set<BoardStatus> statuses, Pageable pageable); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,11 @@ | ||
package me.nettee.board.application.usecase; | ||
|
||
import me.nettee.board.application.domain.Board; | ||
import me.nettee.board.application.model.BoardReadDetailModel; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.data.domain.Pageable; | ||
|
||
public interface BoardReadUseCase { | ||
|
||
Board getBoard(Long id); | ||
|
||
Page<Board> findGeneralBy(Pageable pageable); | ||
BoardReadDetailModel getBoard(Long id); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.