Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=cinema
MYSQL_USER=cinema
MYSQL_PASSWORD=cinema
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary
41 changes: 41 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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 compose ###
cinema_mysql_data/
91 changes: 88 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,88 @@
## [본 과정] 이커머스 핵심 프로세스 구현
[단기 스킬업 Redis 교육 과정](https://hh-skillup.oopy.io/) 을 통해 상품 조회 및 주문 과정을 구현하며 현업에서 발생하는 문제를 Redis의 핵심 기술을 통해 해결합니다.
> Indexing, Caching을 통한 성능 개선 / 단계별 락 구현을 통한 동시성 이슈 해결 (낙관적/비관적 락, 분산락 등)
# hanghae-cinema
항해99 레디스

> spring boot 3.4.1
> java 21
> Mysql

### 기록
> **스프링부트 실행시 `AdapterApplication.java` 파일로 실행**

> `application.properties`파일은 `adapter`모듈에서 설정(DB포함 전부)
* 스프링부트를 실행시키는 `SpringApplication`가 위치한곳

> `domain model`에 `setter`사용할 예정으로 빌더패턴 사용 안함.

### 도커컴포즈
* 도커컴포즈 설정파일
* `docker-compose.yml`
* 탭: ❌, 스페이스바 2번: ⭕

* 도커컴포즈 변수 파일
* `.env`
* 보안을 위해 `.gitignore`에 추가해야 하지만 교육과제므로 github에 올림
* `.env.dev`, `.env.prod` 나누어 환경별로 다르게 적용도 가능

Choose a reason for hiding this comment

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

환경별로 나눠주신 부분 좋네요 👍


* 도커컴포즈 명령어
* 실행 : $docker-compose up -d
* 종료 : $docker-compose down
* 실행 상태 확인 : $docker-compose ps
* 볼륨 재거 : $docker-compose down --volumes
* 로그 확인 : $docker-compose logs -f // `-f`옵션을 주면 실시간
* 특정 서비스 시작 : $docker-compose start <서비스 이름>
* 특정 서비스 종료 : $docker-compose stop <서비스 이름>


--------------------------------------------------------------
### 적용 아키텍처
> 헥사고날 아키텍처

### 모듈 구성
> cinema-adapter
> cinema-application
> cinema-domain
> cinema-infrastructure

모듈은 헥사고날 아키텍처의 계층에 따라 나누었습니다.

Choose a reason for hiding this comment

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

헥사고날 아키텍처를 너무 잘 설계해주신듯 합니다 👍

  • 아키텍처에 대한 설명도 이해하기 쉽게 너무 잘해주셨습니다!


>1. cinema-adapter
> * 외부로부터의 요청을 받는 역할을 합니다.
> * 요청을 받아 `cinema-application`에 입력 포트를 호출 합니다.
>
>2. cinema-application
> * 입력/출력 포트에 대한 인터페이스를 정의하고 입력 포트에 비즈니스 로직을 구현 합니다.
> * 입력 포트는 Adapter에서 호출됩니다.
> * 출력 포트는 인터페이스만 정의하여 외부 시스템(DB등)과의 의존성을 최소화 합니다.
> * domain service 로직은 application 모듈 port(in)에서 호출합니다.
>
>3. cinema-domain
> * 외부에 의존하지 않는 독립적이고 핵심적인 비즈니스 모델 및 서비스 로직이 위치합니다.
> * 외부 기술에도 의존하지 않기에 순수 자바로만 작성합니다.
> * spring프레임워크등 프레임워크에도 의존하지 않습니다.
> * 그렇기 떄문에 트랜잭션 관리는 애플리케이션 계층에서 처리합니다.
>
>4. cinema-infrastructure
> * JPA 엔티티와 데이터베이스에 접근하는 계층입니다.
> * DB외에도 API등 외부 시스템과 상호작용 합니다.

도메인과 jpa엔티티를 나누었고 mapper클래스로 변환하도록 하였습니다.

### 테이블 디자인

아래와 같이 7개 테이블로 구성하였습니다.
> `영화`
> `상영관`
> `상영시간표`
> `영화예매내역`
> `상영관좌석`
> `회원`
> `업로드파일`

* ERD
* `docs/cinema_erd.png`
* 테이블 생성 쿼리
* `docs/cineam_create.sql`
* 초기 데이터
* `docs/init_insert.sql`

`영화`테이블의 `영상물등급`, `영화장르` 컬럼에 적재되는 하는 코드값은 `java`에 `enum`을 사용해 처리
50 changes: 50 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
plugins {
java
id("org.springframework.boot") version "3.4.1"
id("io.spring.dependency-management") version "1.1.7"
}

group = "com.hanghae"
version = "0.0.1-SNAPSHOT"

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

configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}

repositories {
mavenCentral()
}

