Skip to content

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

Merged
merged 56 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
2d39bb0
feat: add BoardDtoMapper, DTOs, and dependencies for board functionality
shin-jingyu Jan 5, 2025
c80cd1f
feat(board):add Command UseCase and Controller
silberbullet Jan 6, 2025
5a93160
feat(board) : merge main branch
silberbullet Jan 6, 2025
bca5627
feat. Board Service 구현
wch-os Jan 6, 2025
2419069
Merge remote-tracking branch 'origin/main' into feature/board-service
wch-os Jan 6, 2025
cba79ca
test. Board Service 단위 테스트
wch-os Jan 6, 2025
747209f
feat: Add Lombok dependency and new BoardQuery classes
shin-jingyu Jan 6, 2025
9f8c1be
feat(board) : try for board web module
silberbullet Jan 7, 2025
1d9c470
chore(board):merge main branch
silberbullet Jan 7, 2025
b6ef149
build(board):add Jackson library and configure jsonrootname
silberbullet Jan 7, 2025
ed3750d
feat: Update BoardQueryController, DTOs, and Mapper for board data ha…
shin-jingyu Jan 7, 2025
d2857ab
fix(board): fix dto and controller
silberbullet Jan 7, 2025
46362b7
test(board): add commandController unit test
silberbullet Jan 7, 2025
96756de
Merge branch 'main' into feature/board-service
wch-os Jan 8, 2025
0c4f906
feat: Add BoardQueryControllerTest, update RequestMapping in Controll…
shin-jingyu Jan 8, 2025
81ae62d
refactor: Remove unused code in BoardQueryControllerTest
shin-jingyu Jan 8, 2025
5c07f42
fix: Update boardPage handling in BoardQueryControllerTest.kt
shin-jingyu Jan 9, 2025
bf819d6
feat(board): BoardReadByStatusesUsecase 추가 구현
wch-os Jan 10, 2025
6b14a72
test(board): BoardService 테스트 코드 추가 및 리팩토링
wch-os Jan 10, 2025
e96cabe
refactor: Add beforeSpec initialization to BoardQueryControllerTest
shin-jingyu Jan 11, 2025
2fe1b9c
feat(board): add validation annotation
silberbullet Jan 12, 2025
44ae379
fix(board): fix httpstatus
silberbullet Jan 12, 2025
8d14bc6
fix(board): fix command controller test
silberbullet Jan 12, 2025
85f4519
refactor: refactor BoardQueryControllerTest
shin-jingyu Jan 13, 2025
cc9f2be
refactor: refactor BoardQueryControllerTest
shin-jingyu Jan 13, 2025
2869742
feat: Add BoardReadByStatusesUseCase and related functionality
shin-jingyu Jan 14, 2025
a26fbfe
fix(board): fix command controller and dto
silberbullet Jan 14, 2025
1ff1de5
merge(board): add BoardReadByStatusesUseCase
silberbullet Jan 14, 2025
19f9eeb
chore(board): rename BoardReadByStatusesUsecase
silberbullet Jan 14, 2025
4df2745
chore(board): fix BoardReadByStatusesUsecase and controller
silberbullet Jan 14, 2025
a44c539
test(board): refactor command controller test
silberbullet Jan 14, 2025
9675332
remove(board): BoardReadUseCase-findGeneralBy 삭제
wch-os Jan 15, 2025
6482568
merge(board): merge board-service branch
silberbullet Jan 15, 2025
16be7a4
chore(board): delete findGeneralBy methods
silberbullet Jan 15, 2025
2fe8e09
chore(board): delete findAll methods
shin-jingyu Jan 15, 2025
b36855a
fix(tests): change Bad Request to Not Found in test cases
shin-jingyu Jan 15, 2025
2c70e28
remove(board): BoardQueryPort-findAll 삭제
wch-os Jan 15, 2025
0291bea
chore: remove unused `spring` logging config from application-local.yml
shin-jingyu Jan 19, 2025
93e03e5
refactor: update request mapping to `boards` and remove controller-le…
shin-jingyu Jan 19, 2025
14fad58
refactor(board): refactor command controller
silberbullet Jan 19, 2025
1195926
Merge remote-tracking branch 'origin/main' into feature/driving-adapter
shin-jingyu Jan 20, 2025
7e1f509
fix(board): replaced the `List` parameter with a `Set` in `BoardReadB…
mentalage46 Jan 21, 2025
7e2f07a
fix(board): changed the parameter order of the findByStatuses method …
mentalage46 Jan 21, 2025
665d31b
feat(board): add `ReadModel` return type to `BoardQueryPort`
mentalage46 Jan 21, 2025
7f8057b
merge(board): merge feature config branch
silberbullet Jan 21, 2025
3fbc6df
build(board): add spring-starter-validation
silberbullet Jan 21, 2025
4f1df36
refactor(board): refactor board mapper using @BeanMapping
silberbullet Jan 21, 2025
68ae6d0
feat(board): add soft delete to board
wch-os Jan 22, 2025
bda07b6
Merge remote-tracking branch 'origin/feature/driving-adapter' into fe…
shin-jingyu Jan 22, 2025
1a1d538
feat(board): Add domain models and integrate into driving layer
shin-jingyu Jan 25, 2025
d549d9b
refactor(board): Update BoardReadDetailModel with modifications
shin-jingyu Jan 25, 2025
aeba058
refactor(board): code cleanup and update DTO
shin-jingyu Jan 26, 2025
2919162
refactor: Rename controllers to API and return Page directly
shin-jingyu Jan 26, 2025
9356e69
refactor: Rename controller to API
shin-jingyu Jan 26, 2025
8fda352
merge(board): merge board-service branch
silberbullet Jan 29, 2025
5ade3f8
fix(board): fix command url and dto and test code
silberbullet Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ repositories {
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")

// logging
implementation("org.springframework.boot:spring-boot-starter-log4j2")
Expand Down Expand Up @@ -76,6 +77,8 @@ dependencies {
testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
testImplementation("io.mockk:mockk:1.13.12")
testImplementation(kotlin("script-runtime"))
testCompileOnly("org.projectlombok:lombok") // 테스트 의존성 추가
testAnnotationProcessor("org.projectlombok:lombok") // 테스트 의존성 추가
testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.3")
}

Expand All @@ -100,8 +103,8 @@ tasks.withType<KotlinCompile> {

tasks.withType<JavaCompile> {
options.compilerArgs.addAll(listOf(
"--enable-preview"
//"-Amapstruct.defaultComponentModel=spring",
"--enable-preview",
"-Amapstruct.defaultComponentModel=spring",
))
}

Expand Down
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.*;

@RestController
@RequestMapping("/api/v1/boards")
@RequiredArgsConstructor
public class BoardCommandApi {

private final BoardCreateUseCase boardCreateUseCase;
private final BoardUpdateUseCase boardUpdateUseCase;
private final BoardDeleteUseCase boardDeleteUseCase;
private final BoardDtoMapper boardDtoMapper;

@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) {
boardDeleteUseCase.deleteBoard(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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.mapper.BoardDtoMapper;
import me.nettee.board.application.domain.type.BoardStatus;
import me.nettee.board.application.model.BoardReadSummaryModel;
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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Set;

@RestController
@RequestMapping("/api/v1/boards")
@RequiredArgsConstructor
public class BoardQueryApi {
private final BoardReadUseCase boardReadUseCase;
private final BoardReadByStatusesUseCase boardReadByStatusesUseCase;

private final BoardDtoMapper boardDtoMapper;

@GetMapping("/{boardId}")
public BoardDetailResponse getBoard(@PathVariable("boardId") long boardId) {
var board = boardReadUseCase.getBoard(boardId);

return boardDtoMapper.toDtoDetail(board);
}

@GetMapping
public Page<BoardReadSummaryModel> getBoardsByStatuses(@RequestParam Set<BoardStatus> statuses, Pageable pageable) {
return boardReadByStatusesUseCase.findByStatuses(statuses, pageable);
}
}
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💊 테스트 편의성을 위해 요청 DTO에도 @Builder 애노테이션을 추가할 수 있습니다.

테스트 시 편리한 객체 생성을 위해 요청 DTO에도 빌더 애노테이션을 추가할 수 있습니다.

💊 (다음 실습) 반복되는 정적 상수 문자열은 도메인 계층의 정책에서 상수 관리로 제공할 수 있습니다.

도메인 및 애플리케이션 계층에서 관리가 선행되어야 하므로 이번 리뷰에서는 간소화하여 작성드리는 것이 좋아 보입니다. 또, 이번 실습 단계보다는 다음 실습(멀티모듈 프로젝트)에서 상세하게 맞춰 보는 것이 더 적절할 수도 있다고 생각합니다.


@Builder
@JsonRootName("board")
public record BoardCommandResponse(
Board board
) {
}
Comment on lines +34 to +39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💊 이 조건에서 @JsonRootName(value = "board") 애노테이션과 최외곽 객체의 명명 중 선택하는 것이 좋아 보입니다.

최외곽으로 반환하는 것이 단일 객체일 때, 테스트 결과 @JsonRootName(...) 애노테이션이 동작하지 않는 것으로 확인했습니다. (이 브랜치의 공동 작업자인 @shin-jingyu 님과 확인하였습니다.)

}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗️ 애플리케이션·도메인 계층의 작업자들과 논의하여 ReadModel 등의 설계가 필요해 보입니다.

BoardSummaryResponse도 필요하다면 생성할 수 있습니다.
하지만 원론적으로 BoardSummaryReadModel의 설계가 애플리케이션 계층에 필요해 보였고, 그것을 초기에 그대로 사용할 수 있어 보입니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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 me.nettee.board.application.model.BoardReadDetailModel;
import me.nettee.board.application.model.BoardReadSummaryModel;

import java.time.Instant;

public final class BoardQueryDto {
private BoardQueryDto() {}

@Builder
public record BoardDetailResponse(
BoardReadDetailModel board
){}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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.application.domain.Board;
import me.nettee.board.application.model.BoardReadDetailModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface BoardDtoMapper {

Board toDomain(BoardCreateCommand command);

Board toDomain(Long id, BoardUpdateCommand command);

BoardDetailResponse toDtoDetail(BoardReadDetailModel board);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package me.nettee.board.application.model;

import java.time.Instant;
import me.nettee.board.application.domain.type.BoardStatus;

public record BoardReadDetailModel(
Long id,
String title,
String content,
BoardStatus status,
Instant createdAt,
Instant updatedAt,
Instant deletedAt
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package me.nettee.board.application.model;

import java.time.Instant;

public record BoardReadSummaryModel(
Long id,
String title,
String content,
Instant createdAt,
Instant updatedAt
){}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package me.nettee.board.application.port;

import java.util.Optional;
import me.nettee.board.application.domain.Board;

public interface BoardCommandPort {

Optional<Board> findById(Long id);

Board create(Board board);

Board update(Board board);

void delete(Long id);

void delete(Board id);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package me.nettee.board.application.port;

import me.nettee.board.application.domain.Board;
import java.util.Optional;
import java.util.Set;
import me.nettee.board.application.domain.type.BoardStatus;
import me.nettee.board.application.model.BoardReadDetailModel;
import me.nettee.board.application.model.BoardReadSummaryModel;
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<BoardReadDetailModel> findById(Long id);

Optional<Board> findById(Long id);
Page<BoardReadSummaryModel> findByStatusesList(Pageable pageable, Set<BoardStatus> statuses);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package me.nettee.board.application.service;

import lombok.RequiredArgsConstructor;
import me.nettee.board.application.domain.Board;
import me.nettee.board.application.model.BoardReadDetailModel;
import me.nettee.board.application.port.BoardCommandPort;
import me.nettee.board.application.port.BoardQueryPort;
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) {
Board board = boardCommandPort.findById(id).orElseThrow(
() -> new IllegalArgumentException("게시글을 찾을 수 없습니다."));

board.softDelete();

boardCommandPort.delete(board);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package me.nettee.board.application.service;

import java.util.Set;
import lombok.RequiredArgsConstructor;
import me.nettee.board.application.domain.type.BoardStatus;
import me.nettee.board.application.model.BoardReadDetailModel;
import me.nettee.board.application.model.BoardReadSummaryModel;
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 BoardReadDetailModel getBoard(Long id) {
return boardQueryPort.findById(id).orElseThrow(
() -> new IllegalArgumentException("게시글을 찾을 수 없습니다."));
}

@Override
public Page<BoardReadSummaryModel> findByStatuses(Set<BoardStatus> statuses, Pageable pageable) {
return boardQueryPort.findByStatusesList(pageable, statuses);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
public interface BoardCreateUseCase {

Board createBoard(Board board);

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
public interface BoardDeleteUseCase {

void deleteBoard(Long id);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package me.nettee.board.application.usecase;

import java.util.Set;
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;

public interface BoardReadByStatusesUseCase {

Page<BoardReadSummaryModel> findByStatuses(Set<BoardStatus> statuses, Pageable pageable);

}

Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package me.nettee.board.application.usecase;

import me.nettee.board.application.domain.Board;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import me.nettee.board.application.model.BoardReadDetailModel;

public interface BoardReadUseCase {

Board getBoard(Long id);
BoardReadDetailModel getBoard(Long id);

Page<Board> findGeneralBy(Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
public interface BoardUpdateUseCase {

Board updateBoard(Board board);

}
Loading