이 글은 FastCampus에서 진행하는 "백엔드 개발자를 위한 한 번에 끝내는 대용량 데이터 & 트래픽 처리 초격차 패키지 Online" 강의를 공부하고 기록하기 위함입니다. 저작권 등이 문제가 생긴다면 바로 정리하도록 하겠습니다.
자 이제 SNS 서비스를 만들어보는 프로젝트를 본격적으로 시작하겠습니다.
프로젝트 환경은 지난번 실습환경 구축 포스팅을 참조해주시면 좋을것 같아요
우선 이번 포스팅에서는 회원정보를 등록하는 서비스를 제작하고자 합니다. 신규 회원 등록에 대한 요구사항은 아래와 같아요
회원정보 관리
- 이메일 , 닉네임, 생년월일을 입력받아 저장한다
- 닉네임의 10자를 초과할 수 없다
- 회원은 닉네임을 변경할 수 있다( + 회원의 닉네임 변경이력을 조회할 수 있어야 한다)
첫번째, 두번째 조건을 이용하여 우선 구현을 해볼게요. domain 패키지 아래 아래와 같은 경로를 생성하겠습니다.
member라는 package를 생성하고 하위에 dto, entity,repository, service package를 추가로 생성했어요. 각 Package의 역할을 간단히 설명하자면
dto : Data Transfer Object의 약자로 데이터를 전송할 때 dto라는 객체에 담아 보내기 위해 사용해요
entity : 데이터베이스에 쓰일 Column(Field) 정보와 여러 Entity간 연관관계를 정의해요
repository : Entity에 의해 정의된 데이터베이스 Table에 접근하여 CRUD을 수행하는 인터페이스를 의미해요
service : client의 요청에 의한 비즈니스 로직을 수행하는 걸 담당해요
너무 간략하게 적어서 의미전달이 정확히 됐을지 모르겠지만 부족한 부분은 아래 예제 코드를 통해 이해하실 수 있을것 같아요
우선 entity class를 정의해볼게요. entity package하위에 Member class를 생성했습니다.
package com.example.fastcampusmysql.domain.member.entity;
import lombok.Builder;
import lombok.Getter;
import org.springframework.util.Assert;
import java.time.LocalDateTime;
import java.util.Objects;
@Getter
public class Member {
final private Long id;
private String nickname; // 추후 변경될 수 있으니 final 생략
final private String email;
final private String birthday;
final private LocalDateTime createdAt; // 회원등록 시간을 확인하기 위해 추가
final private static Long NAME_MAX_LENGTH = 10L;
@Builder // Builder 패턴을 활용하여 객체를 생성할 수 있도록 Anotation 추가
public Member(Long id, String nickname, String email, String birthday, LocalDateTime createdAt) {
this.id = id;
this.email = Objects.requireNonNull(email);
this.birthday = Objects.requireNonNull(birthday);
validateNickName(nickname);
this.nickname = Objects.requireNonNull(nickname);
this.createdAt = createdAt == null ? LocalDateTime.now() : createdAt;
}
// 닉네임 길이 조건 요구사항을 확인하기 위한 method
void validateNickName(String nickname){
Assert.isTrue(nickname.length() <= NAME_MAX_LENGTH,"최대 길이를 초과하였습니다");
}
}
다음으로 DTO를 생성해볼게요
DTO는 앞서 말씀드린것 처럼 데이터를 운반하는 역할을 담당해요. 그럼 어떤 데이터를 담고 있는지 직관적으로 알 수 있으면 좋겠죠? 회원 등록에 필요한 데이터를 담고 있으니 RegisterMemberCommand로 정의해볼게요
package com.example.fastcampusmysql.domain.member.dto;
public record RegisterMemberCommand (
String email,
String nickname,
String birthday) {
}
record에 대해 생소하실 수도 있을것 같은데 이건 따로 포스팅을 해볼게요. 간단히 설명드리면 record란 "데이터 클래스"로 순수하게 데이터를 보유하기 위한 종류의 클래스에요. 만약 위 record를 기존에 많이 쓰던 entity class로 바꾸면 아래와 같은 코드가 될거에요. 생성자 , Getter와 같은 method를 따로 구현하지 않아도 되서 좋아요.
(대신 Record를 사용하시려면 JDK 14이상 버전을 사용해야 되니 참조해주세요)
package com.example.fastcampusmysql.domain.member.dto;
public class RegisterMemberCommand {
private final String email;
private final String nickname;
private final String birthday;
public RegisterMemberCommand(String email, String nickname, String birthday) {
this.email = email;
this.nickname = nickname;
this.birthday = birthday;
}
public String getEmail() {
return email;
}
public String getNickname() {
return nickname;
}
public String getBirthday() {
return birthday;
}
}
자 이제 Reposity로 넘어가볼게요. Repository는 직접 DB에 접근하는 역할을 하는데 회원정보를 save하는 기능을 만들어볼게요
package com.example.fastcampusmysql.domain.member.repository;
import com.example.fastcampusmysql.domain.member.entity.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
@RequiredArgsConstructor
@Repository
public class MemberRepository {
final private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public Member save(Member member){
/*
member id를 보고 갱신 또는 삽입을 정함
반환값은 id를 담아서 반환한다
*/
if(member.getId() == null){
return insert(member);
}
return update(member);
}
private Member insert(Member member){
SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(namedParameterJdbcTemplate.getJdbcTemplate())
.withTableName("Member") // Table 정의
.usingGeneratedKeyColumns("id");
SqlParameterSource params = new BeanPropertySqlParameterSource(member);
var id = simpleJdbcInsert.executeAndReturnKey(params).longValue(); // Table에 Insert후 생성된 row의 key(id)값을 return 받음
return Member.builder()
.id(id)
.email(member.getEmail())
.birthday(member.getBirthday())
.nickname(member.getNickname())
.createdAt(member.getCreatedAt())
.build();
}
private Member update(Member member){
// TODO : implement
return member;
}
}
Member의 id값이 있는지 확인해서 없다면 신규등록 , 있다면 update를 하도록 했어요. update관련 코드를 다음 포스팅에서 진행할게요
이번 구현에서는 simpleJdbcInsert를 사용했어요. JPA를 평소에 사용하셨다면 위의 예제처럼 repository를 구현하는게 어색하실 수 있지만 이번에는 이렇게 구현을 한번 해보도록 할게요. NamedParameterJdbcTemplate, SimpleJdbcInsert와 같은 구문에 대해서는 이 역시 추가 포스팅을 하도록 하겠습니다.
자 이제 마지막으로 서비스를 만들어볼게요
package com.example.fastcampusmysql.domain.member.service;
import com.example.fastcampusmysql.domain.member.dto.RegisterMemberCommand;
import com.example.fastcampusmysql.domain.member.entity.Member;
import com.example.fastcampusmysql.domain.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service
public class MemberWriteService {
final private MemberRepository memberRepository;
public Member create(RegisterMemberCommand command){
/*
GOAL : 회원정보(이메일,닉네임,생년월일)를 등록한다.
: 닉네임을 10자를 넘길 수 없다.
Parameter : memberRegisterCommand
val member = Member.of(memberRegisterCommand)
memberRepository.save()
*/
var member = Member.builder()
.nickname(command.nickname())
.email(command.email())
.birthday(command.birthday())
.build();
memberRepository.save(member);
return member;
}
}
MemberWriteService라는 네이밍으로 Class를 만들었어요.
앞서 정의한 DTO인 RegisterMemberCommand를 통해 데이터를 받고 이를 Member Entity로 변환해요. 그리고 Repository에 전달하여 DB에 Insert하는 과정을 거치면 모든 비즈니스 로직이 끝나게 됩니다.
아 마지막으로 Service를 호출하는 Controller를 만들어보록 하겠습니다. Controller패키지는 Domain 바로 아래 만들었고 MemberController로 정의했어요
package com.example.fastcampusmysql.domain.controller;
import com.example.fastcampusmysql.domain.member.dto.RegisterMemberCommand;
import com.example.fastcampusmysql.domain.member.entity.Member;
import com.example.fastcampusmysql.domain.member.service.MemberWriteService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
@RestController
public class MemberController {
final private MemberWriteService memberWriteService;
@PostMapping("/members")
public Member register(@RequestBody RegisterMemberCommand command){
return memberWriteService.create(command);
}
}
이제 직접 실행을 해봐야하는데 그 전에 DB에 Table을 생성해야 됩니다. JPA를 사용하면 자동으로 생성되지만 이번에 직접 한번 해볼게요!
실습환경 구축에서 설정한 DataBase Browser로 이동해서 console를 실행해볼게요
Consoles 하위에 Connection을 클릭하면 중앙에 SQL 입력할 수 있는 화면이 나올거에요. 그럼 아래 CREATE 구문을 입력합니다.
create table `Member`
(
id int auto_increment,
email varchar(20) not null,
nickname varchar(20) not null,
birthday date not null,
createdAt datetime not null,
constraint member_id_uindex
primary key (id)
);
그리고 상단에 Run 버튼을 클릭하면 Table이 생성될거에요
Tables에서 reload하시면 생성된 테이블 확인하실 수 있어요
자 이제 준비는 끝났구 서버를 실행한 뒤 Swagger에서 직접 실행해볼게요. 실행하면 아래와 같이 데이터가 정상적으로 return되는걸 확인하실 수 있고
마지막으로 console에서 select구문을 한번 실행해볼게요
정상적으로 회원정보가 생성된것 같네요
다음 포스팅에선 회원정보를 조회하는 로직에 대해 다뤄볼게요! 오늘도 읽어주셔서 감사드립니다!
'Computer Science > 서버 개발' 카테고리의 다른 글
[대용량 처리를 위한 MySQL 이해] MySQL 소개 (0) | 2023.05.01 |
---|---|
[대용량 처리를 위한 MySQL 이해] 대용량 시스템의 이해 (2) | 2023.04.29 |
[대용량 처리를 위한 MySQL 이해] SNS 서비스 만들기 - 실습환경 구축 (0) | 2023.04.29 |
[대용량 처리를 위한 MySQL 이해] 대용량 서버를 구축하기 위해서는 어떤걸 알아야 할까? (0) | 2023.04.29 |