본문 바로가기
Backend/Spring

[Spring] 회원 관리 예제 - 백엔드 개발

by eyi-jin 2022. 6. 26.
이 게시글은 [인프런]스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의를 기반으로 작성되었으며 강의를 기억하기 위한 기록물입니다. 순수하게 복습하기 위한 용도로 작성되었음을 미리 밝힙니다.

회원 관리 예제 - 백엔드 개발

비즈니스 요구사항 정리

회원 도메인과 리포지토리 만들기

회원 리포지토리 테스트 케이스 작성

회원 서비스 개발

회원 서비스 테스트

 

비즈니스 요구사항 정리

학습을 위한 것이므로 단순한 비즈니스 요구사항을 전제로 한다.

  • 데이터 : 회원ID, 이름
  • 기능 : 회원 등록, 조회
  • 데이터 저장소(DB)가 선정되지 않은 가상의 시나리오
  • 일반적인 웹 애플리케이션 계층구조
    • 컨트롤러 : 웹 MVC의 컨트롤러 역할
    • 서비스 : 핵심 비즈니스 로직 구현
    • 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
    • 도메인 : 비즈니스 도메인 객체, 예)회원, 주문, 쿠폰 등 DB에 저장하고 관리

 

  • 클래스 의존 관계
    • 아직 데이터 저장소가 선정되지 않아서, 인터페이스로 구현 클래스를 변경할 수 있도록 설계
    • 데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황 가정
    • 개발을 진행하기 위해서 초기 개발단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용

 

회원 도메인과 리포지토리 만들기

//src\main\java\eyijin\hellospring\domain\Member.java

package eyijin.hellospring.domain;

public class Member {
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
//src\main\java\eyijin\hellospring\repository\MemberRepository.java

package eyijin.hellospring.repository;
import eyijin.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    List<Member> findAll();
}
//src\main\java\eyijin\hellospring\repository\MemoryMemberRepository.java

package eyijin.hellospring.repository;
import eyijin.hellospring.domain.Member;
import java.util.*;

public class MemoryMemberRepository implements MemberRepository{

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }
}

 

회원 리포지토리 테스트 케이스 작성

  • 모든 테스트는 순서에 상관없이 메소드 별로 따로 동작하게 설계해야함
  • @AfterEach : 하나의 메소드의 동작이 끝날 때마다 실행하게 되는 메소드, 여기에서는 저장소에 저장된 데이터를 삭제하는 역할을 한다.(한번에 여러개의 테스트를 진행하다보면 db에 직전 테스트 결과가 남아 있을 수 있는데, 이 결과는 다음 테스트에 영향을 줄 수도 있기 때문에 저장소 청소를 해줘야한다.)
  • 테스트 주도 개발(TDD) : 테스트 클래스를 먼저 작성한 뒤 구현 클래스를 작성할 수도 있다.(틀을 미리 작성하는 것)
  • given - when - then : 뭔가가 주어지면서(데이터) 이걸 실행했을때(검증 해야할 것) 결과가 이게 나와야함(검증하는 부분)

 

회원 서비스 개발

  • repository : 단순 개발스러운 용어들 사용함, 저장소에 넣어 빼 이런것들
  • service : 서비스 로직에 가까운 내용들을 넣어야함, 비즈니스 로직을 처리해야함
//src\main\java\eyijin\hellospring\service\MemberService.java

package eyijin.hellospring.service;

import eyijin.hellospring.domain.Member;
import eyijin.hellospring.repository.MemberRepository;
import eyijin.hellospring.repository.MemoryMemberRepository;
import java.util.List;
import java.util.Optional;

public class MemberService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();

    /**
     * 회원가입
     */
    public Long join(Member member){
        //같은 이름이 있는 중복 회원X
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    /**
     *전체 회원 조회
     */
    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberID){
        return memberRepository.findById(memberID);
    }

}

 

회원 서비스 테스트

테스트 코드는 한글로 적기도 합니다.

한글로 작성하면, 좀 더 직관적으로 이해할 수 있습니다.

//src\test\java\eyijin\hellospring\service\MemberServiceTest.java

package eyijin.hellospring.service;

import eyijin.hellospring.domain.Member;
import eyijin.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.Optional;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService = new MemberService();
    MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach(){
        memoryMemberRepository.clearStore();
    }

    @Test
    void join() {
        //given
        Member member = new Member();
        member.setName("hello");

        //when
        Long saveId = memberService.join(member);

        //then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외(){
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        try {
//            memberService.join(member2);
//            fail();
//        }catch (IllegalStateException e){
//            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        }

        //then
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

MemoryMemberRepository 의 중복 해결 → 의존성 주입(DI)

위의 코드와 같이 사용하면, MemoryMemberRepository 의 사용이 중복되어서 문제가 발생할 수 있습니다. 물론 현재의 코드에서 MemoryMemberRepository에서 static map을 사용하기 때문에 당장 문제가 발생하지는 않으나, static을 이용하지 않을 경우 문제가 생길 수 있습니다. 따라서 아래와 같이 코드를 변경해줍니다.

//src\main\java\eyijin\hellospring\service\MemberService.java

public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
        /**
     * 회원가입
     */
        //...
}
//src\test\java\eyijin\hellospring\service\MemberServiceTest.java

package eyijin.hellospring.service;

import eyijin.hellospring.domain.Member;
import eyijin.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Optional;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach(){
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

    @AfterEach
    public void afterEach(){
        memberRepository.clearStore();
    }

    @Test
    void join() {
        //given
        Member member = new Member();
        member.setName("hello");

        //when
        Long saveId = memberService.join(member);

        //then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외(){
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        try {
//            memberService.join(member2);
//            fail();
//        }catch (IllegalStateException e){
//            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        }

        //then
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

'Backend > Spring' 카테고리의 다른 글

JPA vs MyBatis  (0) 2023.07.09
[Spring] 회원 관리 예제 - 웹MVC 개발  (0) 2022.07.02
[Spring] 스프링 빈과 의존관계  (0) 2022.07.01
[Spring] 스프링 웹 개발 기초  (0) 2022.06.25
[Spring] 프로젝트 환경설정  (0) 2022.06.24

댓글