Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature/12] feat: 온보딩 선택을 등록하는 API 구현 #14

Merged
merged 10 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#

# 문서
swagger: http://localhost:8080/swagger-ui

# 설정
vm options: `-Duser.timezone=Asia/Seoul`
14 changes: 14 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ plugins {
kotlin("plugin.jpa") version "1.9.24"
kotlin("jvm") version "1.9.24"
kotlin("plugin.spring") version "1.9.24"
kotlin("plugin.allopen") version "1.5.31"
kotlin("plugin.noarg") version "1.5.31"
}

allOpen {
annotation("javax.persistence.Entity")
annotation("javax.persistence.Convert")
}

noArg {
annotation("javax.persistence.Entity")
annotation("javax.persistence.Convert")
Comment on lines +7 to +18
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

코틀린은 디폴트가 final인데, jpa는 프록시를 생성해야 하기 때문에 상속이 가능해야하잖아요. 그래서 open으로 상속이 가능하게 바꿔주는 설정입니다

}

group = "com.nexters"
Expand All @@ -23,6 +35,8 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("io.springfox:springfox-boot-starter:3.0.0")
implementation("io.springfox:springfox-swagger-ui:3.0.0")
Comment on lines +39 to +40
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

swagger 관련 설정입니다!

implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")

Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/nexters/bottles/BottlesApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import org.springframework.boot.runApplication
class BottlesApplication

fun main(args: Array<String>) {
runApplication<BottlesApplication>(*args)
runApplication<BottlesApplication>(*args)
}
25 changes: 25 additions & 0 deletions src/main/kotlin/com/nexters/bottles/config/JacksonConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.nexters.bottles.config

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinFeature
import com.fasterxml.jackson.module.kotlin.KotlinModule
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class JacksonConfig {

@Bean
fun objectMapper(): ObjectMapper {
return ObjectMapper().registerModule(
KotlinModule.Builder()
.withReflectionCacheSize(512)
.configure(KotlinFeature.NullToEmptyCollection, false)
.configure(KotlinFeature.NullToEmptyMap, false)
.configure(KotlinFeature.NullIsSameAsDefault, false)
.configure(KotlinFeature.SingletonSupport, DISABLED)
.configure(KotlinFeature.StrictNullChecks, false)
.build()
)
}
}
21 changes: 21 additions & 0 deletions src/main/kotlin/com/nexters/bottles/config/SwaggerConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.nexters.bottles.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import springfox.documentation.builders.PathSelectors
import springfox.documentation.builders.RequestHandlerSelectors
import springfox.documentation.spi.DocumentationType
import springfox.documentation.spring.web.plugins.Docket

