Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
680850a
feat: docker file 추가
eedys1234 Jan 9, 2025
d1f2687
feat: common, movies, images 모듈 추가
eedys1234 Jan 9, 2025
0ffd623
feat: movie module 개발 및 root gradlew 수정
eedys1234 Jan 9, 2025
bc675ba
feat: cinema module 개발
eedys1234 Jan 10, 2025
178bac3
feat: BaseEntity 상속받도록 수정
eedys1234 Jan 10, 2025
f4af2c0
feat: ddl문 추가
eedys1234 Jan 10, 2025
4c677fc
feat: gradle setting
eedys1234 Jan 10, 2025
bc08f16
feat: 버그 수정
eedys1234 Jan 10, 2025
f2e1c65
fix: 다른 모듈 객체 참조 안되던 버그 수정
eedys1234 Jan 10, 2025
54dd50e
feat: 호출 경로 수정
eedys1234 Jan 10, 2025
d34190f
feat: sql파일명 변경
eedys1234 Jan 10, 2025
5c9aff3
feat: 불필요한 파일 삭제
eedys1234 Jan 10, 2025
6c71377
feat: batch size 적용, json 직렬화되도록 private 제거
eedys1234 Jan 10, 2025
6c3daa3
feat: .gitigore 수정
eedys1234 Jan 10, 2025
3eab177
feat: 불필요한 파일 제거 및 .gitignore 수정
eedys1234 Jan 10, 2025
783de19
refactor: docker-compose mysql charset, collation 설정
eedys1234 Jan 14, 2025
482f178
refactor: ddl, dml 문 수정
eedys1234 Jan 14, 2025
d0e5aee
fix: SQL 문법 수정
eedys1234 Jan 14, 2025
d2a5c2e
refactor: module 구조 변경
eedys1234 Jan 14, 2025
d9fbac7
Update README.md
eedys1234 Jan 14, 2025
3c44733
feat: http 파일 추가
eedys1234 Jan 14, 2025
21253b3
Merge branch 'main' of https://github.com/eedys1234/redis_1st into week1
eedys1234 Jan 14, 2025
e19feb9
feat: insert sql 문 분리
eedys1234 Jan 18, 2025
8cf16c9
feat: api title & genre 검색 기능 추가
eedys1234 Jan 18, 2025
dffdc05
feat: validation add
eedys1234 Jan 18, 2025
fb3a550
fix: validation check 버그 수정 & genre 동적쿼리 추가
eedys1234 Jan 18, 2025
9e0f0f2
feat: fulltext index query 적용
eedys1234 Jan 19, 2025
84330e7
feat: caffeine 라이브러리 적용하여 로컬 캐싱 기능 적요
eedys1234 Jan 19, 2025
137b2de
README.md(캐싱 데이터,성능 테스트 보고서 추가)
eedys1234 Jan 19, 2025
6f388a5
feat: 불필요한 파일 삭제
eedys1234 Jan 19, 2025
c24f07a
Merge branch 'week2' of https://github.com/eedys1234/redis_1st into w…
eedys1234 Jan 19, 2025
059e50c
feat: screening entity 추가
eedys1234 Jan 25, 2025
d7efa44
feat: 피드백 적용
eedys1234 Jan 25, 2025
fdb980b
feat: redis 조회 기능 추가
eedys1234 Jan 25, 2025
5dfd80f
feat: 예약 API 개발
eedys1234 Jan 25, 2025
2df1b82
feat: 불필요한 파일 삭제
eedys1234 Jan 25, 2025
be9dd07
feat: validation 유효성 검증 안되는 버그 수정
eedys1234 Jan 26, 2025
78cf9d8
feat: insert 방식에서 update 방식으로 변경
eedys1234 Jan 26, 2025
fd2a128
fix: async 수행안되는 버그 수정
eedys1234 Jan 26, 2025
da8d42b
feat: 불필요한 파일 삭제
eedys1234 Jan 26, 2025
28e60c6
feat: reserve 패키지 분리
eedys1234 Jan 26, 2025
102f304
feat: 동시성 테스트코드 작성
eedys1234 Jan 26, 2025
36bb6da
feat: 비관적 락 적용
eedys1234 Jan 26, 2025
4ae4c37
feat: 비관적 락 테스트 코드 수정
eedys1234 Jan 26, 2025
7ef2471
feat: 낙관적 락 적용
eedys1234 Jan 26, 2025
b95fac0
feat: 분산락 구현(AOP 방식)
eedys1234 Jan 26, 2025
1d941e1
Update README.md
eedys1234 Jan 26, 2025
a5e0ed7
feat: timeunit 도 유연하게 매개변수로 받을 수 있도록 수정
eedys1234 Jan 27, 2025
267bfca
feat: 예매 관련 htp 파일 추가
eedys1234 Jan 27, 2025
077ffc7
Merge branch 'week3' of https://github.com/eedys1234/redis_1st into w…
eedys1234 Jan 27, 2025
41445ce
feat: aop annotation 포인트 컷 변경
eedys1234 Jan 27, 2025
52d47b0
feat: AOP 형식의 분산락 구현을 코틀린의 트레일링 람다로 구현
eedys1234 Jan 27, 2025
fa95c75
feat: google guava library 활용하여 rate limit 적용
eedys1234 Feb 1, 2025
62687fc
feat: rate limit 테스트 코드 작성
eedys1234 Feb 1, 2025
1803522
feat: redis를 이용하여 rate limit 적요
eedys1234 Feb 2, 2025
e80e972
feat: 테스트 코드 수정
eedys1234 Feb 2, 2025
a85e68e
feat: redisson을 이용하여 rate limiter 구현
eedys1234 Feb 2, 2025
64108b5
feat: jacoco plugin 적용
eedys1234 Feb 2, 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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.gradle
.idea
*/**/out/
*/**/.gradle
*/**/build
87 changes: 86 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,88 @@
## [본 과정] 이커머스 핵심 프로세스 구현
[단기 스킬업 Redis 교육 과정](https://hh-skillup.oopy.io/) 을 통해 상품 조회 및 주문 과정을 구현하며 현업에서 발생하는 문제를 Redis의 핵심 기술을 통해 해결합니다.

## 프로젝트 소개
- [단기 스킬업 Redis 교육 과정](https://hh-skillup.oopy.io/) 을 통해 상품 조회 및 주문 과정을 구현하며 현업에서 발생하는 문제를 Redis의 핵심 기술을 통해 해결합니다.
> Indexing, Caching을 통한 성능 개선 / 단계별 락 구현을 통한 동시성 이슈 해결 (낙관적/비관적 락, 분산락 등)

### API
- GET /api/v1/movies (영화목록 조회)

***

### Architecture
모듈 구성은 다음과 같다.
- module-infrastructure
- module-application
- module-domain
Copy link

Choose a reason for hiding this comment

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

저는 application -> domain -> infrastructure 가 일반적인 흐름이라고 생각됩니다.
(application 이 진입점이라고 생각이 되어서요)


#### module-infrastructure
- in, out port로 구성되어 있으며, in은 api 를 호출하는 경우이며, out은 persistence 로 데이터를 가져오는 port로 구성

#### module-application
- 도메인의 흐름을 정의하는 계층

#### module-domain
- 순수 도메인이 응집해있는 도메인 모듈로 movie, theater 등 도메인 존재

![image](https://github.com/user-attachments/assets/18153de2-c011-4613-a1c1-5ba82ea796a9)

***

### ERD
- movie, movie_genre, movie_theater, theater, seat, screening_schedule 테이블로 구성되어 있으며 각 테이블 정의는 다음과 같다.

#### movie
- 영화 정보가 정의되어있는 테이블

#### movie_genre
- 영화 장르 정보가 정의되어있는 테이블

#### movie_theater
- 영화와 상영관간의 관계 정보가 정의되어있는 테이블

#### theater
- 상영관 정보가 정의되어있는 테이블

#### seat
- 상영관의 좌석 정보가 정의되어있는 테이블

#### screning_schedule
- 영화 상영표가 정의되어있는 테이블

![image](https://github.com/user-attachments/assets/6d40c181-db9d-4af8-bc10-aa21cb25de6a)

***
캐싱 데이터
```json
{
"id": "{movie_id}",
"title": "{movie_title}",
"film_ratings": "{film_ratings}",
"thumbnail_image_path": "{thumbnail_image_path}",
"running_time": "{running_time}",
"release_date": "{release_date}",
"movie_genre": [
"{movie_genre}:{0}"
],
"theaters": [
{
"theater_name": "{theater_name}",
"screening_schedules": []
}
]
}
```

성능 테스트 보고서
- https://www.notion.so/1807e833dea78069aefcf9e532d2dc6d?showMoveTo=true&saveParent=true

***

```
waitTime: 1초, releaseTime: 10초
```
**waitTime**
Lock 설정 시 대기 시간은 예매가 진행 중임을 의미하므로, 대기 시간을 길게 가져갈 필요가 없다고 판단하여 1초로 설정했습니다.

**releaseTime**
Lock의 timeout 시간을 10초로 설정했습니다. 이는 로직이 10초 이내에 수행된다고 판단했으며, 너무 짧은 시간은 처리 중 lock이 풀리는 문제를, 너무 긴 시간은 lock이 해제되지 않아 점유 상태가 지속되는 문제를 방지하기 위함입니다.
90 changes: 90 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
plugins {
kotlin("jvm") version "1.9.25"
kotlin("plugin.spring") version "1.9.25" apply false
kotlin("plugin.jpa") version "1.9.10"
id("org.springframework.boot") version "3.4.1" apply false
id("io.spring.dependency-management") version "1.1.7" apply false
kotlin("kapt") version "1.3.61" apply false // annotation processing을 위한 kapt
`java-test-fixtures`
idea
jacoco
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

allprojects {
group = "io.github.eedys1234"
version = "0.1.0-SNAPSHOT"
apply(plugin = "org.jetbrains.kotlin.plugin.spring")

repositories {
mavenCentral()
}
}

subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
apply(plugin = "org.springframework.boot")
apply(plugin = "io.spring.dependency-management")
apply(plugin = "kotlin-jpa")
apply(plugin = "kotlin-kapt")
apply(plugin = "idea")
apply(plugin = "java-test-fixtures")
Copy link

Choose a reason for hiding this comment

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

꼼꼼하게 챙겨주셔서 구현해주셨군요 👍

apply(plugin = "jacoco")

kotlin {
jvmToolchain(21)
}

dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict")
}
}

tasks.withType<Test> {
useJUnitPlatform()
javaLauncher.set(
javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(21)) // 원하는 Java 버전 설정
}
)
finalizedBy(tasks.jacocoTestReport)
}

tasks.jacocoTestReport {
dependsOn(tasks.test)
reports {
html.required.set(true)
xml.required.set(true)
csv.required.set(false)
}
}

tasks.jacocoTestCoverageVerification {
dependsOn(tasks.jacocoTestReport)
violationRules {
rule {
limit {
minimum = "0.60".toBigDecimal()
Copy link

Choose a reason for hiding this comment

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

목표 커버리지는 30% 였는데 60% 까지 설정해주셨군요 👍

}
}
}
}
}

