
🖥️ 8회차 학습 (25.11.10 ~ 25.11.14) 기록
알고리즘 끝판왕 그리디, 다이젝스트라, DP를 이번 주에 마무리하고 마침내 스프링을 배우기 위한 준비 단계로 들어섰다. JDBC와 MyBatis 개념과 실습 수업을 들었고, 정말 쉽지 않은 한 주였다. 이번 학습도 아래와 같이 정리했다.
Greedy 알고리즘
Dijkstra 알고리즘
Dynamic Programming 알고리즘
JDBC
동적SQL
MyBatis
사실 알다시피 정말 방대한 분량이다. 100% 이해하지 못하고 지나가는 것들이 정말 많지만, 한 번씩 정리하면서 읽는 것도 도움이 되고 나중에 와서 찾아볼 때도 편해서 정리를 해두는 것이다.
이번 회고록도 지난 금요일부터 진행된 미니 프로젝트에 대한 회고로 진행할 것이다.
BeyondSW 21기 두 번째 프로젝트인 콘솔 미니 게임 Konketmon을 만들면서
JDBC, MVC, 계층 분리, 리팩토링, 도메인 설계에 대해 회고해 보겠다.
처음에는 간단한 model-view-controller 구조로 출발했지만,
점점 기능이 늘어나고 책임이 복잡해지면서
service, repository 계층을 추가함으로써 기능적 구조적 분리를 시도했다. 이를 통해 애플리케이션 전체를 리팩토링하게 되었다.
이번 회고록의 목차이다.
- 프로젝트 개요
- 아키텍처 구조
- 포획 로직 및 전투
- 리팩토링 전후
- 배운점
정리
Konketmon은 포켓몬 게임에서 영감을 얻은 콘솔 기반 미니 게임이다. 콘켓몬의 모든 종류를 포획하면 승리하고, 그 전에 HP 체력이 전부 떨어진다면, 처음부터 다시 시작해야 한다.
다음은 Konketmon의 기능이다.
이 프로젝트의 목적은 다음 세 가지이다.
최종 구조는 다음과 같은 폴더 단위로 정리되었다.
/run
/controller
/view
/model
/service
/repository
/dbconnection
프로그램 시작점(main). run 파일에서 controller 메인 메소드 호출.
모든 콘솔 입출력.
Scanner로 입력 받고 Controller에 전달.
View에서 요청을 받아 Service 호출.
도메인 로직 없이 흐름만 제어.
비즈니스 로직 담당 계층.
전투, 포획 확률 계산, 도감 규칙, 회원가입 규칙 등. controller 계층에서 DB 처리 요청을 받아 Repository 계층에서 쿼리문을 받아와 요청 받은 것을 제공해준다.
SQL/JDBC 접근 담당.
PreparedStatement, ResultSet 처리 등 DB 입출력.
JDBC Connection 관리.
싱글턴 기반으로 하나의 Connection을 재사용하도록 한다.
catchRate = BASECATCHRATE + (1 - hpRatio) * 0.7;


예시 코드
로그인 기능을 예로 들어보자
변경 전 - Controller
public boolean loginUser(String username, String password) throws SQLException {
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM user WHERE id=? AND pw=?");
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
this.user = new User(rs.getString(1),rs.getInt(3),rs.getBoolean(4));
}
return this.user != null;
}
↓ ↓ ↓ 변경 후 - Controller
public boolean loginUser(String username, String password) throws SQLException {
return userService.login(username, password);
}
변경 후 - Service
public boolean login(String username, String password) {
this.user = userRepository.login(this.con, username, password);
return user != null;
}
변경 후 - Repository
public User login(Connection con, String username, String password) {
User user = null;
try (PreparedStatement pstmt = con.prepareStatement("SELECT * FROM user WHERE id=? AND pw=?")) {
pstmt.setString(1, username);
pstmt.setString(2, password);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
user = new User(rs.getString(1), rs.getInt(3), rs.getBoolean(4));
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return user;
}
위와 같이 Controller 내에 존재하는 모든 메소드를 Controller - Service - Repository 계층으로 나누어서 리팩토링하였다.
개선점은 아래와 같다.
초기 프로젝트 구조는 단순히 model, view, controller만 존재했다.
그러나 기능이 조금만 늘어나자 코드의 결합도가 증가했다.
→ Service 와 Repository를 추가함으로써 역할 분배, 결합도를 낮추도록 설계.
이 구조로 재정비하여 각 레이어의 단순화 하였다. 이렇게 하면 추후 제 3자가 비교적 더 쉽게 코드를 이해하는 것과 유지보수 측면에서 좋다.
특히 Service 레이어가 생기면서, 게임 규칙(포획률, 전투 규칙, 승리 조건)을
Controller나 View에 섞지 않고 독립적으로 구현할 수 있었다.
리팩토링은 프로젝트를 이끈 팀원의 아이디어로 시작됐다. 작게는 메소드 명과 변수명 부터 시작하여 구조 재정비 및 계층 분리를 했다.
Controller하나와 Repository 하나로 User, Konketmon, Konketdex 도메인의 Service와 DAO 계층을 다루었던 것을 나누자고 한 것이다.
가장 헷갈렸던 파트는 컨트롤러에 있는 User, Konketmon, Konketdex 의 Service, DAO 를 다루는 메소드들을 쪼개고 나누어서 각각 userService, konketmonService, konketdexService 로 보낸 후, Service에서 새로 생성된 메소드들을 통해 요청되는 Data access를 userRepo, konketRepo, konketdexRepo 측에서 접근할 수 있도록 메소드들을 생성하는 작업이다.
리팩토링을 진행하던, 계층 분리를 하던, MVC2 구조를 채택하던, 프로젝트의 전체 구조에 대한 이해가 있어야 한다고 느껴졌다. 프로젝트 전체 구조에 대한 이해를 하려면, 각 기능의 클래스와 생성자, 그리고 사용하는 자료구조와 어떤 곳으로 어떤 객체가 넘어가는지 흐름을 알고 있어야 한다고 느꼈다.
만약 이 구조 리팩터링을 나보고 혼자 하라고 했다면 솔직하게 일주일도 넘게 걸렸을 것이다. 하지만 코드의 구조를 짜고 직접 메소드 하나하나 생성했던 리더 팀원은 빠르게 리팩터링을 마쳤다. 이 시간을 통해 배우기도 많이 배웠지만 내 부족함을 여실히 느꼈고, 프로젝트 경험이 얼마나 개발에 영향을 미치는지 느끼게 되었다.
개발 및 프로젝트 경험이 많이 나는 팀원들과의 협업이었던 만큼, 내가 얼마나 부족한지 느낄 수 있었다. 프로젝트 자체는 스케일이 크지 않았고, 그렇기에 분리된 패키지에서 개별화된 개발을 통한 협업을 하기에는 한계가 있었다. 그런 환경에서 개발을 하다보니 무쌍을 찍는 팀원의 개발을 지켜보며 이해하고 따라가는데 급급했던 모습이 생각난다. 프로젝트 아이디어부터 도메인, 클래스, 각 클래스의 필드 등 사실상 프로젝트의 청사진을 모두 다 그려진 상태에서, 나는 그 것을 이해하면 되는 부분이었지만, 그 것을 이해하는 데에도 부족함이 느껴져서 더 노력하고 프로젝트 경험을 늘려야겠다는 경각심이 생겼다.