dependencies {
compileOnly("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
developmentOnly("org.springframework.boot:spring-boot-devtools")
}

subprojects {

Choose a reason for hiding this comment

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

서브 프로젝트의 공통 의존성, 서브 프로젝트 별 필요한 의존성을 잘 설정해주셨네요 👍

apply(plugin = "java")
apply(plugin = "io.spring.dependency-management")

dependencies {
implementation("jakarta.transaction:jakarta.transaction-api")
implementation("org.springframework.boot:spring-boot-starter")
testImplementation("org.springframework.boot:spring-boot-starter-test")
annotationProcessor("org.projectlombok:lombok")
compileOnly("org.projectlombok:lombok")
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
}
}

tasks.withType<Test> {
useJUnitPlatform()
}
23 changes: 23 additions & 0 deletions cinema-adapter/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
plugins {
id("java")
id("org.springframework.boot") version "3.4.1"
id("io.spring.dependency-management") version "1.1.7"
}

group = "com.hanghae"
version = "0.0.1-SNAPSHOT"

repositories {
mavenCentral()
}

dependencies {
implementation(project(":cinema-application")) // 애플리케이션 계층 의존성
implementation(project(":cinema-infrastructure")) // RepositoryPort 구현체를 찾지 못해서 추가
implementation("org.springframework.boot:spring-boot-starter-web") // web
implementation("org.springframework.boot:spring-boot-starter-data-jpa") //jpa 레포지토리 의존성 문제로 추가 (@EnableJpaRepositories)
}

tasks.test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.hanghae.adapter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

/*
1. 다른 모듈의 빈을 찾지 못하는 문제 해결을 위해 "@ComponentScan" 어노테이션 추가
2. JpaRepository를 찾지 못해 "@EnableJpaRepositories" 추가
3. Jpa Entity를 찾지 못해 "@EntityScan" 추가
4. application.properties 파일은 스프링부트를 실행시키는 "cinema-adapter" 모듈에 작성해야 적용됨
* 타모듈 application.properties에 설정해도 적용 안됨
*/
@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.hanghae.infrastructure.repository")
@EntityScan(basePackages = "com.hanghae.infrastructure.entity")
@ComponentScan(basePackages = {
"com.hanghae.domain",
"com.hanghae.application",
"com.hanghae.infrastructure",
"com.hanghae.adapter"
})
public class AdapterApplication {
public static void main(String[] args) {
//서버 실행시 어뎁터로 실행
SpringApplication.run(AdapterApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.hanghae.adapter.web;

import com.hanghae.application.dto.MovieScheduleResponseDto;
import com.hanghae.application.port.in.MovieScheduleService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequiredArgsConstructor
public class MovieController {
private final MovieScheduleService movieScheduleService;

@GetMapping("/api/v1/movie-schedules")
public List<MovieScheduleResponseDto> getMovieSchedules() {
return movieScheduleService.getMovieSchedules();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.hanghae.adapter.web.exception;


public record ErrorResponse(int status, String message) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.hanghae.adapter.web.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException e) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
}
30 changes: 30 additions & 0 deletions cinema-adapter/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
spring.application.name=cinema

# 서버 포트 설정 (기본값: 8080)
server.port=8080

# MySQL 설정
spring.datasource.url=jdbc:mysql://localhost:3307/cinema?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8&useUnicode=true
spring.datasource.username=cinema
spring.datasource.password=cinema
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA 설정
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=true
spring.jpa.format-sql=true

# 쿼리를 가독성 있게 포맷팅
spring.jpa.properties.hibernate.format_sql=true

# SQL 실행 로그 출력, 파라미터 값까지 출력 (Spring Boot 2.1 이상 지원)
spring.jpa.properties.hibernate.type=TRACE
spring.jpa.properties.hibernate.highlight_sql=true

# 바인딩된 파라미터 값도 출력 (Hibernate 5.3 이상)
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql=TRACE

# log
#logging.level.root=info
20 changes: 20 additions & 0 deletions cinema-application/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
id("java")
id("org.springframework.boot") version "3.4.1"
id("io.spring.dependency-management") version "1.1.7"
}

group = "com.hanghae"
version = "0.0.1-SNAPSHOT"

repositories {
mavenCentral()
}

dependencies {
implementation(project(":cinema-domain")) // 도메인 계층 의존성
}

tasks.test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.hanghae.application.dto;

import java.time.LocalDate;
import java.time.LocalDateTime;

public record MovieScheduleResponseDto(
String title, //영화 제목
String rating, //영상물 등급
LocalDate releaseDate, //개봉일
String thumbnailPath, // 썸네일 경로
Long runningTimeMinutes, // 러닝타임(분)
String genre, // 영화 장르
String screenName, // 상영관 이름
LocalDateTime showStartAt // 상영시작시간
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.hanghae.application.port.in;

import com.hanghae.application.dto.MovieScheduleResponseDto;

import java.util.List;

public interface MovieScheduleService {
List<MovieScheduleResponseDto> getMovieSchedules();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.hanghae.application.port.out;

import com.hanghae.domain.model.ScreeningSchedule;

import java.util.List;

public interface ScreeningScheduleRepositoryPort {
List<ScreeningSchedule> findAll();
}
Loading
Loading