Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
763580f
first commit
hongs429 Jan 7, 2025
4514acf
모듈 구성과 의존성 관계 적용
hongs429 Jan 8, 2025
c2b9f74
도메인 정의 && 도메인 레벨의 관계 설정
hongs429 Jan 8, 2025
10fb36b
commitmm
hongs429 Jan 9, 2025
24418d8
commit 내용
hongs429 Jan 11, 2025
713e05b
first commit
hongs429 Jan 9, 2025
c7b8096
first commit
hongs429 Jan 7, 2025
5f0f369
도메인 정의 && 도메인 레벨의 관계 설정
hongs429 Jan 8, 2025
02e3555
commitmm
hongs429 Jan 9, 2025
e40aaf9
commit 내용
hongs429 Jan 11, 2025
146d9ed
git 정상화
hongs429 Jan 12, 2025
f816885
feat(cinema) : create cinema
hongs429 Jan 12, 2025
9551e01
feat(cinema) : create cinema TC
hongs429 Jan 13, 2025
5c7d8b6
fix(build.gradle) : update dependency
hongs429 Jan 13, 2025
2e0f48c
feat(domain) : create domain validation
hongs429 Jan 13, 2025
10b8739
fix(jpa entity) : drop foreign keys
hongs429 Jan 13, 2025
43c5ef2
chore(load-testing): setup k6, InfluxDB, and Grafana with Docker Compose
hongs429 Jan 14, 2025
d2e69b7
refactor(build): 의존관계 수정으로 clean architecture 구조로 변경
hongs429 Jan 15, 2025
0144f98
create(test code): ScreeningControllerTest
hongs429 Jan 15, 2025
db609dd
feat(querydsl): querydsl 추가
hongs429 Jan 18, 2025
8a8f6bb
feat(querydsl && redis && caffeine): querydsl 추가 및 캐시추가
hongs429 Jan 19, 2025
6285262
fix(cacheManager && redis && loading test): cacheManager 이슈 해결 && red…
hongs429 Jan 20, 2025
b3d8a1b
feat(입력 검증 유효성): 클라이언트로 받은 파라미터의 입력 검증 책임을 application의 입력 모델로 변경 && …
hongs429 Jan 25, 2025
4701c83
feat(Reservation): 예약 서비스 만들기 && 도메인 정의
hongs429 Jan 27, 2025
b60bdae
fix(Reservation): 이미 존재하는 예약에 대해서 검증하는 로직 추가
hongs429 Jan 28, 2025
bb0ac70
fix(동시성 - unique constraint): reservationSeat에 screening 추가하여 unique …
hongs429 Feb 2, 2025
465514a
fix(동시성 - redisson): screening-seat 조합으로 락을 걸어 진행
hongs429 Feb 3, 2025
002f15f
fix(lock 해제 시점): try~finally로 락을 획득한 요청이 예약을 실패한 케이스에 대해 한 곳에서 락을 해제하…
hongs429 Feb 3, 2025
03091b1
fix(Test 일관성): 각 테스트의 일관성 유지를 위해 @AfterEach 추가
hongs429 Feb 3, 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
12 changes: 12 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf

# These are Windows script files and should use crlf
*.bat text eol=crlf

# Binary files should be left untouched
*.jar binary

43 changes: 43 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/

docker/db/
docker/influxdb/
docker/grafana/
node_modules/
src/main/generated/
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM golang:1.22-alpine3.20 as builder
WORKDIR $GOPATH/src/go.k6.io/k6
ADD docker .
RUN apk --no-cache add git
RUN go install go.k6.io/xk6/cmd/xk6@latest
RUN xk6 build --with github.com/grafana/xk6-output-influxdb --output /tmp/k6

FROM alpine:3.20
RUN apk add --no-cache ca-certificates && \
adduser -D -u 12345 -g 12345 k6
COPY --from=builder /tmp/k6 /usr/bin/k6

