-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat: News 엔티티 추가 (#2) #6
Conversation
- addContent 호출 시 Content의 News가 null이면 안 된다. - 따라서 News.addContent() 메서드는 일반적으로 사용할 수 없다. - content.initialNews() 메서드로만 News.addContent() 메서드를 호출 할 수 있다.
- News.addContent() 메서드로 Content 영속하도록 변경
@MappedSuperclass | ||
abstract class PrimaryKeyEntity : Persistable<UUID> { | ||
|
||
@Id | ||
@Column(columnDefinition = "uuid") | ||
private val id: UUID = UlidCreator.getMonotonicUlid().toUuid() | ||
|
||
@Transient | ||
private var _isNew = true | ||
|
||
override fun getId(): UUID = id | ||
|
||
override fun isNew(): Boolean = _isNew | ||
|
||
override fun equals(other: Any?): Boolean { | ||
if (other == null) { | ||
return false | ||
} | ||
if (other !is HibernateProxy && this::class != other::class) { | ||
return false | ||
} | ||
return id == getIdentifier(other) | ||
} | ||
|
||
private fun getIdentifier(obj: Any): Any { | ||
return when (obj) { | ||
is HibernateProxy -> obj.hibernateLazyInitializer.identifier | ||
else -> (obj as PrimaryKeyEntity).id | ||
} | ||
} | ||
|
||
override fun hashCode(): Int = Objects.hashCode(id) | ||
|
||
@PostPersist | ||
@PostLoad | ||
protected fun load() { | ||
_isNew = false | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 클래스는 식별자를 UUID로 제공하기 위함인데 링크 참고하시면 이해되실 겁니다!
describe("convertToDatabaseColumn") { | ||
context("요소가 한 개 이면") { | ||
val attribute = EnumSet.of(Language.ENGLISH) | ||
|
||
it(",가 없는 문자열을 반환한다.") { | ||
enumSetLanguageConverter.convertToDatabaseColumn(attribute) shouldBe "ENGLISH" | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fun from(language: Language, news: News): Content = when (language) { | ||
Language.ENGLISH -> Content( | ||
newsInformation = NewsInformation( | ||
title = "Star Citizen Live", | ||
excerpt = "You asked. We're answering! Join us today for a live Q&A show with the Vehicle Gameplay team." | ||
), | ||
language = language, | ||
content = "blah blah", | ||
news = news, | ||
) | ||
|
||
Language.KOREAN -> Content( | ||
newsInformation = NewsInformation( | ||
title = "스타 시티즌 뉴스", | ||
excerpt = "물어보셨죠? 저희가 답해드리겠습니다! 지금 바로 차량 게임플레이 팀과 함께하는 라이브 Q&A 쇼에 참여하세요." | ||
), | ||
language = language, | ||
content = "어쩌구 저쩌구", | ||
news = news, | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코틀린 문법에 아직 익숙하지는 않지만, 최대한 컨벤션을 맞추려고 하고 있습니다.
해당 링크 참조하시면 좋을 것 같네요.
- lazy 로딩 이슈 해결을 위함
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생 많으셨습니다!!!!!
말씀하신 사항 대로 따라가겠습니다!!!
코멘트 남겨드린 부분 한번 검토해주시면 감사드립니다!
newsType: NewsType, | ||
originId: Long, | ||
publishedAt: LocalDateTime, | ||
) : PrimaryKeyEntity() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
원문의 URL 를 담을 수 있는 컬럼이 있다면 좋을 것 같습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
원문 링크를 함께 제공하여 다음과 같은 효과를 얻을 수 있습니다.
-
원문 링크를 함께 게시하여 출처를 명시할 수 있습니다.
-
모든 소식의 내용을 모두 번역하여 보여주면 좋겠지만 Iea 2953 와 같은 대규모 프로모션 컨텐츠는 개발에 많은 시간이 소요될 것 입니다. 이런 경우 요약본만 번역하고 원문 링크를 게시하여 원문을 보도록 안내할 수 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
한번 검토해주시면 감사드립니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
originUrl
이라는 컬럼을 추가하면 되겠네요!
class News( | ||
newsType: NewsType, | ||
originId: Long, | ||
publishedAt: LocalDateTime, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
publishedAt
의 타입을 Instant 을 사용하는 것을 제안합니다.
- 서버 시간과 상관없이 UTC 기준으로 시간이 생성되며, 텍스트에서 parse 할 때 시간대를 설정해줘야 하므로 DB 에 저장된 날짜와 시간에 대한 시간대가 뒤섞이는 것을 방지할 수 있을 것으로 사료됩니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instant 보다는, 더 다양한 사용을 위해 ZonedDateTime이 좋을 것 같네요!
{
"id": 19478,
"publish_start": "2023-09-15 19:00:04",
"time_created": "2023-09-15 18:32:53",
"excerpt": "You asked. We're answering! Join us today for a live Q&A show with the Vehicle Gameplay team.",
"title": "Star Citizen Live ",
"url": "https://robertsspaceindustries.com/comm-link/transmission/19478-Star-Citizen-Live"
}
- RSI에서 제공하는 API 포맷인데, 여기선 시간대가 보이지 않는데 이 경우에는 어떻게 처리하는게 좋을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일단 GMT 나 UTC 로 지정해주시면 됩니다!
관련 이슈
close #2
PR 세부 내용
News
,Content
엔티티를 정의하고 추가했습니다.테이블 구조는 다음과 같습니다.
News
News 그 자체인 엔티티 입니다.
Content와 1:N 관계를 가지고 있습니다.
NewsType
,originId
,publishedAt
,newsInformation
,mutableSupportLanguages
,contents
를 필드로 가지고 있습니다.NewsType
열거형으로,
PATCH_NOTE
,NEWS
를 가지고 있습니다.추후 개발자 트래커 혹은 그 외의 형식이 추가될 가능성을 염두했습니다.
originId
다음은 뉴스 API의 JSON 형식입니다.
여기서,
id
필드가 정수로 된 고유한 식별자라는 것을 알 수 있습니다.만약, 저희만의 뉴스 혹은 그 외 서비스에서 제공하는 뉴스라면 id가 중복될 수 있으므로
Provider
를 추가하여 중복된 식별자를 가지지 않도록 해야할 것 같습니다.우선 지금은 RSI의 정보만 받기 때문에 똑같이 Long 타입으로 두었습니다.
publishedAt
위의 JSON 형식에서
publish_start
필드를 나타냅니다.newsInformation
밑에서
Content
를 설명할 때 자세히 얘기하겠습니다.News에도 해당 필드를 가지게 한 이유는 역정규화 관점에서 생각하시면 될 것 같습니다.
또한 여러 개의 Content를 소유할 수 있으므로 여러 개의 Content를 대표하는 정보라고 보시면 될 것 같습니다.
(영어와 한글로 된 Content를 소유했을 때 어떤 것을 미리보기에서 보여줄 지)
지금은 처음 등록된 Content의 newsInformation을 설정되도록 했습니다.
mutableSupportLanguages
Language
열거형을 가지고 있는 EnumSet 입니다.News를 보여줄 때 어떤 언어를 제공하는지 알려주는 필드입니다.
참고로
supportLanguages
필드가 있는데, 둘의 차이는 방어적 복사본을 제공하냐의 차이라고 보면 될 것 같습니다.(실제 내부에서 사용하는 비즈니스 로직은
mutableSupportLanguages
를 사용해야 합니다!)이렇게 사용한 이유는
EnumSet
을 사용하기 때문입니다..mutableSupportLanguages을 get으로 원본을 제공했을 때 외부에서 변경이 있으면 사이드 이펙트가 발생할 수 있습니다.
따라서 방어적 복사본을 제공하려고
get() = mutableSupportLanguages.toHashSet()
와 같이 정의했으나...mutableSupportLanguages가 EnumSet 이므로, 타입 매칭이 되지 않아 컴파일 에러가 발생하더군요. 😂
따라서 mutableSupportLanguages 타입을 Set으로 변경하면 타입 매칭은 되지만,
add()
메서드를 사용할 수 없기 때문에 또한 애로사항이 발생합니다.결국 그렇게 해서
supportLanguages
를 정의하여 사용했습니다.contents
밑에 설명드릴
Content
엔티티를 리스트로 가지고 있는 필드입니다.외부에는 제공하지 않고 내부에서
Content
엔티티를 영속하기 위한 필드입니다.해당 필드를 사용하여
addContent()
메서드를 제공하고 있습니다.Content
News의 상세 내용을 가지는 엔티티 입니다.
News와 N:1 관계를 가지고 있습니다.
sequence
,news
,newsInformation
,language
,content
를 필드로 가지고 있습니다.sequence
식별자로 사용되는 필드이고, DB의 자동 증가 컬럼에 의존합니다.
id라는 이름을 사용하지 않고 sequence라는 이름을 사용한 이유는 Content의 식별자를 식별자로 사용하지 않게 의도하기 위해서 입니다.
Content는 News의 생명 주기를 따릅니다. (생성 시점은 다르지만, Content가 생성되려면 News가 필요합니다.)
또한 Content 조회 시 News를 먼저 조회해야 합니다. (어떤 언어의 Content가 제공되는지 알아야 하므로)
sequence를 PK로 사용하지 않고 News의 PK를 Content의 PK로 사용하면 될 것 같지만, N:1 관계이므로 PK의 중복이 발생합니다.
따라서 위와 같은 이유로 식별자를 사용했다고 이해하시면 될 것 같습니다.
Content의 조회는 News의 식별자와 Language를 통하면 될 것 같습니다.
news
News의 참조 관계를 설정하기 위한 필드입니다.
N:1 관계로 매핑되어 있습니다.
newsInformation
title
,excerpt
를 가지고 있는 값 타입 입니다.값 타입이기 때문에 불변하며, 가지고 있는 필드는 nullable 합니다.
왜냐하면
excerpt
필드가 패치 노트에서는 null 값으로 주어지더군요... 😂글을 작성할 때 생략할 수 있는 부분이므로 null 값을 사용했습니다.
title 또한 nullable 합니다.
글에서 title은 필수적인 값이지만, null을 허용하지 않는다면 빈 문자열을 두기 애매하여 nullable 하게 하였습니다.
null을 허용하지 않게 하고, 빈 문자열 대신 "제목이 없습니다." 라는 문자열을 허용하게 할 지 고려가 필요합니다.
language
Content가 어떤 언어로 작성되었는지 알려주는 필드입니다.
추후 조회 기능 제공 시 Language와 News의 식별자로 조회할 때 사용할 목적입니다.
Content
엔티티를 영속하려면 무조건News
의addContent()
메서드를 사용해야 합니다.이것은 검증 규칙과 비즈니스 로직을 강제하기 위함입니다. (중복된 언어, supportLanguage 추가 등)
비즈니스 로직에 대한 명세는 테스트 코드 확인하시면 될 것 같습니다.
질문 사항 있으시면 리뷰로 남겨주세요!