[#5] Refactor: kotlin 베이스로 프로젝트 구성

- cafe 도메인 중심으로 코틀린 변환중
- build.gradle 코틀린 관련 플러그인 추가 및 log4j2 제외 처리
This commit is contained in:
beaniejoy
2022-06-22 01:29:02 +09:00
parent 162533389d
commit 9d7850e7d1
21 changed files with 263 additions and 320 deletions

View File

@@ -12,12 +12,16 @@ buildscript {
classpath "io.spring.gradle:dependency-management-plugin:${dependencyManagementVersion}"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
classpath "org.jetbrains.kotlin:kotlin-noarg:${kotlinVersion}"
classpath "org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}"
}
}
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: "kotlin-jpa"
apply plugin: "kotlin-noarg"
apply plugin: "kotlin-allopen"
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
@@ -30,26 +34,41 @@ repositories {
mavenCentral()
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
allOpen {
annotation("javax.persistence.Entity")
annotation("javax.persistence.MappedSuperclass")
annotation("javax.persistence.Embeddable")
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
implementation('io.github.microutils:kotlin-logging:2.1.21')
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'
annotationProcessor 'org.projectlombok:lombok'
// log4j2
// implementation 'org.springframework.boot:spring-boot-starter-log4j2'
// testImplementation 'org.springframework.boot:spring-boot-starter-log4j2'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
// MySQL
runtimeOnly 'mysql:mysql-connector-java'
runtimeOnly 'mysql:mysql-connector-java' // MySQL
runtimeOnly 'com.h2database:h2' // H2
// annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

View File

@@ -1,21 +0,0 @@
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

@@ -0,0 +1,18 @@
package io.beaniejoy.dongnecafe.common.domain
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.LocalDateTime
import javax.persistence.EntityListeners
import javax.persistence.MappedSuperclass
@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
open class BaseTimeEntity {
@CreatedDate
private val createdDate: LocalDateTime? = null
@LastModifiedDate
private val updatedDate: LocalDateTime? = null
}

View File

@@ -4,7 +4,7 @@ import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeInfoResponseDto
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeSearchResponseDto
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeUpdateRequestDto
import io.beaniejoy.dongnecafe.domain.cafe.service.CafeService
import lombok.RequiredArgsConstructor
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.web.PageableDefault
@@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.*
import java.util.*
@RestController
@RequiredArgsConstructor
@RequestMapping("/cafes")
class CafeController(
private val cafeService: CafeService
@@ -21,23 +20,30 @@ class CafeController(
@GetMapping
fun searchCafeList(
@PageableDefault(sort = ["name"], direction = Sort.Direction.ASC, size = 10) pageable: Pageable
): ResponseEntity<List<CafeSearchResponseDto>> {
val cafeResponseList = cafeService.getCafeList(pageable)
return ResponseEntity.ok(cafeResponseList)
): Page<CafeSearchResponseDto> {
return cafeService.getCafeList(pageable)
}
@GetMapping("/{cafeId}")
fun getCafeDetailedInfo(@PathVariable("cafeId") cafeId: UUID?): ResponseEntity<CafeInfoResponseDto> {
fun getCafeDetailedInfo(@PathVariable("cafeId") cafeId: UUID): ResponseEntity<CafeInfoResponseDto> {
val cafeResponse = cafeService.getCafeInfoByCafeId(cafeId)
return ResponseEntity.ok(cafeResponse)
}
@PutMapping("/{cafeId}")
// TODO spring boot validation 적용 필요
@PutMapping("/{id}")
fun updateCafeInfo(
@PathVariable("cafeId") cafeId: UUID,
@RequestBody resource: CafeUpdateRequestDto?
@PathVariable("id") id: UUID,
@RequestBody resource: CafeUpdateRequestDto
): ResponseEntity<String> {
cafeService.updateCafe(cafeId, resource)
return ResponseEntity.ok("Successfully Cafe[$cafeId] Info Updated")
cafeService.updateCafe(
id = id,
name = resource.name!!,
address = resource.address!!,
phoneNumber = resource.phoneNumber!!,
description = resource.description!!
)
return ResponseEntity.ok("Successfully Cafe[$id] Info Updated")
}
}

View File

@@ -1,84 +0,0 @@
package io.beaniejoy.dongnecafe.domain.cafe.domain;
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeInfoResponseDto;
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeSearchResponseDto;
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;
import java.util.stream.Collectors;
@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)
private List<CafeMenu> cafeMenuList;
@OneToMany(mappedBy = "cafe", fetch = FetchType.LAZY)
private List<CafeImage> cafeImageList;
public void updateInfo(String name, String address, String phoneNumber, String description) {
this.name = name;
this.address = address;
this.phoneNumber = phoneNumber;
this.description = description;
}
public CafeSearchResponseDto toSearchResponseDto() {
return CafeSearchResponseDto.builder()
.cafeId(cafeId)
.name(name)
.address(address)
.totalRate(totalRate)
.imageList(cafeImageList
.stream()
.map(CafeImage::toResponseDto)
.collect(Collectors.toList()))
.build();
}
public CafeInfoResponseDto toInfoResponseDto() {
return CafeInfoResponseDto.builder()
.cafeId(cafeId)
.name(name)
.address(address)
.phoneNumber(phoneNumber)
.totalRate(totalRate)
.description(description)
.menuList(cafeMenuList
.stream()
.map(CafeMenu::toListResponseDto)
.collect(Collectors.toList()))
.imageList(cafeImageList
.stream()
.map(CafeImage::toResponseDto)
.collect(Collectors.toList()))
.build();
}
}

View File

@@ -0,0 +1,42 @@
package io.beaniejoy.dongnecafe.domain.cafe.domain
import io.beaniejoy.dongnecafe.common.domain.BaseTimeEntity
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeInfoResponseDto
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeSearchResponseDto
import java.util.stream.Collectors
import javax.persistence.*
@Entity(name = "cafe")
class Cafe(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long,
@Column(name = "name")
var name: String,
@Column(name = "address")
var address: String,
@Column(name = "phone_number")
var phoneNumber: String,
@Column(name = "total_rate")
val totalRate: Double,
@Column(name = "description")
var description: String,
@OneToMany(mappedBy = "cafe", fetch = FetchType.LAZY)
val cafeMenuList: MutableList<CafeMenu>,
@OneToMany(mappedBy = "cafe", fetch = FetchType.LAZY)
val cafeImageList: MutableList<CafeImage>
) : BaseTimeEntity() {
fun updateInfo(name: String, address: String, phoneNumber: String, description: String) {
this.name = name
this.address = address
this.phoneNumber = phoneNumber
this.description = description
}
}

View File

@@ -1,38 +0,0 @@
package io.beaniejoy.dongnecafe.domain.cafe.domain;
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeImageResponseDto;
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;
public CafeImageResponseDto toResponseDto() {
return CafeImageResponseDto.builder()
.imgUrl(imgUrl)
.build();
}
}

View File

@@ -0,0 +1,18 @@
package io.beaniejoy.dongnecafe.domain.cafe.domain
import io.beaniejoy.dongnecafe.common.domain.BaseTimeEntity
import javax.persistence.*
@Entity
class CafeImage(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long,
@Column(name = "img_url")
val imgUrl: String,
@ManyToOne
@JoinColumn(name = "cafe_id")
val cafe: Cafe,
) : BaseTimeEntity()

View File

@@ -1,15 +0,0 @@
package io.beaniejoy.dongnecafe.domain.cafe.dto.cafe;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CafeImageResponseDto {
private String imgUrl;
}

View File

@@ -0,0 +1,13 @@
package io.beaniejoy.dongnecafe.domain.cafe.dto.cafe
import io.beaniejoy.dongnecafe.domain.cafe.domain.CafeImage
data class CafeImageResponseDto(
val imgUrl: String? = null
) {
companion object {
fun of(cafeImage: CafeImage): CafeImageResponseDto {
return CafeImageResponseDto(cafeImage.imgUrl)
}
}
}

View File

@@ -1,32 +0,0 @@
package io.beaniejoy.dongnecafe.domain.cafe.dto.cafe;
import io.beaniejoy.dongnecafe.domain.cafe.dto.menu.CafeMenuListResponseDto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.UUID;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CafeInfoResponseDto {
private UUID cafeId;
private String name;
private String address;
private String phoneNumber;
private Double totalRate;
private String description;
private List<CafeMenuListResponseDto> menuList;
private List<CafeImageResponseDto> imageList;
}

View File

@@ -0,0 +1,30 @@
package io.beaniejoy.dongnecafe.domain.cafe.dto.cafe
import io.beaniejoy.dongnecafe.domain.cafe.domain.Cafe
import io.beaniejoy.dongnecafe.domain.cafe.dto.menu.CafeMenuListResponseDto
data class CafeInfoResponseDto(
val id: Long? = null,
val name: String? = null,
val address: String? = null,
val phoneNumber: String? = null,
val totalRate: Double? = null,
val description: String? = null,
val menuList: List<CafeMenuListResponseDto> = emptyList(),
val imageList: List<CafeImageResponseDto> = emptyList()
) {
companion object {
fun of(cafe: Cafe): CafeInfoResponseDto {
return CafeInfoResponseDto(
id = cafe.id,
name = cafe.name,
address = cafe.address,
phoneNumber = cafe.phoneNumber,
totalRate = cafe.totalRate,
description = cafe.description,
menuList = cafe.cafeMenuList.map { it.toListResponseDto() },
imageList = cafe.cafeImageList.map { CafeImageResponseDto.of(it) }
)
}
}
}

View File

@@ -1,26 +0,0 @@
package io.beaniejoy.dongnecafe.domain.cafe.dto.cafe;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.UUID;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CafeSearchResponseDto {
private UUID cafeId;
private String name;
private String address;
private Double totalRate;
private List<CafeImageResponseDto> imageList;
}

View File

@@ -0,0 +1,23 @@
package io.beaniejoy.dongnecafe.domain.cafe.dto.cafe
import io.beaniejoy.dongnecafe.domain.cafe.domain.Cafe
data class CafeSearchResponseDto(
val id: Long = 0L,
val name: String? = null,
val address: String? = null,
val totalRate: Double? = null,
val imageList: List<CafeImageResponseDto> = emptyList()
) {
companion object {
fun of(cafe: Cafe): CafeSearchResponseDto {
return CafeSearchResponseDto(
id = cafe.id,
name = cafe.name,
address = cafe.address,
totalRate = cafe.totalRate,
imageList = cafe.cafeImageList.map { CafeImageResponseDto.of(it) }
)
}
}
}

View File

@@ -1,19 +0,0 @@
package io.beaniejoy.dongnecafe.domain.cafe.dto.cafe;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class CafeUpdateRequestDto {
private String name;
private String address;
private String phoneNumber;
private String description;
}

View File

@@ -0,0 +1,8 @@
package io.beaniejoy.dongnecafe.domain.cafe.dto.cafe
data class CafeUpdateRequestDto(
val name: String? = null,
val address: String? = null,
val phoneNumber: String? = null,
val description: String? = null
)

View File

@@ -1,13 +0,0 @@
package io.beaniejoy.dongnecafe.domain.cafe.repository;
import io.beaniejoy.dongnecafe.domain.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,10 @@
package io.beaniejoy.dongnecafe.domain.cafe.repository
import io.beaniejoy.dongnecafe.domain.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.*
interface CafeRepository : JpaRepository<Cafe, UUID> {
}

View File

@@ -1,53 +0,0 @@
package io.beaniejoy.dongnecafe.domain.cafe.service;
import io.beaniejoy.dongnecafe.domain.cafe.domain.Cafe;
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeInfoResponseDto;
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeSearchResponseDto;
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeUpdateRequestDto;
import io.beaniejoy.dongnecafe.domain.cafe.error.CafeNotFoundException;
import io.beaniejoy.dongnecafe.domain.cafe.repository.CafeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class CafeService {
private final CafeRepository cafeRepository;
@Transactional(readOnly = true)
public List<CafeSearchResponseDto> getCafeList(Pageable pageable) {
Page<Cafe> cafeListWithPagination = cafeRepository.findAll(pageable);
return cafeListWithPagination.stream()
.map(Cafe::toSearchResponseDto)
.collect(Collectors.toList());
}
@Transactional(readOnly = true)
public CafeInfoResponseDto getCafeInfoByCafeId(UUID cafeId) {
Cafe cafe = cafeRepository.findById(cafeId)
.orElseThrow(() -> new CafeNotFoundException(cafeId));
return cafe.toInfoResponseDto();
}
public void updateCafe(UUID cafeId, CafeUpdateRequestDto resource) {
Cafe cafe = cafeRepository.findById(cafeId)
.orElseThrow(() -> new CafeNotFoundException(cafeId));
cafe.updateInfo(
resource.getName(),
resource.getAddress(),
resource.getPhoneNumber(),
resource.getDescription());
}
}

View File

@@ -0,0 +1,52 @@
package io.beaniejoy.dongnecafe.domain.cafe.service
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeInfoResponseDto
import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeSearchResponseDto
import io.beaniejoy.dongnecafe.domain.cafe.error.CafeNotFoundException
import io.beaniejoy.dongnecafe.domain.cafe.repository.CafeRepository
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.*
@Service
@Transactional
class CafeService(
private val cafeRepository: CafeRepository
) {
@Transactional(readOnly = true)
fun getCafeList(pageable: Pageable): Page<CafeSearchResponseDto> {
val cafeListWithPagination = cafeRepository.findAll(pageable)
return cafeListWithPagination.map { CafeSearchResponseDto.of(it) }
}
@Transactional(readOnly = true)
fun getCafeInfoByCafeId(cafeId: UUID): CafeInfoResponseDto {
val cafe = cafeRepository.findByIdOrNull(cafeId)
?: throw CafeNotFoundException(cafeId)
return CafeInfoResponseDto.of(cafe)
}
fun updateCafe(
id: UUID,
name: String,
address: String,
phoneNumber: String,
description: String,
) {
val cafe = cafeRepository.findByIdOrNull(id)
?: throw CafeNotFoundException(id)
cafe.updateInfo(
name = name,
address = address,
phoneNumber = phoneNumber,
description = description
)
}
}

View File

@@ -1,6 +1,11 @@
spring:
datasource:
url: jdbc:h2:mem:testdb;MODE=MySQL
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
format_sql: true
show-sql: true