이 글은 GDSC Ewha 서버 파트의 2024 솔루션 챌린지를 위해 진행한 Springboot 미니 프로젝트를 설명하는 글입니다. GDSC Ewha의 활동을 더 보고 싶다면 여기를 참고해주세요.
프로젝트의 깃허브 레포지토리 링크
기획안에서 요구사항 뽑아내기
여러분들은 기획팀으로부터 다음과 같은 기획안을 전달받았습니다.
👩💼: 젊은 세대를 위한 SNS인 “인스타키로그램”를 만들려고 해요. 맨 처음에 회원 가입을 할 때 나이, 이름, 국가, 도시, 우편번호를 입력 받게 만들 거에요. 한 회원은 여러 포스트를 만들 수 있어요. 하지만 공동 포스트 게시는 지원하지 않아요. 포스트에는 포스팅된 당시의 시각이 포함되었으면 좋겠어요. 그리고 포스트 제목, 포스트 사진, 사진을 설명하는 글이 필요해요. 포스트에는 좋아요를 눌러줄 수도 있고, 좋아요 수도 확인 가능해요. 좋아요를 누른 회원 목록은 안 보여줘도 돼요. 그리고 회원은 자신의 프로필도 만들 수 있어요. 프로필에는 프로필 사진, 이모지로 표현하는 프로필 상태, 그리고 짧은 자기 소개를 넣을 수 있어요. 프로필 상태는 ‘행복’, ‘슬픔’, ‘즐거움’ 세 가지를 우선 넣을 거고, 나중에 더 추가할 예정이에요. 또한 회원은 친한 친구들끼리 “써클”이라는 것을 만들 수도 있어요. 각 써클에는 이름이랑 써클 설명이 있어요.
기획안 정리하기
이렇게 자연어로 러프하게 설명되어 있는 기획안에서 Class Diagram를 만들기 앞서, 우선 기획안을 다시 정리해보도록 하겠습니다. 참고로, 가능하다면 다음과 같은 형식으로 스스로 기획안을 정리해보길 추천합니다.
- 엔티티와 필드 정의: [객체]에는 A, B, C…와 같은 필드가 있어야 한다.
- 엔티티간 관계 정의: [객체A]는 한/여러 [객체B]에 속하고, [객체B]는 한/여러 [객체A]에 속한다. (또는, [객체A]와 [객체B]는 1:1/1:N/N:1/N:N 관계이다.)
- 엔티티간 참조 방향: [객체A]에서는 [객체B]의 조회가 가능/불가능하고, [객체B]에서는 [객체A]의 조회가 가능/불가능하다. (또는, [객체A]→[객체B] 관계/[객체A]↔[객체B] 관계이다.)
- 기타 로직: ~는 ~해야 한다.
정리된 요구사항
- 회원은 회원 가입을 할 때 이메일, 비밀번호, 나이, 이름, 국가, 도시, 우편번호를 입력 받아야 한다.
- 포스트에는 포스팅 시각, 포스트 제목, 포스트 사진, 포스트 글, 좋아요 수가 있어야 한다.
- 포스트에 좋아요를 누르면 좋아요 수가 +1되고, 취소하면 -1된다.
- 한 회원은 여러 블로그 포스트를 만들 수 있고, 하나의 포스트는 하나의 회원에게만 속한다. (회원:포스트=1:N)
- 회원 프로필에는 프로필 사진, 프로필 상태, 자기소개가 있어야 한다.
- 한 회원은 하나의 프로필만 만들 수 있고, 한 프로필은 하나의 회원에게만 속한다. (회원:프로필=1:1)
- 써클에는 써클 이름, 써클 설명이 있어야 한다.
- 한 회원은 여러 써클에 소속 가능하다. 한 써클에는 여러 회원이 있을 수 있다. (회원:써클=N:M 관계)
요구사항을 바탕으로 엔티티 설계
엔티티 구별 및 필드 넣기(+임베디드 타입 설정)
먼저 각 엔티티를 구분하고, 엔티티에 들어갈 필드 및 타입을 적어줍니다. 위의 요구사항에서 엔티티와 필드만 설명한 요구사항은 다음과 같습니다.
- 회원은 회원 가입을 할 때 이메일, 비밀번호, 나이, 이름, 국가, 도시, 우편번호를 입력 받아야 한다.
- 포스트에는 포스팅 시각, 포스트 제목, 포스트 사진, 포스트 글, 좋아요 수가 있어야 한다.
- 회원 프로필에는 프로필 사진, 프로필 상태, 자기소개가 있어야 한다.
- 써클에는 써클 이름, 써클 설명이 있어야 한다.
이를 변수명 및 Java의 데이터 타입을 명확하게 명시해서 정리해보도록 하겠습니다.
- Member(email: String, password: String, age: int, name: String, country: String, city: String, zipcode: int)
- Post(postDate: String, title: String, thumbnail: String, body: String, likes: int)
- Profile(picture: String, status: String, intro: String)
- Circle(name: String, desc: String)
picture나 thumbnail 같은 이미지들은 전부 GCP Storage에 저장한 후, 각 이미지의 이름으로 조회할 것이기 때문에 엔티티에서는 String 타입으로 설정했습니다.
여기서 잠시, 세 가지를 알고 가보겠습니다.
JPA의 내장 타입, Embeddable에 대해
Embeddable
타입은 하나의 엔터티나 클래스 안에 다른 엔터티나 클래스를 포함시킬 때 사용합니다. 위에서 Member
엔티티는 country: String
, city: String
, zipcode: int
필드를 가지고 있는 것을 확인할 수 있습니다. 이 필드들은 모두 ‘주소’에 속하므로, Address
라는 Embeddable
타입 클래스로 만들어줄 수 있습니다.
반드시 내장 타입을 사용해야 하는 것은 아니지만, 이런 식으로 서로 비슷한 값끼리 묶어주는 것이 객체지향적인 관점에 더 적절하며, 코드의 재사용성을 높여줍니다.
포함시키고 싶은 하위 개념의 클래스에 @Embeddable
어노테이션을 명시해주고, 해당 클래스를 사용할 때 @Embedded
어노테이션을 명시해주면 사용할 수 있습니다.
@Embeddable //내장 타입 명시
@Getter
public class Address {
private String country;
private String city;
private String zipcode;
public Address(String country, String city, String zipcode){
this.country = country;
this.city = city;
this.zipcode = zipcode;
}
}
@Embedded //내장 타입 사용
private Address address;
JPA의 열거형 매핑 타입, EnumType에 대해
EnumType
은 엔터티 클래스에서 열거형 타입의 속성을 매핑할 때 사용되는 타입입니다. 어떤 필드가 열거된 여러 속성 중 하나에 속한다고 할 때, 코드의 유지보수성을 위해서 EnumType
을 쓸 수 있습니다.
public enum Status {
HAPPY, SAD, JOY
}
@Enumerated(EnumType.STRING)
private Status status; //프로필 상태: [HAPPY, SAD, JOY]
여기서 정말 중요한 점은, EnumType
에는 ORDINAL
과 STRING
이 있는데 이 중 꼭 STRING
을 써야 한다는 점입니다. ORDINAL
는 1, 2, …
오 같이 숫자로 들어갑니다. 따라서 중간에 다른 숫자가 생기면 문제가 생깁니다. 예를 들어, 원래 HAPPY
가 1, JOY
가 2였는데, 중간에 다른 상태인 SAD
가 생기면 JOY
가 3이 되어 문제가 생깁니다.
Java의 LocalDateTime에 대해
Java 8부터는 java.time
api가 출시되어 간편하게 현재 시각을 불러올 수 있습니다.
날짜만 불러오는 LocalDate
, 시간만 불러오는 LocalTime
, 그리고 둘 다 불러오는 LocalDateTime
이 있는데, 포스팅에는 날짜와 시간 모두 있는 것이 좋다고 생각해 LocalDateTime
를 사용하도록 하겠습니다. LocalDateTime.now()
를 사용하면 다음과 같은 형식으로 시각을 확인할 수 있습니다.
실제로 사용자가 조회할 때는 아래 코드와 같이 좀 더 사용자 친화적인 포맷으로 보여주려고 합니다. 엔티티에는 LocalDateTime
자체를 우선 저장하겠습니다.
import java.time.LocalDateTime;
//클래스 내부
LocalDateTime currentDateTime = LocalDateTime.now();
//결과 : 2023-12-13T15:01:00.407895708
//조금 더 사용자 친화적인 포맷으로 시간 보여주기
String formattedDataTime = currentDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
//결과 : 2023-12-13 03:05:49
위의 세 가지 공부한 내용을 반영해 다시 엔티티를 설계해보면 다음과 같습니다.
- Member(email: String, password: String, age: int, name: String, address: Address)
- Embeddable Address(country: String, city: String, zipcode: int)
- Post(postDate: LocalDateTime, title: String, thumbnail: String, body: String, likes: int)
- Profile(picture: String, status: Status, intro: String)
- enum Status(HAPPY, SAD, JOY)
- Circle(name: String, desc: String)
이제 엔티티가 만들어졌으니, 엔티티간 연간관계를 설정하겠습니다.
요구사항을 바탕으로 연관관계 설계(연관관계 주인)
저희는 RDB를 만들고 있으므로 엔티티간 연관관계를 설정해야 합니다. Spring JPA에서는 일대일/일대다/다대다 관계와, 단방향/양방향 관계를 맺을 수 있습니다. 각 관계가 무엇인지 알아보기 전에 먼저 앞선 요구사항 중 연관관계와 관련된 요구사항을 뽑아보겠습니다.
- 한 회원은 여러 블로그 포스트를 만들 수 있고, 하나의 포스트는 하나의 회원에게만 속한다. (회원:포스트=1:N)
- 한 회원은 하나의 프로필만 만들 수 있고, 한 프로필은 하나의 회원에게만 속한다. (회원:프로필=1:1)
- 한 회원은 여러 써클에 소속 가능하다. 한 써클에는 여러 회원이 있을 수 있다. (회원:써클=N:M 관계)
저희의 요구사항에는 일대일/일대다/다대다 관계가 전부 있고, 방향은 양방향 관계만 있습니다. 각 관계에 필요한 코드를 살펴보겠습니다.
일대일 양방향 관계, @OneToOne
일대일 양방향 관계에서는 변경이 많이 일어나는 곳/조회가 많이 일어나는 곳에 외래키를 놓습니다. 이 상황에서는 profile에 넣겠습니다.
@OneToOne
@JoinColumn(name = "member_id") //외래키
private Member member;
@OneToOne(mappedBy = "member") //외래키가 없으므로, Profile 클래스의 member 필드로 역참조
private Profile profile;
일대다 양방향 관계, @OneToMany와 @ManyToOne
다대일 관계에서는 반드시 many쪽에 외래키를 둡니다. 즉, many쪽이 무조건 연관관계의 주인이 됩니다.
@ManyToOne
@JoinColumn(name = "member_id") //외래키
private Member member
@OneToMany(mappedBy = "member") //외래키가 없으므로, Post 클래스의 member 필드로 역참조
private List<Post> posts = new ArrayList<>(); // Post 쪽이 다수이므로, List로 필드 만들기
다대다 양방향 관계, @ManyToMany
다대다 관계는 실무에서 절대로 사용하지 않습니다.
이번 요구사항에서 볼 수 있듯이, 현실 세상에서는 회원과 써클의 관계(한 회원은 여러 써클에 소속 가능하다. 한 써클에는 여러 회원이 있을 수 있다. )처럼 다대다 관계가 있습니다. 그러나 다대다 관계를 Spring에서 만들면 보이지 않는 중간 테이블이 자동으로 생성되며, 이 중간 테이블을 수정하는 것도 불가능합니다.
따라서 직접 중간 테이블을 만들어서(예를 들어, ‘회원써클’ 테이블) ‘회원 N:M 써클’이었던 관계를 쪼개서 ‘회원 1: N 회원의 써클 N:1 써클’의 관계로 만들어주어야 합니다. 즉, “각 회원은 여러 회원써클을 가질 수 있다. 한 써클은 여러 회원써클에 속할 수 있다.”로 요구사항을 재해석합니다.
그렇게 되면 위의 일대다 양방향 관계와 같은 방식으로 연관관계를 만들어줄 수 있습니다.
Class Diagram 설계
지금까지 결정된 엔티티 스펙을 바탕으로 클래스 다이어그램을 그려보겠습니다. 클래스 다이어그램에는 각 클래스의 필드, PK(기본키), FK(외래키), 각 클래스의 관계를 모두 표현합니다.
클래스 다이어그램은 다음 사이트에서 쉽게 그릴 수 있습니다.
다음 주차
다음 주차에는 이렇게 만들어진 클래스 다이어그램을 바탕으로 실제 엔티티를 설계하고, 이를 GCP Cloud SQL과 연결해보겠습니다.
'🗄️Backend > [GDSC] SpringBoot 프로젝트' 카테고리의 다른 글
Spring Boot 단기 프로젝트: 6. Make API(Auth, Circle) (0) | 2024.02.08 |
---|---|
Spring Boot 단기 프로젝트: 5. Make API(Profile, Posts) (0) | 2024.02.08 |
Spring Boot 단기 프로젝트: 4. Connect to GCP Cloud Storage & make Dto (0) | 2024.02.08 |
Spring Boot 단기 프로젝트: 3. RestController & Swagger-UI & Postman (0) | 2024.02.08 |
Spring Boot 단기 프로젝트: 2. Build Entity & Connect to GCP Cloud SQL (0) | 2024.02.08 |