티스토리 뷰
DAO, DTO, Service, Controller의 개념을 잘 학습하지 않은 상태에서 "체스 미션 5단계 : DB 적용"을 구현하다보니 코드가 이상해지고 있다는 느낌을 받았다. 오늘은 이 객체들에 대해 알아보고 현재 내 코드를 어떻게 수정할 수 있을지 고민해보려한다. 이후 어떻게 변경되었는지도 추가하면 좋을지도..?
DAO(Data Access Object)
DB에 접근해 데이터를 조회하거나 조작하는 객체를 말한다.
ellie's code
현재 에러처리에 소홀히 한 부분이 있다. (단순히 제이슨이 강의해주신 부분을 따라함..ㅎ) 이를 좀 더 유의미한 에러처리로 변경해야겠다!! 그리고 현재 PreparedStatement는 try-resources문을 사용해 자원을 회수하고 있지만 Connection에 대한 회수는 이루어지지 않고 있다. Connectoin, PreparedStatement를 학습하고 이에 대한 처리를 추가해줘야겠다!!
public class GameDao {
private final Connection connection;
public GameDao() {
this(MySqlConnector.getConnection());
}
public GameDao(final Connection connection) {
this.connection = connection;
}
public void save(final GameDto game) {
final String sql = "INSERT INTO game (state, turn) values (?, ?)";
try (final PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, game.getState());
statement.setString(2, game.getTurn());
statement.execute();
} catch (final SQLException e) {
e.printStackTrace();
}
}
}
DAO vs. Repository
몇가지 글을 읽어봤지만 DAO와 Repository의 차이는 아직 모르겠다. 예전에 SpringBoot기반의 프로젝트를 개발할 때는 JPA를 사용하면서 자연스럽게(?) Repository를 사용했었는데, 그 당시에도 Repository의 개념을 몰랐다. 그저 "JPA를 이용해 데이터베이스에 접근하고, 데이터를 조작한다."라고 생각했던 것 같다. 흠.. 앞으로 알아가야할게 참 많다.
DTO(Data Transfer Object)
계층간 데이터 교환을 위한 객체를 말한다.
오직 getter/setter 메서드 만을 갖는다. (다른 로직을 갖지 않는다.)
setter를 제거하고 생성자에서 데이터를 받아 필드에 할당해준다면 전달 과정에서 데이터의 변조를 막을 수 있다. (불변객체)
왜 DTO를 쓸까?
DTO는 View, DB의 변화가 비지니스 로직에 영향을 주는 것을 차단한다. View나 DB에 변화가 생기면 DTO의 내부를 바꾸면 되므로 비지니스 로직에 영향을 덜 줄 수 있다.
어디서 DTO를 쓸까?
DAO와 Service, Controller 사이의 데이터 교환을 할 때 사용하는 경우와 Controller와 View 사이의 데이터 교환을 할 때 사용하는 경우로 생각해볼 수 있다.
ellie's code
내가 구현한 GameDto를 보면 createState라는 메서드를 가지고 있다. 여기 뿐만아니라 BoardDto에서도 Map<String, String>을 Map<Position, Piece>로 변환하는 메서드를 가지고 있다. 이것은 getter/setter외의 다른 로직이라고 볼 수 있다. 그래서 이러한 메서드들이 DTO에 있는게 맞는가?라는 고민을 갖게 됐다. 어디로 보내야할지 생각해봐야겠다.
또 하나의 고민은 필드를 DB 데이터 타입에 따라야하는지, Domain의 데이터 타입에 따라야하는지 잘 모르겠다. 현재는 모두 DB의 데이터 타입을 따르도록 구현했다. (View와의 교환을 위한 DTO에서도 View에서 사용하는 데이터 타입으로 지정했다.)
public class GameDto {
private final int id;
private final String state;
private final String turn;
public GameDto(final String state, final String turn) {
this(0, state, turn);
}
public GameDto(final int id, final String state, final String turn) {
this.id = id;
this.state = state;
this.turn = turn;
}
public static GameDto of(final int id, final State state, final Color turn) {
return new GameDto(id, createState(state), turn.getName());
}
public static GameDto of(final ChessGame chessGame) {
return new GameDto(0, createState(chessGame.getState()), chessGame.getTurn().getName());
}
private static String createState(final State state) {
if (state instanceof Ready) {
return "Ready";
}
if (state instanceof Started) {
return "Started";
}
return "Ended";
}
public Integer getId() {
return id;
}
public String getState() {
return state;
}
public String getTurn() {
return turn;
}
}
VO(Value Object)
값 그 자체를 표현하는 객체를 말한다. (DTO와 혼동하지 말자!!)
setter를 가지면 안 되며, 생성자를 통해서만 값을 초기화해야한다. (= 불변객체, read-only)
getter 외의 로직을 가질 수 있다.
속성 값이 같으면 같은 객체로 간주해야하기 때문에 equlas, hashCode 메서드를 모두 오버라이딩 해 줘야 한다.
ellie's code
체스에서는 Piece 클래스가 VO가 되지 않을까 싶다.
public abstract class Piece {
private final Color color;
private final Type type;
protected Piece(final Color color, final Type type) {
this.color = color;
this.type = type;
}
public abstract boolean isRightMovement(final Position from, final Position to, final boolean isEmptyTarget);
public abstract boolean isJumpable();
public boolean isSame(final Type type) {
return this.type == type;
}
public boolean isSame(final Color color) {
return this.color.equals(color);
}
public double getScore() {
return type.getScore();
}
public Color getColor() {
return color;
}
public Type getType() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Piece piece = (Piece) o;
return color == piece.color && type == piece.type;
}
@Override
public int hashCode() {
return Objects.hash(color, type);
}
}
Service
비지니스 로직을 처리하는 객체를 말한다.
DAO 여러개를 묶어서 사용하는 객체를 가리키기도 한다.
주로 DAO를 통해 데이터에 접근하고, DTO로 전달받은 데이터를 처리하는 데 필요한 로직을 구현한다.
Controller가 Request를 받으면 적절한 Service에 전달하고, 전달 받은 Service는 비지니스 로직을 처리한다.
ellie's code
public class ChessService {
private final GameDao gameDao;
private final BoardDao boardDao;
public ChessService() {
this.gameDao = new GameDao();
this.boardDao = new BoardDao();
}
public ChessGame load() {
final GameDto gameDto = gameDao.findByMaxId();
final BoardDto boardDto = boardDao.findByGameId(gameDto.getId());
final Board board = boardDto.toBoard();
final State state = createState(gameDto.getState(), gameDto.getTurn(), board);
return new ChessGame(state, board);
}
}
Controller(Web)
View(Client)에서 보낸 요청 URL에 따라 적절한 응답을 한다.
View의 Response Body에 담긴 데이터(DTO)를 Service에 넘겨 적절하게 처리한다.
Service에서 처리하고 반환된 데이터(DTO)를 Response Body에 담아 View에 전달한다.
ellie's code
현재 View에 원하는 에러메시지를 전달하기 위해 try-catch문을 사용해 DTO에 에러메시지를 담아 보낸한다. 하지만 Spark Java에는 이미 에러를 처리해주는 exception 메서드가 있다고 한다. (후니가 친절하게 알려줌😎) 당시에 Doc를 볼 때 이런게 있구나~ 하고 넘어갔었는데 다시 알아보고 적용해봐야겠다!!
public class ChessWebController {
private static final Gson GSON = new Gson();
final ChessService chessService;
public ChessWebController() {
chessService = new ChessService();
}
public void run() {
get("/", (req, res) -> {
return render(new HashMap<>(), "index.html");
});
get("/load", (req, res) -> {
return GSON.toJson(load());
});
}
private ChessResponseDto load() {
try {
final ChessGame chessGame = chessService.load();
return chessService.createChessResponseDto(chessGame);
} catch (final Exception e) {
return chessService.createErrorChessResponseDto(e.getMessage());
}
}
}
Ref.
https://lazymankook.tistory.com/30
https://www.youtube.com/watch?v=z5fUkck_RZM
https://velog.io/@jyyoun1022/SpringEntityDTODAO-%EA%B0%9C%EB%85%90%EC%A0%95%EB%A6%AC
'Backend > Java' 카테고리의 다른 글
[모락] byte[]를 String으로 변환하기 (2) | 2022.08.26 |
---|---|
Spark Java에서 에러 메시지 전송하기 (0) | 2022.04.07 |
처음 만난 SparkJava (0) | 2022.04.03 |
공유 중인 가변 데이터는 동기화해 사용하라 (0) | 2022.03.30 |
Enum에서 상수를 사용하는 방법 (4) | 2022.03.17 |