2024. 9. 26. 17:09ㆍ개발자 과정/Kotlin
class RpsService {
private var userScore: Int = 0
private var comScore: Int = 0
현재 우리의 로직에서 스코어를 저장하는 방식은 멤버변수를 통하고 있다.
그렇다면 프로그램을 종료해도 스코어를 유지하거나, 여러사람 각각의 스코어를 저장하는 기능을
멤버변수로 구현하려면 어떻게 해야할까? 생각만 해도 골치가 아프다.
그래서 DB를 써야한다. 이번 강의는 DB를 쓰기위한 초석을 다질 것이다.
의존성 추가
kotlin("plugin.jpa") version "1.9.24"
//h2
implementation("com.h2database:h2")
//jpa
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
우리는 h2db를 사용할 것이다. 따로 네트워크 연결이 필요없는 로컬 데이터 베이스라 개발환경에서 프로토 타입으로 쓰기 용이하다. Jpa는 프로그램과 DB간의 소통창구역할을 할 것이다.
resources 설정
resources를 열어보면 application.properties 라는 파일이 있을 것이다.
이것의 확장자를 .yml로 수정해주자.
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:./data/my_data;MODE=MySQL
username: sa
password:
h2:
console:
enabled: true # H2 콘솔을 활성화 (필요 시)
jpa:
hibernate:
ddl-auto: update # 데이터베이스 스키마 자동 업데이트
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect
그 후 위의 텍스트를 복사&붙여넣기를 하면된다.
구현
entity
controller, service와 같은 위치에 이미지와 같이 패키지와 객체를 생성한다.
이것을 entity 즉, 데이터를 객체화 하여 우리가 핸들링을 할 수 있게 하는 것이다.
@Entity
@Table(name="rpsdata")
class RpsData(
@Column(name = "result")
val result: String,
@Column(name = "user_select")
val userSelect:String,
@Column(name = "com_select")
val comSelect:String,
@Column(name="user_score")
val userScore:Int,
@Column(name="com_score")
val comScore:Int,
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id:Long?=null
)
그리고 위의 코드를 작성한다. @를 어노테이션(annotation)이라 부르는데, 여기서 설명하면 길어지고,
어노테이션에 어떤 이름이 명시되는가만 보아도 해당 객체의 역할과 의도를 알 수 있다.
지금 코드의 어노테이션을 보면 알겠지만, 우리가 만들 DB의 구조를 매핑한다는 것을 알 수 있다.
repository
그 후 repository 패키지와 interface객체를 생성한다. 어디서 본거 같은 이름이면 잘 따라온것이다.
3-Layer구조의 남은 한가지 였던 repository layer 가 생성된 것이다!
interface RpsDataRepository : JpaRepository<RpsData,Long>
interface의 내용은 위와 같이 작성한다.
service
이제 다시 service를 수정할 차례이다. layer구조를 보면 알겠지만, repositoy를 사용하는건 service 계층이다.
controller에 다이렉트로 넣지 않는 이유는 한 번 생각해 보면 좋다.
class RpsService(
private val rpsDataRepository: RpsDataRepository
) {
controller에 service를 di한 것 처럼 service에 repository를 di하자.
@Service
class RpsService(
private val rpsDataRepository: RpsDataRepository
) {
fun play(userRps: RpsSelect): RpsResultResponse {
// 기존 게임 기록을 불러와 점수를 초기화
var userScore:Int=0
var comScore:Int=0
rpsDataRepository.findAll()
.let {
if(it.isNotEmpty()){
userScore=it.last().userScore
comScore=it.last().comScore
}
}
//게임 로직
val rpsBord = arrayOf(
arrayOf("무승부", "패배", "승리"),
arrayOf("승리", "무승부", "패배"),
arrayOf("패배", "승리", "무승부")
)
return try {
val comRps = RpsSelect.getRandom()
RpsResultResponse.from(
rpsDataRepository.save(
RpsData(
rpsBord[userRps.ordinal][comRps.ordinal]
.also {
if(it=="승리")++userScore
else if(it=="패배")++comScore
},
userRps.getMessage(),
comRps.getMessage(),
userScore, comScore
)
)
)
} catch (e: Exception) {
throw IllegalArgumentException("에러!")
}
}
fun history():List<RpsResultResponse>{
return rpsDataRepository.findAll()
.map { RpsResultResponse.from(it) }
}
}
전체 코드다. 달라진 점은 크게 가위바위보 로직에 들어가기전, 스코어가 있는지 확인한 후 없으면 0점부터 시작, 있으면 해당 스코어 부터 시작하는 것이고, DTO를 보내는 시점에 우선 db에 저장 한 후, 저장된 엔티티를 DTO에 전달한다는 차이점이 있으며, 스코어 기록과 히스토리를 보기 위한 함수가 추가된 것이다.
따라서,
DTO
data class RpsResultResponse(
val result: String,
val userSelect: String,
val comSelect: String,
val userScore: String,
val comScore: String,
) {
companion object {
fun from(
rpsData: RpsData
): RpsResultResponse {
return RpsResultResponse(
result = rpsData.result,
userSelect = "유저 선택: ${rpsData.userSelect}",
comSelect = "콤퓨타 선택: ${rpsData.comSelect}",
userScore = "유저 스코어: ${rpsData.userScore}",
comScore = "콤퓨타 스코어: ${rpsData.comScore}"
)
}
}
}
DTO의 함수인 from의 매개변수도 바뀌고
controller
@RequestMapping("/rps")
@RestController
class RpsController(
private val rpsService: RpsService
) {
@PostMapping("/play")
fun play(@RequestParam userRps: RpsSelect)
: ResponseEntity<RpsResultResponse> {
return ResponseEntity.status(HttpStatus.CREATED).body(rpsService.play(userRps))
}
@GetMapping("/history")
fun history():ResponseEntity<List<RpsResultResponse>>{
return ResponseEntity.ok().body(rpsService.history())
}
}
controller도 히스토리를 받아오기 위한 기능 추가와, 로직이 변화 함에 따라 어노테이션을 새로 설정하면 된다.
swagger

여기까지 왔으면 스웨거에서 게임을 플레이하고 히스토리를 불러올때 잘 불러와진다는 것을 알 수 있다.
이제 프로그램을 끈 후 바로 히스토리를 불러와 보자.
프로그램을 종료 후 재실행 했음에도 스코어가 휘발되지 않고 저장되어 있는 것을 확인 할 수 있다.
아까 h2는 로컬환경 db라 했는데, ./data/my_data.mv 라는 경로에 저장되고 있는 것이다.
지금까지의 흐름을 보면
1. 가위바위보를 입력하면 그에 따른 결과 보내기
2. 가위바위보 로직을 고도화 하고 DTO를 통해 세부적인 데이터 보내기(스코어, 컴퓨터가 낸거 등등..)
3. 스코어와 히스토리를 휘발되지 않도록 DB를 도입, 3-layer를 완성하여 data가 휘발되지 않는 영속성을 띄게 하고, 핸들링이 가능하도록 개발
이라는 흐름을 따라왔다.
이 과정에서도 대충 넘어간 이론등이 있었지만, 나처럼 일단 해봐야 이해가 되는 사람이 있다면, 도움이 되었으면 한다.
'개발자 과정 > Kotlin' 카테고리의 다른 글
springboot기초(외전)-추상화는 곧 가독성이다. (0) | 2025.01.08 |
---|---|
springboot기초(4)-멤버를 추가해보자 (3) | 2024.10.03 |
springboot기초(2)-가위바위보를 고도화 해보자 (1) | 2024.09.24 |
springboot기초(1)-백엔드로 가위바위보를 만들어보자 (3) | 2024.09.23 |
깃허브 액숀을 통해 CI/CD를 구성해보자! (0) | 2024.08.12 |