본문 바로가기
Spring Tutorial

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

by 미소5 2024. 7. 14.

일반적인 웹 애플리케이션 계층 구조

  • 컨트롤러: 웹 MVC의 컨트롤러 역할
  • 서비스: 핵심 비즈니스 로직 구현
  • 리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
  • 도메인: 비즈니스 도메인 객체, 예) 회원, 주문, 쿠폰 등 주로 데이터베이스에 저장하고 관리됨

 


  • 비즈니스 요구사항
    • 데이터: 회원ID, 이름
    • 기능: 회원 등록, 조회
    • 아직 데이터 저장소는 선정되지 않음 (→우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계)

 


  • 회원 도메인: 회원 객체
    • 데이터: 회원ID, 이름
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;
    }
}

 


 

  • 회원 리포지토리 : 회원객체를 저장하는 저장소
public interface MemberRepository {
    Member save(Member member);

    Optional<Member> findById(Long id);

    Optional<Member> findByName(String name);
    // Optional: 값이 null이면 Optional로 반환 

    List<Member> findAll();
}

 

  • 회원 리포지토리 메모리 구현체
public class MemoryMemberRepository implements MemberRepository {

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;
        /* 동시성 문제가 고려되어 있지 않음
     * (실무에서는 ConcurrentHashMap, AtomicLong 사용 고려)*/

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

    @Override
    public Optional<Member> findById(Long id) {
        //return store.get(id);을  값이 null이어도 반환할수있도록
        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());
    }
    
    public void clearStore() {
        store.clear();
    }
    
}

 

+회원 리포지토리 테스트

테스트는 src/test/java 하위 폴더에 생성

class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

    //테스트는 테스트 순서에 관계없이, 각각 독립적으로 실행되어야 함!
    @AfterEach      //각 테스트가 종료될 때 마다
    public void afterEach() {
        repository.clearStore();
        // 메모리 DB에 저장된 데이터를 삭제
    }

    @Test
    public void save() {
        //given
        Member member = new Member();
        member.setName("spring");

        //when
        repository.save(member);

        //then
        Member result = repository.findById(member.getId()).get();
        //제대로 저장됐는지 확인 후, get()으로 꺼내서, result에 저장

        //Assertions.assertThat(member).isEqualTo(result);
        assertThat(member).isEqualTo(result);
    }

    @Test
    public void findByName() {
        //given
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);
        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);
        //when
        Member result = repository.findByName("spring1").get();
        
        //then
        assertThat(result).isEqualTo(member1); //result는 member1
    }

    @Test
    public void findAll() {
        //given
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);
        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);
        
        //when
        List<Member> result = repository.findAll();

        //then
        assertThat(result.size()).isEqualTo(2);   //result는 2개
    }

}

 

Shift+F6 으로 변수명만 변경 가능

 


 

  • 회원 서비스
    • 기능: 회원 등록, 조회
public class MemberService {
    /*private final MemberRepository memberRepository= new MemoryMemberRepository();;
      회원 서비스가 메모리 회원 리포지토리를 직접 생성
      ->회원 리포지토리의 코드가 회원 서비스 코드를 DI 가능하게 변경*/
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /**
     * 회원가입
     */
    public Long join(Member member) {
        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);
    }

}

 

+회원 서비스 테스트

class MemberServiceTest {
    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach     //각 테스트 실행 전에 호출된다
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
        //항상 새로운 객체를 생성하고, 의존관계도 새로 맺어준다.
    }

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


    @Test
    public void join() throws Exception{
        //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 중복_회원_예외() throws Exception {
        //Given
        Member member1 = new Member();
        member1.setName("spring");
        Member member2 = new Member();
        member2.setName("spring");

        //When
        memberService.join(member1);

        /*try{
            memberService.join(member2);
            fail();
        } catch (IllegalStateException e){
            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        }*/
        IllegalStateException e =assertThrows(IllegalStateException.class,
                () -> memberService.join(member2));     //예외가 발생해야 한다

        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        //예외메시지는 "이미 존재하는 회원입니다."
    }
}

 

 

 


 

  • 클래스에서 ctrl+shift+t 누르면, 해당 클래스의 입력 정보 바탕으로 테스트 코드 자동 생성
ctrl+shift+t ->create new test

 


  • 스프링 컨테이너에 DB까지 연결한 통합 테스트 만들어보기

[Spring 입문11] 스프링 DB 접근 기술: 스프링 통합 테스트 (tistory.com)

 

[Spring 입문11] 스프링 DB 접근 기술: 스프링 통합 테스트

Spring 입문3 -회원 관리 예제 (tistory.com) Spring 입문3 -회원 관리 예제 컨트롤러: 웹 MVC의 컨트롤러 역할 서비스: 핵심 비즈니스 로직 구현 리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저

joly156.tistory.com

 

728x90
반응형