jacoco {
toolVersion = "0.8.5"
}
12 changes: 12 additions & 0 deletions cinema.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="cinema" external.linked.project.path="$MODULE_DIR$/cinema" external.root.project.path="$MODULE_DIR$/cinema" external.system.id="GRADLE" external.system.module.group="org.example" external.system.module.version="1.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/cinema">
<excludeFolder url="file://$MODULE_DIR$/cinema/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/cinema/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
12 changes: 12 additions & 0 deletions common.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="common" external.linked.project.path="$MODULE_DIR$/common" external.root.project.path="$MODULE_DIR$/common" external.system.id="GRADLE" external.system.module.group="org.example" external.system.module.version="1.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/common">
<excludeFolder url="file://$MODULE_DIR$/common/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/common/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
21 changes: 21 additions & 0 deletions coverage-error.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[2025.01.10 19:30:21] (Coverage ERROR): Failed to parse agent arguments
java.lang.IllegalArgumentException: At least 5 arguments expected but 1 found.
'C:\Users\ÀÌdȯ\AppData\Local\Temp\coverageargs'
Expected arguments are:
0) data file to save coverage result
1) a flag to enable tracking per test coverage
2) a flag to calculate coverage for unloaded classes
3) a flag to use data file as initial coverage, also use it if several parallel processes are to write into one file
4) a flag to run line coverage or branch coverage otherwise

