[#3] cafe api 개발 및 전반적인 container 환경 설정 세팅

- docker compose 활용한 컨테이너 환경 구축
- cafe 관련 api 구축
- flyway migration db 및 seed data 설정
This commit is contained in:
beaniejoy
2021-10-09 02:41:20 +09:00
parent 1dd0defd28
commit c4a1c849cf
25 changed files with 461 additions and 4 deletions

34
Dockerfile Normal file
View File

@@ -0,0 +1,34 @@
FROM adoptopenjdk/openjdk11:latest as BUILD_IMAGE
ENV WORK_DIR=/usr/app/
# app 작업 디렉토리 설정
WORKDIR $WORK_DIR
# gradle 실행을 위한 필수 디렉토리 준비
COPY gradlew $WORK_DIR
COPY build.gradle $WORK_DIR
COPY settings.gradle $WORK_DIR
COPY gradle $WORK_DIR/gradle
RUN ./gradlew -x test build || return 0
COPY src src
# jar 파일 build
RUN ./gradlew bootjar
FROM adoptopenjdk/openjdk11:latest
ENV WORK_DIR=/usr/app/
WORKDIR $WORK_DIR
COPY --from=BUILD_IMAGE $WORK_DIR/build/libs/*.jar app-server.jar
ENTRYPOINT ["java", \
"-jar", \
"-Dspring.profiles.active=${PROFILE_OPTION}", \
"-Dspring.datasource.url=${SPRING_DATASOURCE_URL}", \
"-Dredis.host=${REDIS_HOST}", \
"app-server.jar"]

View File

@@ -22,14 +22,28 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
// log4j2
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
testImplementation 'org.springframework.boot:spring-boot-starter-log4j2'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
// MySQL
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
configurations {
all {
// log4j2 적용을 위해 기존 spring boot에서 제공하는 logging exclude
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
}
test {
useJUnitPlatform()
}

50
docker-compose.yml Normal file
View File

@@ -0,0 +1,50 @@
version: "3.8"
services:
nginx:
image: nginx
container_name: nginx
ports:
- "80:80"
volumes:
- ./nginx/:/etc/nginx/conf.d/
depends_on:
- app-server-1
- app-server-2
app-server-1:
build:
context: .
dockerfile: Dockerfile
env_file:
- env/app.env
depends_on:
- db-mysql
app-server-2:
build:
context: .
dockerfile: Dockerfile
env_file:
- env/app.env
depends_on:
- db-mysql
migration:
image: flyway/flyway:7.5.1
command: -configFiles=/flyway/conf/flyway.config -locations=filesystem:/flyway/sql -connectRetries=60 migrate
volumes:
- ${PWD}/flyway/db-migration/main:/flyway/sql
- ${PWD}/flyway/conf/flyway_main.conf:/flyway/conf/flyway.config
depends_on:
- db-mysql
seed:
image: flyway/flyway:7.5.1
command: -configFiles=/flyway/conf/flyway.config -locations=filesystem:/flyway/sql -connectRetries=60 migrate
volumes:
- ${PWD}/flyway/db-migration/seed:/flyway/sql
- ${PWD}/flyway/conf/flyway_seed.conf:/flyway/conf/flyway.config
depends_on:
- db-mysql
db-mysql:
image: mysql:5.7.34
ports:
- "3306:3306"
env_file:
- env/mysql.env

4
env/app.env vendored Normal file
View File

@@ -0,0 +1,4 @@
PROFILE_OPTION=dev
SPRING_DATASOURCE_URL=jdbc:mysql://db-mysql:3306/dongnecafe?autoreconnect=true&characterEncoding=utf8&serverTimezone=Asia/Seoul
SPRING_DATASOURCE_USERNAME=root
SPRING_DATASOURCE_PASSWORD=dev

2
env/mysql.env vendored Normal file
View File

@@ -0,0 +1,2 @@
MYSQL_DATABASE=dongnecafe
MYSQL_ROOT_PASSWORD=dev

7
flyway/Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM flyway/flyway
COPY conf/flyway_main.conf main.conf
COPY conf/flyway_seed.conf seed.conf
ENTRYPOINT ["-configFiles=main.conf", "migrate"]
ENTRYPOINT ["-configFiles=seed.conf", "migrate"]

View File

@@ -0,0 +1,4 @@
flyway.url=jdbc:mysql://db-mysql:3306/dongnecafe
flyway.user=root
flyway.password=dev
flyway.driver=com.mysql.cj.jdbc.Driver

View File

@@ -0,0 +1,4 @@
flyway.url=jdbc:mysql://db-mysql:3306/dongnecafe
flyway.user=root
flyway.password=dev
flyway.driver=com.mysql.cj.jdbc.Driver

10
nginx/default.conf Normal file
View File

@@ -0,0 +1,10 @@
upstream appserver {
server app-server-1:8080;
server app-server-2:8080;
}
server {
listen 80;
location / {
proxy_pass http://appserver;
}
}

View File

@@ -1,4 +1,4 @@
package io.beaniejoy.dongecafe;
package io.beaniejoy.dongnecafe;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

View File

@@ -0,0 +1,39 @@
package io.beaniejoy.dongnecafe.cafe.controller;
import io.beaniejoy.dongnecafe.cafe.dto.CafeResponseDto;
import io.beaniejoy.dongnecafe.cafe.service.CafeService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
@RestController
@RequiredArgsConstructor
@RequestMapping("/cafes")
public class CafeController {
private final CafeService cafeService;
@GetMapping("/")
public ResponseEntity<List<CafeResponseDto>> getCafeList(
@PageableDefault(sort = "name", direction = Sort.Direction.ASC, size = 10) Pageable pageable) {
List<CafeResponseDto> cafeResponseList = cafeService.getCafeList(pageable);
return ResponseEntity.ok(cafeResponseList);
}
@GetMapping("/{cafeId}")
public ResponseEntity<CafeResponseDto> getCafeInfo(@PathVariable("cafeId") UUID cafeId) {
CafeResponseDto cafeResponse = cafeService.getCafeByCafeId(cafeId);
return ResponseEntity.ok(cafeResponse);
}
}

View File

@@ -0,0 +1,54 @@
package io.beaniejoy.dongnecafe.cafe.domain;
import io.beaniejoy.dongnecafe.cafe.dto.CafeResponseDto;
import io.beaniejoy.dongnecafe.common.domain.BaseTimeEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.List;
import java.util.UUID;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Cafe extends BaseTimeEntity {
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
@Column(name = "cafe_id", columnDefinition = "BINARY(16)")
private UUID cafeId;
private String name;
private String address;
private String phoneNumber;
private Double totalRate;
private String description;
@OneToMany(mappedBy = "cafe", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<CafeMenu> cafeMenuList;
@OneToMany(mappedBy = "cafe", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<CafeImage> cafeImageList;
public CafeResponseDto toResponseDto() {
return CafeResponseDto.builder()
.cafeId(cafeId)
.name(name)
.address(address)
.phoneNumber(phoneNumber)
.totalRate(totalRate)
.description(description)
.build();
}
}

View File

@@ -0,0 +1,31 @@
package io.beaniejoy.dongnecafe.cafe.domain;
import io.beaniejoy.dongnecafe.common.domain.BaseTimeEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.UUID;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class CafeImage extends BaseTimeEntity {
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
@Column(name = "cafe_img_id", columnDefinition = "BINARY(16)")
private UUID cafeImgId;
private String imgUrl;
@ManyToOne
@JoinColumn(name = "cafe_id")
private Cafe cafe;
}

View File

@@ -0,0 +1,37 @@
package io.beaniejoy.dongnecafe.cafe.domain;
import io.beaniejoy.dongnecafe.common.domain.BaseTimeEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.List;
import java.util.UUID;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class CafeMenu extends BaseTimeEntity {
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
@Column(name = "menu_id", columnDefinition = "BINARY(16)")
private UUID menuId;
private String name;
private Integer price;
@ManyToOne
@JoinColumn(name = "cafe_id")
private Cafe cafe;
@OneToMany(mappedBy = "cafeMenu")
private List<MenuOption> menuOptionList;
}

View File

@@ -0,0 +1,31 @@
package io.beaniejoy.dongnecafe.cafe.domain;
import io.beaniejoy.dongnecafe.common.domain.BaseTimeEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.UUID;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class MenuOption extends BaseTimeEntity {
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
@Column(name = "option_id", columnDefinition = "BINARY(16)")
private UUID optionId;
private String title;
@ManyToOne
@JoinColumn(name = "menu_id")
private CafeMenu cafeMenu;
}

View File

@@ -0,0 +1,27 @@
package io.beaniejoy.dongnecafe.cafe.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.UUID;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CafeResponseDto {
private UUID cafeId;
private String name;
private String address;
private String phoneNumber;
private Double totalRate;
private String description;
}

View File

@@ -0,0 +1,14 @@
package io.beaniejoy.dongnecafe.cafe.error;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class CafeExceptionHandler {
@ExceptionHandler(CafeNotFoundException.class)
public ResponseEntity<String> handleNotFound(CafeNotFoundException exception) {
return ResponseEntity.badRequest().body(exception.getMessage());
}
}

View File

@@ -0,0 +1,9 @@
package io.beaniejoy.dongnecafe.cafe.error;
import java.util.UUID;
public class CafeNotFoundException extends RuntimeException {
public CafeNotFoundException(UUID cafeId) {
super("Cafe[" + cafeId + "] is not found");
}
}

View File

@@ -0,0 +1,13 @@
package io.beaniejoy.dongnecafe.cafe.repository;
import io.beaniejoy.dongnecafe.cafe.domain.Cafe;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface CafeRepository extends JpaRepository<Cafe, UUID> {
Page<Cafe> findAll(Pageable pageable);
}

View File

@@ -0,0 +1,38 @@
package io.beaniejoy.dongnecafe.cafe.service;
import io.beaniejoy.dongnecafe.cafe.domain.Cafe;
import io.beaniejoy.dongnecafe.cafe.dto.CafeResponseDto;
import io.beaniejoy.dongnecafe.cafe.error.CafeNotFoundException;
import io.beaniejoy.dongnecafe.cafe.repository.CafeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class CafeService {
private final CafeRepository cafeRepository;
public List<CafeResponseDto> getCafeList(Pageable pageable) {
Page<Cafe> cafeListWithPagination = cafeRepository.findAll(pageable);
return cafeListWithPagination.stream()
.map(Cafe::toResponseDto)
.collect(Collectors.toList());
}
public CafeResponseDto getCafeByCafeId(UUID cafeId) {
Cafe cafe = cafeRepository.findById(cafeId)
.orElseThrow(() -> new CafeNotFoundException(cafeId));
return cafe.toResponseDto();
}
}

View File

@@ -0,0 +1,9 @@
package io.beaniejoy.dongnecafe.common.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@Configuration
@EnableJpaAuditing
public class AuditingConfig {
}

View File

@@ -0,0 +1,21 @@
package io.beaniejoy.dongnecafe.common.domain;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime updatedDate;
}

View File

@@ -1 +0,0 @@

View File

@@ -0,0 +1,6 @@
spring:
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true

View File

@@ -1,4 +1,4 @@
package io.beaniejoy.dongecafe;
package io.beaniejoy.dongnecafe;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;