Skip to content

Commit

Permalink
[Feat/#68] 예외 및 응답코드 표준화 & 적용 (#167)
Browse files Browse the repository at this point in the history
* fix: 중복 선언된 @EnableWebMVC 삭제

* refactor: MDCFilter MVC 필터로 수정

* refactor: request 타입 HttpServletRequest로 수정

* refactor: 예외 공통 로깅 적용

* feat: message 프로퍼티 추가

* feat: MessageSourceAccessor 구현

* feat: MessageSourceConfig 설정

* feat: 공통 예외 생성

* feat: 구독 예외 생성

* feat: few 예외 헨들러 선언

* refactor: IllegalArgumentException 예외 메시지 구체화

* refactor: dao에서 예외가 아닌 XXX? 반환하도록 수정

* refactor: usecase에 few 예외 적용

* refactor: subscribe.state.subscribed 응답값 요구 사항 반영

* refactor: setUp에서 ARTICLE_MST 테이블 삭제 추가

* fix: 오타 수정 unsbuscribe -> unsubscribe

* refactor: 일반 예외 예외 메시지 구체화

* feat: subscribe.state.subscribed 추가

* refactor: few 예외 적용

* test: c827ff21ebb1539ffa5618d2147dfb2513503957에서의 selectWriter 응답 변경에 따른 수정
  • Loading branch information
belljun3395 authored Jul 8, 2024
1 parent dab70a7 commit 340c402
Show file tree
Hide file tree
Showing 46 changed files with 353 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class MemberDao(
private val dslContext: DSLContext
) {

fun selectWriter(query: SelectWriterQuery): WriterRecord {
fun selectWriter(query: SelectWriterQuery): WriterRecord? {
val writerId = query.writerId

return dslContext.select(
Expand All @@ -30,7 +30,6 @@ class MemberDao(
.and(Member.MEMBER.TYPE_CD.eq(MemberType.WRITER.code))
.and(Member.MEMBER.DELETED_AT.isNull)
.fetchOneInto(WriterRecord::class.java)
?: throw IllegalArgumentException("cannot find writer record by writerId: $writerId")
}

fun selectWriters(query: SelectWritersQuery): List<WriterRecord> {
Expand Down Expand Up @@ -59,14 +58,13 @@ class MemberDao(
.fetchOneInto(MemberIdRecord::class.java)
}

fun insertMember(command: InsertMemberCommand): Long {
fun insertMember(command: InsertMemberCommand): Long? {
val result = dslContext.insertInto(Member.MEMBER)
.set(Member.MEMBER.EMAIL, command.email)
.set(Member.MEMBER.TYPE_CD, command.memberType.code)
.returning(Member.MEMBER.ID)
.fetchOne()

return result?.getValue(Member.MEMBER.ID)
?: throw RuntimeException("Member with email ${command.email} insertion fail") // TODO: 에러 표준화
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class SubmitHistoryDao(
private val dslContext: DSLContext
) {

fun insertSubmitHistory(command: InsertSubmitHistoryCommand): Long {
fun insertSubmitHistory(command: InsertSubmitHistoryCommand): Long? {
val result = dslContext.insertInto(SUBMIT_HISTORY)
.set(SUBMIT_HISTORY.PROBLEM_ID, command.problemId)
.set(SUBMIT_HISTORY.MEMBER_ID, command.memberId)
Expand All @@ -20,6 +20,5 @@ class SubmitHistoryDao(
.fetchOne()

return result?.getValue(SUBMIT_HISTORY.ID)
?: throw RuntimeException("Submit History with ID ${command.problemId} insertion fail") // TODO: 에러 표준화
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class MemberDaoTest : JooqTestSpec() {
memberDao.selectWriter(it)
}

assertNotNull(result)
assertNotNull(result!!)
assertEquals(2L, result.writerId)
assertEquals("few2", result.name)
assertEquals(URL("http://localhost:8080/writers/url2"), result.url)
Expand Down
33 changes: 33 additions & 0 deletions api/src/main/kotlin/com/few/api/config/MessageSourceConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.few.api.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.support.ReloadableResourceBundleMessageSource
import java.nio.charset.StandardCharsets

@Configuration
class MessageSourceConfig {
@Bean
fun messageSource(): ReloadableResourceBundleMessageSource {
val messageSource = ReloadableResourceBundleMessageSource()
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name())
for (path in MESSAGE_SOURCE_CLASSPATH_LIST) {
messageSource.addBasenames(path)
}
return messageSource
}

companion object {
private val MESSAGE_SOURCE_CLASSPATH_LIST = listOf(
"classpath:messages/article",
"classpath:messages/document",
"classpath:messages/external",
"classpath:messages/image",
"classpath:messages/member",
"classpath:messages/problem",
"classpath:messages/submit",
"classpath:messages/subscribe",
"classpath:messages/workbook"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.few.api.domain.admin.document.service

import com.few.api.domain.admin.document.service.dto.GetUrlQuery
import com.few.api.domain.admin.document.service.dto.getPreSignedUrlServiceKey
import com.few.api.exception.common.ExternalIntegrationException
import com.few.storage.GetPreSignedObjectUrlService
import com.few.storage.image.service.support.CdnProperty
import org.springframework.context.annotation.Profile
Expand All @@ -23,13 +24,13 @@ class GetLocalUrlService(
key.lowercase(Locale.getDefault())
.contains(query.getPreSignedUrlServiceKey())
}.findAny().let {
if (it.isEmpty) throw IllegalStateException("Failed to find service")
if (it.isEmpty) throw IllegalArgumentException("Cannot find service for ${query.getPreSignedUrlServiceKey()}")
services[it.get()]!!
}

return service.execute(query.`object`)?.let {
URL(it)
} ?: throw IllegalStateException("Failed to get image url")
} ?: throw ExternalIntegrationException("external.presignedfail.url")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.few.api.domain.admin.document.dto.AddArticleUseCaseOut
import com.few.api.domain.admin.document.service.GetUrlService
import com.few.api.domain.admin.document.service.dto.GetUrlQuery
import com.few.api.domain.admin.document.utils.ObjectPathGenerator
import com.few.api.exception.common.ExternalIntegrationException
import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.article.ArticleDao
import com.few.api.repo.dao.article.command.InsertFullArticleRecordCommand
import com.few.api.repo.dao.document.DocumentDao
Expand Down Expand Up @@ -38,7 +40,7 @@ class AddArticleUseCase(
/** select writerId */
val writerId = SelectMemberByEmailQuery(useCaseIn.writerEmail).let {
memberDao.selectMemberByEmail(it)
} ?: throw RuntimeException("writer not found")
} ?: throw NotFoundException("member.notfound.id")

/**
* - content type: "md"
Expand Down Expand Up @@ -72,15 +74,15 @@ class AddArticleUseCase(
}
url
}
} ?: throw IllegalStateException("Failed to put document")
} ?: throw ExternalIntegrationException("external.putfail.docummet")

htmlSource
}
useCaseIn.contentType.lowercase(Locale.getDefault()) == "html" -> {
useCaseIn.contentSource
}
else -> {
throw IllegalArgumentException("content type is not supported")
throw IllegalArgumentException("Unsupported content type: ${useCaseIn.contentType}")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.few.api.domain.admin.document.usecase

import com.few.api.domain.admin.document.dto.AddWorkbookUseCaseIn
import com.few.api.domain.admin.document.dto.AddWorkbookUseCaseOut
import com.few.api.exception.common.InsertException
import com.few.api.repo.dao.workbook.WorkbookDao
import com.few.api.repo.dao.workbook.command.InsertWorkBookCommand
import org.springframework.stereotype.Component
Expand All @@ -20,7 +21,7 @@ class AddWorkbookUseCase(
description = useCaseIn.description
).let {
workbookDao.insertWorkBook(it)
} ?: throw RuntimeException("Failed to insert workbook")
} ?: throw InsertException("workbook.insertfail.record")

return AddWorkbookUseCaseOut(workbookId)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.few.api.domain.admin.document.dto.ConvertContentUseCaseOut
import com.few.api.domain.admin.document.service.GetUrlService
import com.few.api.domain.admin.document.service.dto.GetUrlQuery
import com.few.api.domain.admin.document.utils.ObjectPathGenerator
import com.few.api.exception.common.ExternalIntegrationException
import com.few.api.exception.common.InsertException

import com.few.api.repo.dao.document.DocumentDao
import com.few.api.repo.dao.document.command.InsertDocumentIfoCommand
Expand Down Expand Up @@ -42,11 +44,11 @@ class ConvertContentUseCase(
path = documentName,
url = url
).let { command ->
documentDao.insertDocumentIfo(command)
documentDao.insertDocumentIfo(command) ?: throw InsertException("document.insertfail.record")
}
url
}
} ?: throw IllegalStateException("Failed to put document")
} ?: throw ExternalIntegrationException("external.document.presignedfail")

val html =
convertDocumentService.mdToHtml(document.readBytes().toString(Charsets.UTF_8))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.few.api.domain.admin.document.dto.PutImageUseCaseOut
import com.few.api.domain.admin.document.service.GetUrlService
import com.few.api.domain.admin.document.service.dto.GetUrlQuery
import com.few.api.domain.admin.document.utils.ObjectPathGenerator
import com.few.api.exception.common.ExternalIntegrationException
import com.few.api.exception.common.InsertException
import com.few.api.repo.dao.image.ImageDao
import com.few.api.repo.dao.image.command.InsertImageIfoCommand
import com.few.storage.image.service.PutImageService
Expand Down Expand Up @@ -37,11 +39,11 @@ class PutImageUseCase(
getUrlService.execute(query)
}.let { url ->
InsertImageIfoCommand(source, url).let { command ->
imageDao.insertImageIfo(command) ?: throw IllegalStateException("Failed to save image info")
imageDao.insertImageIfo(command) ?: throw InsertException("image.insertfail.record")
}
return@let url
}
}
} ?: throw ExternalIntegrationException("external.presignedfail.image")

return PutImageUseCaseOut(url!!)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.few.api.domain.article.service

import com.few.api.domain.article.service.dto.BrowseArticleProblemIdsQuery
import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.problem.ProblemDao
import com.few.api.repo.dao.problem.query.SelectProblemsByArticleIdQuery
import com.few.api.repo.dao.problem.record.ProblemIdsRecord
Expand All @@ -13,7 +14,7 @@ class BrowseArticleProblemsService(

fun execute(query: BrowseArticleProblemIdsQuery): ProblemIdsRecord {
SelectProblemsByArticleIdQuery(query.articleId).let { query: SelectProblemsByArticleIdQuery ->
return problemDao.selectProblemsByArticleId(query) ?: throw IllegalArgumentException("cannot find problems by articleId: ${query.articleId}") // todo 에러 표준화
return problemDao.selectProblemsByArticleId(query) ?: throw NotFoundException("problem.notfound.articleId")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ReadArticleWriterRecordService(
) {

@Transactional(readOnly = true)
fun execute(query: ReadWriterRecordQuery): WriterRecord {
fun execute(query: ReadWriterRecordQuery): WriterRecord? {
SelectWriterQuery(query.writerId).let { query: SelectWriterQuery ->
return memberDao.selectWriter(query)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.few.api.domain.article.service.BrowseArticleProblemsService
import com.few.api.domain.article.service.ReadArticleWriterRecordService
import com.few.api.domain.article.service.dto.BrowseArticleProblemIdsQuery
import com.few.api.domain.article.service.dto.ReadWriterRecordQuery
import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.article.ArticleDao
import com.few.api.repo.dao.article.query.SelectArticleRecordQuery
import com.few.data.common.code.CategoryType
Expand All @@ -24,10 +25,10 @@ class ReadArticleUseCase(
fun execute(useCaseIn: ReadArticleUseCaseIn): ReadArticleUseCaseOut {
val articleRecord = SelectArticleRecordQuery(useCaseIn.articleId).let { query: SelectArticleRecordQuery ->
articleDao.selectArticleRecord(query)
} ?: throw IllegalArgumentException("cannot find article record by articleId: $useCaseIn.articleId")
} ?: throw NotFoundException("article.notfound.id")

val writerRecord = ReadWriterRecordQuery(articleRecord.writerId).let { query: ReadWriterRecordQuery ->
readArticleWriterRecordService.execute(query)
readArticleWriterRecordService.execute(query) ?: throw NotFoundException("writer.notfound.id")
}

val problemIds = BrowseArticleProblemIdsQuery(articleRecord.articleId).let { query: BrowseArticleProblemIdsQuery ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.few.api.domain.problem.usecase

import com.few.api.domain.problem.usecase.`in`.BrowseProblemsUseCaseIn
import com.few.api.domain.problem.usecase.out.BrowseProblemsUseCaseOut
import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.problem.ProblemDao
import com.few.api.repo.dao.problem.query.SelectProblemsByArticleIdQuery
import org.springframework.stereotype.Component
Expand All @@ -13,7 +14,7 @@ class BrowseProblemsUseCase(

fun execute(useCaseIn: BrowseProblemsUseCaseIn): BrowseProblemsUseCaseOut {
SelectProblemsByArticleIdQuery(useCaseIn.articleId).let { query: SelectProblemsByArticleIdQuery ->
problemDao.selectProblemsByArticleId(query) ?: throw IllegalArgumentException("cannot find problems by articleId: ${query.articleId}") // todo 에러 표준화
problemDao.selectProblemsByArticleId(query) ?: throw NotFoundException("problem.notfound.articleId")
}.let {
return BrowseProblemsUseCaseOut(it.problemIds)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.few.api.repo.dao.problem.command.InsertSubmitHistoryCommand
import com.few.api.repo.dao.problem.query.SelectProblemAnswerQuery
import com.few.api.domain.problem.usecase.`in`.CheckProblemUseCaseIn
import com.few.api.domain.problem.usecase.out.CheckProblemUseCaseOut
import com.few.api.exception.common.InsertException
import com.few.api.exception.common.NotFoundException
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

Expand All @@ -20,12 +22,13 @@ class CheckProblemUseCase(
val problemId = useCaseIn.problemId
val submitAns = useCaseIn.sub

val record = problemDao.selectProblemAnswer(SelectProblemAnswerQuery(problemId)) ?: throw RuntimeException("Problem Answer with ID $problemId not found") // TODO: 에러 표준화
val record = problemDao.selectProblemAnswer(SelectProblemAnswerQuery(problemId)) ?: throw NotFoundException("problem.notfound.id")
val isSolved = record.answer == submitAns

val submitHistoryId = submitHistoryDao.insertSubmitHistory(
InsertSubmitHistoryCommand(problemId, 1L, submitAns, isSolved)
) // not used 'submitHistoryId'
) ?: throw InsertException("submit.insertfail.record")
// not used 'submitHistoryId'

return CheckProblemUseCaseOut(
explanation = record.explanation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.few.api.repo.dao.problem.query.SelectProblemQuery
import com.few.api.domain.problem.usecase.`in`.ReadProblemUseCaseIn
import com.few.api.domain.problem.usecase.out.ReadProblemContentsUseCaseOutDetail
import com.few.api.domain.problem.usecase.out.ReadProblemUseCaseOut
import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.problem.support.ContentsJsonMapper
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
Expand All @@ -20,7 +21,7 @@ class ReadProblemUseCase(
val problemId = useCaseIn.problemId

val record = problemDao.selectProblemContents(SelectProblemQuery(problemId))
?: throw RuntimeException("Problem with ID $problemId not found") // TODO: 에러 표준화
?: throw NotFoundException("problem.notfound.id")

val contents: List<ReadProblemContentsUseCaseOutDetail> = contentsJsonMapper.toObject(record.contents).contents.map {
ReadProblemContentsUseCaseOutDetail(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.few.api.client.subscription.dto.WorkbookSubscriptionArgs
import com.few.api.domain.subscription.event.dto.WorkbookSubscriptionEvent
import com.few.api.domain.subscription.service.WorkbookService
import com.few.api.domain.subscription.service.dto.ReadWorkbookTitleDto
import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.subscription.SubscriptionDao
import org.springframework.context.event.EventListener
import org.springframework.scheduling.annotation.Async
Expand All @@ -22,7 +23,7 @@ class WorkbookSubscriptionEventListener(
fun handleWorkbookSubscriptionEvent(event: WorkbookSubscriptionEvent) {
val title = ReadWorkbookTitleDto(event.workbookId).let { dto ->
workbookService.readWorkbookTitle(dto)
?: throw RuntimeException("Workbook not found")
?: throw NotFoundException("workbook.notfound.id")
}
subscriptionDao.countAllSubscriptionStatus().let { record ->
WorkbookSubscriptionArgs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.few.api.domain.subscription.service

import com.few.api.domain.subscription.service.dto.InsertMemberDto
import com.few.api.domain.subscription.service.dto.ReadMemberIdDto
import com.few.api.exception.common.InsertException
import com.few.api.repo.dao.member.MemberDao
import com.few.api.repo.dao.member.command.InsertMemberCommand
import com.few.api.repo.dao.member.query.SelectMemberByEmailQuery
Expand All @@ -18,6 +19,6 @@ class MemberService(
}

fun insertMember(dto: InsertMemberDto): Long {
return memberDao.insertMember(InsertMemberCommand(dto.email, dto.memberType))
return memberDao.insertMember(InsertMemberCommand(dto.email, dto.memberType)) ?: throw InsertException("member.insertfail.record")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import com.few.api.repo.dao.subscription.SubscriptionDao
import com.few.api.repo.dao.subscription.command.InsertWorkbookSubscriptionCommand
import com.few.api.repo.dao.subscription.query.SelectAllWorkbookSubscriptionStatusQueryNotConsiderDeletedAt
import com.few.api.domain.subscription.usecase.`in`.SubscribeWorkbookUseCaseIn
import com.few.api.exception.common.NotFoundException
import com.few.api.exception.subscribe.SubscribeIllegalArgumentException
import com.few.api.repo.dao.subscription.query.CountWorkbookMappedArticlesQuery
import com.few.data.common.code.MemberType
import org.springframework.context.ApplicationEventPublisher
Expand Down Expand Up @@ -47,17 +49,17 @@ class SubscribeWorkbookUseCase(

/** 이미 구독한 히스토리가 있고 구독이 취소된 경우 */
!subscriptionStatus.isActiveSub -> {
val lastDay = subscriptionDao.countWorkbookMappedArticles(CountWorkbookMappedArticlesQuery(subTargetWorkbookId)) ?: throw RuntimeException("워크북 매핑된 아티클을 조회할 수 없습니다.")
val lastDay = subscriptionDao.countWorkbookMappedArticles(CountWorkbookMappedArticlesQuery(subTargetWorkbookId)) ?: throw NotFoundException("workbook.notfound.id")
if (lastDay <= subscriptionStatus.day) {
throw RuntimeException("이미 학습을 완료한 워크북입니다.")
throw SubscribeIllegalArgumentException("subscribe.state.end")
}
/** 재구독 */
subscriptionDao.reSubscribeWorkbookSubscription(command)
}

/** 이미 구독한 히스토리가 있고 구독이 취소되지 않은 경우 */
else -> {
throw RuntimeException("이미 구독한 워크북입니다.")
throw SubscribeIllegalArgumentException("subscribe.state.subscribed")
}
}
applicationEventPublisher.publishEvent(WorkbookSubscriptionEvent(workbookId = subTargetWorkbookId))
Expand Down
Loading

0 comments on commit 340c402

Please sign in to comment.