at com.intellij.rt.coverage.instrumentation.CoverageArgs.fromString(CoverageArgs.java:60)
at com.intellij.rt.coverage.instrumentation.Instrumentator.performPremain(Instrumentator.java:58)
at com.intellij.rt.coverage.instrumentation.Instrumentator.premain(Instrumentator.java:44)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at com.intellij.rt.coverage.main.CoveragePremain.premain(CoveragePremain.java:35)
at com.intellij.rt.coverage.main.CoveragePremain.premain(CoveragePremain.java:28)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:560)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:572)
26 changes: 26 additions & 0 deletions docker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: '3.8' #Docker Compose 버전

services:
redis:
image: redis:latest
container_name: redis-container
ports:
- "6379:6379"
mysql:
image: mysql:8.1 # 사용할 MySQL 이미지 버전
container_name: mysql-container # 컨테이너 이름
restart: always
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: movies
MYSQL_USER: admin
MYSQL_PASSWORD: gkdgo
MYSQL_CHARSET: utf8mb4
MYSQL_COLLATION: utf8mb4_general_ci
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
ports:
- "3306:3306"
volumes:
- ./initdb:/docker-entrypoint-initdb.d


70 changes: 70 additions & 0 deletions docker/initdb/1-init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
CREATE DATABASE IF NOT EXISTS movies;