USER 12345
WORKDIR /home/k6
ENTRYPOINT ["k6"]
50 changes: 47 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
## [본 과정] 이커머스 핵심 프로세스 구현
[단기 스킬업 Redis 교육 과정](https://hh-skillup.oopy.io/) 을 통해 상품 조회 및 주문 과정을 구현하며 현업에서 발생하는 문제를 Redis의 핵심 기술을 통해 해결합니다.
> Indexing, Caching을 통한 성능 개선 / 단계별 락 구현을 통한 동시성 이슈 해결 (낙관적/비관적 락, 분산락 등)
## Multi Module Design

#### **의존관계**
- `presentation` → `application` → `infrastructure`
- `application` → `domain`
- `infrastructure` → `domain`

#### **모듈별 역할**
- 각 모듈의 상세 설명은 해당 모듈 디렉토리 내 README.md 파일을 참고하십시오.

---

## Table Design
- 테이블 구조는 아래의 다이어그램으로 대체됩니다.
- **![img.png](img.png)**

---

## Architecture

- **Port-Adapter 패턴 적용**
- 각 모듈 간 결합을 제거하여 독립성을 유지합니다.
- `Application` 계층은 비즈니스 로직만 담당하며, 외부 기술에 의존하지 않습니다.
- `Infrastructure` 계층은 외부 기술(JPA, DB, API 등)을 담당하며, Port를 통해 `Application`과 통신합니다.

---

## API Design

#### **`GET /api/v1/screenings`**
- 최신 영화별로 그룹핑하여 빠른 시간순으로 정렬된 상영 영화 목록을 반환합니다.
- 기본적으로 오늘부터 2일 이내 상영 영화 목록을 반환하며, 클라이언트 요청에 따라 기간을 조정할 수 있습니다.
- **HTTP 요청 예시**:
- IntelliJ Http Client `http/getScreenings.http` 참고.

---

## 프로젝트 주요 특징
- **모듈화된 설계**: 명확한 책임 분리를 통해 유지보수와 확장성을 높임.
- **API 유연성**: 다양한 클라이언트 요청 시나리오를 지원할 수 있는 유연한 파라미터 설계.
- **테이블 설계와 아키텍처**: 프로젝트 구조와 데이터베이스 설계를 통해 높은 일관성과 성능을 유지.

---

## 데이터 관리
- **flyway**로 형상 관리. Movie, Genre, Seat, Cinema 엔티티 생성 및 기본 데이터 생성
- Screening(상영 영화 시간표)는 ``CommandLineRunner``를 구현하여 프로잭트 구동 시 생성
68 changes: 68 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
plugins {
id 'java'
id 'io.spring.dependency-management' version '1.1.7'
}

group = 'project.redis'
version = '0.0.1-SNAPSHOT'


repositories {
mavenCentral()
}

subprojects {
apply plugin: 'java'
apply plugin: 'java-library'
apply plugin: 'io.spring.dependency-management'

java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}

dependencyManagement {
imports {
mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.1'
}
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
}


dependencies {
compileOnly 'org.projectlombok:lombok:1.18.36'
annotationProcessor 'org.projectlombok:lombok:1.18.36'
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' // Java Time 지원


testImplementation 'com.fasterxml.jackson.core:jackson-databind'
testCompileOnly 'org.projectlombok:lombok:1.18.36'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.36'
}

tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
}

tasks {
test {
useJUnitPlatform()
}
}
}



jar {
enabled = false
}
83 changes: 83 additions & 0 deletions docker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
version: '3.8'
services:
redis-movie:
image: mysql:9.1.0
container_name: cinema-mysql
restart: always
ports:
- "3309:3306"
volumes:
- ./db/conf.d:/etc/mysql/conf.d
- ./db/data:/var/lib/mysql
- ./db/initdb.d:/docker-entrypoint-initdb.d
environment:
- TZ=Asia/Seoul
- MYSQL_ROOT_PASSWORD=1234
- MYSQL_DATABASE=redis-movie
- MYSQL_USER=hongs
- MYSQL_PASSWORD=local1234
command: >
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci

redis:
image: redis
container_name: redis-container
ports:
- "6380:6379"


influxdb:
image: influxdb:2.7.5
networks:
- monitoring
ports:
- "8086:8086"
volumes:
- ./influxdb/data:/var/lib/influxdb2
- ./influxdb/config:/etc/influxdb2
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=hongs
- DOCKER_INFLUXDB_INIT_PASSWORD=local1234
- DOCKER_INFLUXDB_INIT_ORG=hongs
- DOCKER_INFLUXDB_INIT_BUCKET=redis-cinema
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=EsM8u2l07RkAdG8pLE0adgUtL2tXBrdWqVtQyb1rBv4MnRuza8UF4hNYlzzFakyF66Cw9WnRyGLepctBa5DBWQ==
# - DOCKER_INFLUXDB_INIT_RETENTION=1d # 데이터 보존 기간 (24시간) => default : 0 (infinite)

k6:
image: k6-custom:latest
container_name: cinema-k6-load-test
restart: always
networks:
- monitoring
ports:
- "6565:6565"
environment:
- K6_OUT=xk6-influxdb=http://influxdb:8086
- K6_INFLUXDB_ORGANIZATION=hongs
- K6_INFLUXDB_BUCKET=redis-cinema
- K6_INFLUXDB_INSECURE=true
- K6_INFLUXDB_TOKEN=EsM8u2l07RkAdG8pLE0adgUtL2tXBrdWqVtQyb1rBv4MnRuza8UF4hNYlzzFakyF66Cw9WnRyGLepctBa5DBWQ==
volumes:
- ./k6/scripts:/scripts
entrypoint: ["tail", "-f", "/dev/null"] # 컨테이너 실행 후 대기 상태 유지


grafana:
image: grafana/grafana:8.2.6
ports:
- "3000:3000"
networks:
- monitoring
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
volumes:
- ./grafana/grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning

networks:
monitoring:
driver: bridge
25 changes: 25 additions & 0 deletions docker/k6/scripts/load_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import http from 'k6/http';
import { check } from 'k6';

export const options = {
stages: [
{ duration: '1m', target: 10 }, // 1분 동안 10명의 VU
{ duration: '2m', target: 50 }, // 2분 동안 50명의 VU 유지
{ duration: '1m', target: 0 }, // 1분 동안 VU 감소
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95%의 요청이 500ms 이내여야 함
'http_reqs': ['rate>50'], // 초당 50개의 요청 이상 처리
},
};

export default function () {
const url = 'http://host.docker.internal:8080/api/v1/screenings/redis'; // 테스트할 엔드포인트

const response = http.get(url);

check(response, {
"status is 200": (r) => r.status === 200,
"response time is acceptable": (r) => r.timings.duration < 500,
});
}
3 changes: 3 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#org.gradle.configuration-cache=true
#org.gradle.parallel=true
#org.gradle.caching=true
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# This file was generated by the Gradle 'init' task.
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format
Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
7 changes: 7 additions & 0 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading