Skip to content
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

[CT-4] 차트게임 기능 domain 레이어 작성 (+ktlint 설정 변경) #13

Merged
merged 38 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ffc27e3
[CT-1] 프로젝트 생성 및 gitignore 적용
nosorae May 8, 2024
5e89d3f
[CT-1] 프로젝트 생성 후 compileSdk와 라이브러리 버전 호환 에러 해소
nosorae May 8, 2024
cf8960a
[CT-1] domain 모듈 추가
nosorae May 8, 2024
d15d947
[CT-1] data 모듈 추가
nosorae May 8, 2024
7c5f0a7
[CT-1] presentation 모듈 추가
nosorae May 8, 2024
8a05cab
[CT-1] 모듈간 의존성 설정
nosorae May 8, 2024
c08d130
[CT-1-2] ktlint 설정 및 코드 스타일 적용 시작
nosorae May 9, 2024
0c09aa0
[CT-1-2] PR시 실행할 Github Action YML파일 추가
nosorae May 9, 2024
600376a
[CT-1-2] hilt 의존성 추가 및 초기 앱모듈 세팅
nosorae May 9, 2024
8a3177e
[CT-1-2] buildSrc 추가 및 적용, 불필요 의존성 제거
nosorae May 9, 2024
d923eed
[CT-1-2] ktlintFormat
nosorae May 9, 2024
6b386cd
[CT-1-2] JDK 버전 호환 문제로 인한 yml 파일 수정 (action/job name 이름 수정)
nosorae May 9, 2024
cfd9d50
[CT-1-2] 배포자 지정
nosorae May 9, 2024
3cc6ad4
[CT-1-2] 배포자 수정, 순서 수정
nosorae May 9, 2024
e7eb99a
[CT-1-2] 일단 Action 제거, 추후 테스트 완료하고 다시 추가 예정
nosorae May 9, 2024
00ccbe5
[CT-4] Entity 추가
nosorae May 11, 2024
6006196
[CT-4] 커스텀 예외 추가
nosorae May 11, 2024
12a3a5d
[CT-4] domain 레이어에 kotlinx-coroutines 의존성 추가
nosorae May 11, 2024
c127680
[CT-4] 트레이드 게임에 필요한 Repository 추상 메소드 정의
nosorae May 11, 2024
bf8e0e2
[CT-4] 게임데이터 조회, 매매하기, 다음 턴 불러오기, 차트 변경 UseCase 추가
nosorae May 11, 2024
0b1c8d1
[CT-4] 코드 스타일 변경, 유저 수익 필드명 변경
nosorae May 11, 2024
ff0dc95
Merge branch 'develop' into feature/ct-4-chart-game-domain
nosorae May 11, 2024
fed07fa
[CT-4] 주석 수정
nosorae May 11, 2024
9b0aa5d
[CT-4] Trade 엔티티 리팩토링 - 주생성자 프로퍼티로 값을 이끌어낼 수 있는 프로퍼티는 중괄호 안의 프로퍼티로 이동
nosorae May 11, 2024
54347d8
[CT-4] 게임 종료된 경우 로직 추가, 엔티티에 지연초기화하는 부분(copy후에도 지연초기화된 인스턴스 그대로라서) 제거
nosorae May 12, 2024
8c6379c
[CT-4] 코드 스타일 변경
nosorae May 12, 2024
e7a866d
[CT-4] 리뷰반영: Chart 엔티티 id를 Long으로 변경
nosorae May 13, 2024
bce58fa
[CT-4] Tick의 TickUnit 프로퍼티를 Chart로 이동
nosorae May 13, 2024
a7abdeb
[CT-4] 리뷰반영: Chart와 ChartGame 의 id에 디폴트 값 추가 (로컬 DB에서 생성하는 값)
nosorae May 13, 2024
50cd2db
[CT-4] 리뷰반영: Trade 리스트 사이즈는 동적으로 변할 수 있는 경우 있기 때문에 사용자 지정 getter로 변경
nosorae May 13, 2024
fe7bb3a
[CT-4] 리뷰반영: enum class 네이밍 컨벤션 수정
nosorae May 13, 2024
e146331
[CT-4] 리뷰반영: CharGame을 copy 하는 함수명 수정
nosorae May 13, 2024
0cc1bb5
[CT-4] ktlint_code_style = intellij_idea 실험
nosorae May 13, 2024
b60bd6f
[CT-4] 리뷰반영: ktlint_code_style = android_studio 로 변경 후 코드 스타일 적용
nosorae May 13, 2024
aac4c16
[CT-4] 함수 파라미터 2개 이상이면 multiline으로 설정되게 코드 스타일 적용
nosorae May 13, 2024
6f95433
[CT-4] 리뷰반영: suspend fun 기준에 맞게 수정
nosorae May 15, 2024
da44da5
[CT-4] 누락된 코드스타일 변경 반영
nosorae May 15, 2024
505c02b
[CT-4] 리뷰반영: 커스텀 예외를 값으로 리턴
nosorae May 15, 2024
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
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/Dependency.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ object Dependency {
const val HILT = "com.google.dagger:hilt-android:2.44"
const val HILT_COMPILER = "com.google.dagger:hilt-android-compiler:2.44"
const val JAVAX_INJECT = "javax.inject:javax.inject:1"
const val KOTLINX_COROUTINSE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1"
f-lab-nathan marked this conversation as resolved.
Show resolved Hide resolved

const val ANDROIDX_CORE = "androidx.core:core-ktx:1.9.0" // 1.13.1

Expand Down
1 change: 1 addition & 0 deletions domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ java {

dependencies {
implementation(Dependency.JAVAX_INJECT)
implementation(Dependency.KOTLINX_COROUTINSE)
}
16 changes: 16 additions & 0 deletions domain/src/main/java/com/yessorae/domain/entity/Chart.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.yessorae.domain.entity

import com.yessorae.domain.entity.tick.Tick
import java.time.LocalDateTime

data class Chart(
val id: Int,
nosorae marked this conversation as resolved.
Show resolved Hide resolved
// ex. AAPL
val tickerSymbol: String,
// 차트 시작 날짜
val startDateTime: LocalDateTime,
// 차트 끝 날짜
val endDateTime: LocalDateTime,
// 차트 데이터
val ticks: List<Tick>,
)
92 changes: 92 additions & 0 deletions domain/src/main/java/com/yessorae/domain/entity/ChartGame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.yessorae.domain.entity

import com.yessorae.domain.entity.tick.Tick
import com.yessorae.domain.entity.trade.Trade
import com.yessorae.domain.entity.value.Money

data class ChartGame(
val id: Int,
// 차트 데이터
val chart: Chart,
// 거래 내역
val trades: List<Trade>,
// 현재 턴
val currentTurn: Int,
// 전체 턴
val totalTurn: Int,
// 게임 전 잔고
val startBalance: Money,
// 현재 잔고
val currentBalance: Money,
// 유저의 게임 강제 종료 여부
val isQuit: Boolean,
) {
val totalProfit: Money = currentBalance - startBalance

val rateOfProfit: Double = (totalProfit / startBalance).value * 100

val tradeCount: Int = trades.size
nosorae marked this conversation as resolved.
Show resolved Hide resolved

val totalCommission: Money = Money(trades.sumOf { trade -> trade.commission.value })

val visibleTicks: List<Tick> =
chart.ticks
.sortedBy { it.startTimestamp }
.subList(0, chart.ticks.size - totalTurn + currentTurn - 1)

// 게임 모든 턴을 끝까지 완료한 경우 true
val isGameComplete: Boolean = currentTurn == totalTurn

// 정상종료이든 강제종료이든 종료된 경우 true
val isGameEnd: Boolean = isQuit || isGameComplete

internal fun getNextTurn(): ChartGame {
val nextTurn = currentTurn + 1
return this.copy(
currentTurn = nextTurn,
)
}

internal fun createFromNewChart(newChart: Chart): ChartGame {
nosorae marked this conversation as resolved.
Show resolved Hide resolved
return copy(
chart = newChart,
currentTurn = START_TURN,
trades = emptyList(),
currentBalance = startBalance,
)
}

internal fun createFromNewTrade(newTrade: Trade): ChartGame {
return copy(
trades = trades + newTrade,
currentBalance = currentBalance + newTrade.profit,
)
}

internal fun createFromQuit(): ChartGame {
return copy(
isQuit = true,
)
}

companion object {
private const val START_TURN = 1

fun new(
chart: Chart,
totalTurn: Int,
startBalance: Money,
): ChartGame {
return ChartGame(
id = 0,
nosorae marked this conversation as resolved.
Show resolved Hide resolved
chart = chart,
trades = emptyList(),
currentTurn = START_TURN,
totalTurn = totalTurn,
startBalance = startBalance,
currentBalance = startBalance,
isQuit = false,
)
}
}
}
14 changes: 14 additions & 0 deletions domain/src/main/java/com/yessorae/domain/entity/User.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.yessorae.domain.entity

import com.yessorae.domain.entity.value.Money

data class User(
val balance: Money,
val winCount: Int,
val loseCount: Int,
val averageRateOfProfit: Double,
// TODO::LATER 아래 프로퍼티는 로그인 피쳐할 때 추가
// val isAnonnymous: Boolean,
// val profileImg: String?,
// val nickname: String,
)
29 changes: 29 additions & 0 deletions domain/src/main/java/com/yessorae/domain/entity/tick/Tick.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.yessorae.domain.entity.tick

import java.time.LocalDateTime

data class Tick(
// 시가
val openPrice: Double,
// 최고가
val maxPrice: Double,
// 최저가
val minPrice: Double,
// 종가
val closePrice: Double,
// 거래 횟수
val transactionCount: Int,
// The Unix Msec timestamp for the start of the aggregate window.
val startTimestamp: LocalDateTime,
// 거래량. The trading volume of the symbol in the given time period.
val tradingVolume: Int,
// 거래량가중평균가격.
// Volume Weighted Average Price의 약어로,
// 조회 대상 종목의 거래일에 발생한 전체 거래대금을 전체 거래량으로 나눈 가격을 의미
val volumeWeightedAveragePrice: Double,
// 장외거래(場外去來, Over-the-counter)는
// 주식, 채권, 상품선물, 파생금융상품과 같은 투자자산을 거래소(exchange)를 거치지 않고,
// 양 당사자가 직접 거래하는 것을 의미
val isOverTheCounter: Boolean,
val unit: TickUnit,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.yessorae.domain.entity.tick

enum class TickUnit {
Day,
nosorae marked this conversation as resolved.
Show resolved Hide resolved
Hour,
// TODO::LATER 분 단위 지원
}
52 changes: 52 additions & 0 deletions domain/src/main/java/com/yessorae/domain/entity/trade/Trade.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.yessorae.domain.entity.trade

import com.yessorae.domain.entity.value.Money

data class Trade(
// 차트게임 아이디
val gameId: Long,
// 현재 가지고 있는 가격
val ownedAverageStockPrice: Money,
// 1 주당 가격
val stockPrice: Money,
// 거래량
val count: Int,
// 거래한 턴
val turn: Int,
// 매수/매도
val type: TradeType,
// 수수료율
val commissionRate: Double,
// TODO::LATER 세금
) {
// 총 거래금
val totalTradeMoney: Money = stockPrice * count

// 수수료
val commission: Money = totalTradeMoney * commissionRate

// 실현 손익
val profit: Money = ((stockPrice - ownedAverageStockPrice) * count) - commission

companion object {
internal fun new(
gameId: Long,
ownedAverageStockPrice: Money,
stockPrice: Money,
count: Int,
turn: Int,
type: TradeType,
commissionRate: Double,
): Trade {
return Trade(
gameId = gameId,
ownedAverageStockPrice = ownedAverageStockPrice,
stockPrice = stockPrice,
count = count,
turn = turn,
type = type,
commissionRate = commissionRate,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.yessorae.domain.entity.trade

enum class TradeType {
Buy,
Sell,
}
31 changes: 31 additions & 0 deletions domain/src/main/java/com/yessorae/domain/entity/value/Money.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.yessorae.domain.entity.value

data class Money(
val value: Double,
) {
// TODO::LATER 화폐단위 바꿔주는 함수 추가

operator fun plus(other: Money): Money {
return Money(value + other.value)
}

operator fun times(other: Money): Money {
return Money(value * other.value)
}

operator fun times(count: Int): Money {
return Money(value * count)
}

operator fun times(count: Double): Money {
return Money(value * count)
}

operator fun minus(other: Money): Money {
return Money(value - other.value)
}

operator fun div(other: Money): Money {
return Money(value / other.value)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.yessorae.domain.exception

sealed class ChartGameException(override val message: String = "") : Exception(message) {
f-lab-nathan marked this conversation as resolved.
Show resolved Hide resolved
data class CanNotChangeChartException(
override val message: String,
) : ChartGameException(message = message)

data class CanNotUpdateNextTickException(
override val message: String,
) : ChartGameException(message = message)

data class CanNotChangeTradeException(
override val message: String,
) : ChartGameException(message = message)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.yessorae.domain.repository

import com.yessorae.domain.entity.ChartGame
import kotlinx.coroutines.flow.Flow

interface ChartGameRepository {
f-lab-nathan marked this conversation as resolved.
Show resolved Hide resolved
fun saveChartGame(chartGame: ChartGame): Long

fun fetchChartFlowStream(gameId: Long): Flow<ChartGame>

suspend fun fetchChartGame(gameId: Long): ChartGame

suspend fun updateChartGame(chartGame: ChartGame)

suspend fun deleteChartGame(gameId: Long)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.yessorae.domain.repository

import com.yessorae.domain.entity.Chart

interface ChartRepository {
suspend fun fetchNewChartRandomly(): Chart
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.yessorae.domain.repository

import com.yessorae.domain.entity.trade.Trade

interface TradeRepository {
suspend fun saveTradeHistory(trade: Trade)

suspend fun deleteTradeHistoryAt(gameId: Long)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.yessorae.domain.repository

import com.yessorae.domain.entity.value.Money

interface UserRepository {
suspend fun fetchCommissionRateConfig(): Double

suspend fun fetchTotalTurnConfig(): Int

suspend fun fetchCurrentBalance(): Money
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.yessorae.domain.usecase

import com.yessorae.domain.exception.ChartGameException
import com.yessorae.domain.repository.ChartGameRepository
import com.yessorae.domain.repository.ChartRepository
import javax.inject.Inject

class ChangeChartUseCase
@Inject
constructor(
private val chartRepository: ChartRepository,
private val chartGameRepository: ChartGameRepository,
) {
suspend operator fun invoke(gameId: Long) {
val oldChartGame = chartGameRepository.fetchChartGame(gameId = gameId)

if (oldChartGame.isGameEnd) {
throw ChartGameException.CanNotChangeChartException(
message = "can't change chart because game has been end",
)
}

chartGameRepository.updateChartGame(
chartGame =
oldChartGame.createFromNewChart(
newChart = chartRepository.fetchNewChartRandomly(),
),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.yessorae.domain.usecase

import com.yessorae.domain.repository.ChartGameRepository
import javax.inject.Inject

class QuitChartGameUseCase
@Inject
f-lab-nathan marked this conversation as resolved.
Show resolved Hide resolved
constructor(
private val chartGameRepository: ChartGameRepository,
) {
suspend operator fun invoke(gameId: Long) {
val oldChartGame = chartGameRepository.fetchChartGame(gameId = gameId)
chartGameRepository.updateChartGame(chartGame = oldChartGame.createFromQuit())
}
}
Loading
Loading