USE movies;

CREATE TABLE IF NOT EXISTS movie (
movie_id bigint unsigned AUTO_INCREMENT PRIMARY KEY COMMENT 'movie ID',
title varchar(197) NOT NULL COMMENT '영화 제목',
film_ratings varchar(197) NOT NULL COMMENT '영화 등급',
release_date datetime NOT NULL COMMENT '개봉일',
thumbnail_image_path varchar(255) NOT NULL COMMENT '썸네일 이미지 경로',
running_time bigint NOT NULL COMMENT '상영시간(분단위)',
create_at datetime NOT NULL COMMENT '생성일',
create_by varchar(197) NULL COMMENT '생성자',
update_at datetime NULL COMMENT '수정일',
update_by varchar(197) NULL COMMENT '수정자'
);

CREATE TABLE IF NOT EXISTS movie_genre (
movie_genre_id bigint unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '영화 장르 ID',
movie_id bigint unsigned NOT NULL COMMENT '영화 ID',
name varchar(197) NOT NULL COMMENT '장르명',
create_at datetime NOT NULL COMMENT '생성일',
create_by varchar(197) NULL COMMENT '생성자',
update_at datetime NULL COMMENT '수정일',
update_by varchar(197) NULL COMMENT '수정자'
);

CREATE TABLE IF NOT EXISTS theater (
theater_id bigint unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '상영관 ID',
name varchar(197) NOT NULL COMMENT '상영관 이름',
create_at datetime NOT NULL COMMENT '생성일',
create_by varchar(197) NULL COMMENT '생성자',
update_at datetime NULL COMMENT '수정일',
update_by varchar(197) NULL COMMENT '수정자'
);

CREATE TABLE IF NOT EXISTS screening(
screening_id bigint unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '상영시간 ID',
theater_id bigint unsigned NOT NULL COMMENT '상영관 ID',
movie_id bigint unsigned NOT NULL COMMENT '영화 ID',
start_time datetime NOT NULL COMMENT '시작 시간',
end_time datetime NOT NULL COMMENT '종료 시간',
create_at datetime NOT NULL COMMENT '생성일',
create_by varchar(197) NULL COMMENT '생성자',
update_at datetime NULL COMMENT '수정일',
update_by varchar(197) NULL COMMENT '수정자'
);

CREATE TABLE IF NOT EXISTS seat (
seat_id bigint unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '좌석 ID',
theater_id bigint unsigned NOT NULL COMMENT '상영관 ID',
seat_row varchar(197) NOT NULL COMMENT '좌석(행)',
seat_col varchar(197) NOT NULL COMMENT '좌석(열)',
create_at datetime NOT NULL COMMENT '생성일',
create_by varchar(197) NULL COMMENT '생성자',
update_at datetime NULL COMMENT '수정일',
update_by varchar(197) NULL COMMENT '수정자'
);

CREATE TABLE IF NOT EXISTS reserve (
reserve_id bigint unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '예약 ID',
reserve_receipt_id varchar(197) NULL COMMENT '예약 영수증 ID',
screening_id bigint unsigned NOT NULL COMMENT '상영시간 ID',
seat_id bigint unsigned NOT NULL COMMENT '좌석 ID',
user_id bigint unsigned NULL COMMENT '사용자 ID',
create_at datetime NULL COMMENT '생성일',
create_by varchar(197) NULL COMMENT '생성자',
update_at datetime NULL COMMENT '수정일',
update_by varchar(197) NULL COMMENT '수정자'
);
30 changes: 30 additions & 0 deletions docker/initdb/2-theater.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
INSERT INTO theater (`name`, `create_at`, `create_by`)
WITH RECURSIVE seq AS
(
SELECT 0 AS num
UNION ALL
SELECT num + 1 as num
from seq
where num < 19
)
SELECT
CONCAT('Theater_', CHAR(65 + seq.num)) as `name`,
NOW() as `create_at`,
'admin' as `create_by`
FROM seq;

INSERT INTO seat(`theater_id`, `seat_row`, `seat_col`, `create_at`, `create_by`)
SELECT
t.theater_id,
r.seat_row,
c.seat_col,
NOW() as `create_at`,
'admin' as `create_by`
FROM
theater t,
(SELECT CHAR(65 + n) as seat_row FROM (
SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
) rr) r,
(SELECT n as seat_col FROM (
SELECT 1 AS n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5
) cc) c;
Loading