@Configuration
class SwaggerConfig {

@Bean
fun api(): Docket {
return Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.nexters.bottles"))
.paths(PathSelectors.any())
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.nexters.bottles.profile.controller

import com.nexters.bottles.profile.controller.dto.RegisterProfileRequestDto
import com.nexters.bottles.profile.facade.ProfileFacade
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api")
class ProfileController(
private val profileFacade: ProfileFacade,
) {

@PostMapping("/profile/choice")
fun registerProfile(@RequestBody registerProfileRequestDto: RegisterProfileRequestDto) {
profileFacade.saveProfile(registerProfileRequestDto)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.nexters.bottles.profile.controller.dto

data class InterestDto(
val culture: List<String> = arrayListOf(),
val sports: List<String> = arrayListOf(),
val entertainment: List<String> = arrayListOf(),
val etc: List<String> = arrayListOf(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.nexters.bottles.profile.controller.dto

data class RegionDto(
val city: String,
val state: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.nexters.bottles.profile.controller.dto

data class RegisterProfileRequestDto(
val mbti: String,
val keyword: List<String>,
val interest: InterestDto,
val job: String,
val smoking: String,
val alcohol: String,
val religion: String,
val region: RegionDto
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.nexters.bottles.profile.domain

import com.nexters.bottles.profile.controller.dto.InterestDto
import com.nexters.bottles.profile.controller.dto.RegionDto

data class ProfileSelect(
val mbti: String,
val keyword: List<String> = arrayListOf(),
val interest: InterestDto,
val job: String,
val smoking: String,
val alcohol: String,
val religion: String,
val region: RegionDto,
)
29 changes: 29 additions & 0 deletions src/main/kotlin/com/nexters/bottles/profile/domain/UserProfile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.nexters.bottles.profile.domain

import com.nexters.bottles.profile.repository.converter.ProfileSelectConverter
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp
import java.time.LocalDateTime
import javax.persistence.*

@Entity
class UserProfile(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,

@Column(name = "user_id")
var userId: Long,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

요거 생각이 안나서 이렇게 했는데 낼 수정해놓을게요!


@Column(name = "profile_select")
@Convert(converter = ProfileSelectConverter::class)
var profileSelect: ProfileSelect,

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이 저장되는 데이터를 별도로 검색하는 요구 조건이 없고, 통째로 저장하고 통째로 꺼내쓸 것 같아서 json으로 db에 저장하게 했어요

Copy link
Member

Choose a reason for hiding this comment

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

지금은 좋은 것 같아요!
그런데 혹시 나중에 '매칭할 때 키워드를 기반으로 매칭한다' 와 같은 조건이 생긴다면 어떻게 해야할지 생각해봐야할 것 같아요 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

mysql 5.8이후 부터인가 json 안에 특정 값의 index도 가능하고, 값에 대해 쿼리도 가능하더라고요! 추후 마이그레이션도 좋습니다!

@Column(name = "created_at", updatable = false)
@CreationTimestamp
val createdAt: LocalDateTime = LocalDateTime.now(),

@Column(name = "updated_at")
@UpdateTimestamp
var updatedAt: LocalDateTime = LocalDateTime.now()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.nexters.bottles.profile.facade

import com.nexters.bottles.profile.controller.dto.RegisterProfileRequestDto
import com.nexters.bottles.profile.domain.ProfileSelect
import com.nexters.bottles.profile.domain.UserProfile
import com.nexters.bottles.profile.service.ProfileService
import org.springframework.stereotype.Component

@Component
class ProfileFacade(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

파사드 클래스 써보셨나요?
controller -> facde -> service -> repository

이렇게 레이어가 생기게 했어요. 이유는 서비스 레이어 (service, component)간에 참조가 생기지 않게하기 위해서 많이 쓰는데 어떻게 쓰시나요? 단순 mvc를 할때는 사실 불필요할 수 있어서 의견 내주세요!

Copy link
Member

Choose a reason for hiding this comment

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

저는 파사드 클래스 써보진 않았어요!
서비스 레이어 간에 참조가 생기지 않게한다는 것은 service에서는 domain과 repository만 의존하도록 하고 dto의 참조를 끊기 위해서인가요?
이렇게 하는 이유가 dto가 변경될 가능성이 높아서 service까지 영향을 미치지 못하게 하기 위함인가요?!

저는 파사드 클래스 쓰는거 좋아요!
그런데 dto가 controller 패키지에 있으면 controller <-> facade 간의 양방향 의존성이 생기는데, dto를 facade에 넣는 것은 어떤가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

레이어드 아키텍처 때문인데요, 예를들어 컨트롤러가 컨트롤러를 참조하면 이상하듯 서비스가 서비스를 참조해도 괜찮냐? 하시는 분들이 있더라고요. 서비스가 서비스를 호출하면 잠재적으로 재귀호출이 문제가 될 수 있어서요. 또 추후 문제이기도 하지만 서비스 레이어만 있으면 aop를 적용할때도 self invocation 문제가 생겨 결국 서비스가 서비스 호출하는 경우도 생기고요! dto를 facade에 넣는건 아주 좋네요!

다만 지금 12 <- 15 <- 17을 바라보고 있어서, 그럼 17번에서 위치이동 빼먹지 않고 시킬게요!

Copy link
Member

Choose a reason for hiding this comment

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

아하 다른 서비스 참조가 생기지 않게하기 위함이군요!
좋습니다👍👍

private val profileService: ProfileService,
) {

fun saveProfile(profileDto: RegisterProfileRequestDto) {
validateProfile(profileDto)

profileService.saveProfile(
UserProfile(
userId = 1L,
profileSelect = ProfileSelect(
mbti = profileDto.mbti,
keyword = profileDto.keyword,
interest = profileDto.interest,
job = profileDto.job,
smoking = profileDto.smoking,
alcohol = profileDto.alcohol,
religion = profileDto.religion,
region = profileDto.region,
)
)
)
}

private fun validateProfile(profileDto: RegisterProfileRequestDto) {
require(profileDto.keyword.size <= 5) {
"키워드는 5개 이하여야 해요"
}
val interestCount = profileDto.interest.culture.size + profileDto.interest.sports.size
+profileDto.interest.entertainment.size + profileDto.interest.etc.size
require(interestCount <= 5) {
"취미는 5개 이하여야 해요"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.nexters.bottles.profile.repository

import com.nexters.bottles.profile.domain.UserProfile
import org.springframework.data.jpa.repository.JpaRepository

interface UserProfileRepository : JpaRepository<UserProfile, Long>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.nexters.bottles.profile.repository.converter

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinFeature
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.nexters.bottles.profile.domain.ProfileSelect
import javax.persistence.AttributeConverter
import javax.persistence.Converter

@Converter(autoApply = true)
class ProfileSelectConverter : AttributeConverter<ProfileSelect, String> {

private val objectMapper = ObjectMapper().registerModule(
KotlinModule.Builder()
.withReflectionCacheSize(512)
.configure(KotlinFeature.NullToEmptyCollection, false)
.configure(KotlinFeature.NullToEmptyMap, false)
.configure(KotlinFeature.NullIsSameAsDefault, false)
.configure(KotlinFeature.SingletonSupport, DISABLED)
.configure(KotlinFeature.StrictNullChecks, false)
.build()
)

override fun convertToDatabaseColumn(attribute: ProfileSelect?): String {
return try {
objectMapper.writeValueAsString(attribute)
} catch (e: Exception) {
throw RuntimeException("Error converting JSON to String", e)
}
}

override fun convertToEntityAttribute(dbData: String?): ProfileSelect? {
return try {
dbData?.let { objectMapper.readValue(it, ProfileSelect::class.java) }
} catch (e: Exception) {
throw RuntimeException("Error converting String to JSON", e)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.nexters.bottles.profile.service

import com.nexters.bottles.profile.domain.UserProfile
import com.nexters.bottles.profile.repository.UserProfileRepository
import org.springframework.stereotype.Service
import javax.transaction.Transactional

@Service
class ProfileService(
private val profileRepository: UserProfileRepository
) {

@Transactional
fun saveProfile(userProfile: UserProfile): UserProfile {
return profileRepository.save(userProfile)
}
}
1 change: 0 additions & 1 deletion src/main/resources/application.properties

This file was deleted.

27 changes: 27 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
server:
port: 8080

spring:
profiles:
active: local
jpa:
properties:
hibernate:
jdbc:
time_zone: UTC
mvc:
pathmatch:
matching-strategy: ant_path_matcher

---
spring:
profiles: local
datasource:
url: jdbc:mysql://localhost:3306/bottles?useSSL=false
username: root
password: root

logging:
level:
org:
springframework: INFO
7 changes: 7 additions & 0 deletions src/main/resources/sql/table_query.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE user_profile (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
profile_select JSON,
Comment on lines +11 to +14
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ddl 쿼리들을 원래 어떻게 관리하셨나요? 일단 여기에 저장해뒀습니다!

Copy link
Member

Choose a reason for hiding this comment

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

그냥 따로 기록해놨었어요! 여기에 저장해두는거 좋아요!

created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL
);
Loading