minghxx.blog
  • [Spring] 스프링 입문 3) 회원 관리 예제 - 백엔드 개발
    2023년 10월 14일 14시 19분 17초에 업로드 된 글입니다.
    작성자: 민발자
    728x90

    스프링 입문 - 스프링 부트 웹 MVC, DB 접근 기술 

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

    1. 비즈니스 요구사항 정리

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

    컨트롤러 ▶ 웹 MVC의 컨트롤러, API의 컨트롤러 역할

    서비스 ▶ 핵심 비즈니스 로직 구현(회원 중복가입 불가)

    도메인 ▶ DB에 주로 저장하고 관리되는 비즈니스 도메인 객체(회원, 주문, 쿠폰 등)

    리포지토리 ▶ 데이터 베이스에 접근, 도메인 객체를 DB에 저장하고 관리, 핵심 비즈니스 로직이 동작하도록 구현한 객체

     

    2) 클래스 의존 관계

    데이터 저장소 선정되지 않아 인터페이스로 만들고 메모리기반 구현체 생성

    ▶ 추후 저장소 선정 후 구현 클래스 변경할 수 있도록 인터페이스로 생성


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

    1) 회원 도메인

    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; }
    }

     

    2) 리포지토리 인터페이스

    회원 객체를 저장할 저장소

    public interface MemberRepository {
    	Member save(Member member); // 회원 저장
    	Optional<Member> findById(Long id); // id로 회원 찾기
    	Optional<Member> findByName(String name); // 이름으로 회원 찾기
    	List<Member> findAll(); // 회원 모두 조회
    }
    ▶ Optional
    java 8부터 도입, 결과가 null일 때 Optional로 감싸서 반환한다.

     

    3) 리포지토리 메모리 구현체

    public class MemoryMemberRepository implements MemberRepository {
        //동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용 고려
    	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); //store에 저장
    		return member;
    	}
        
    	@Override
    	public Optional<Member> findById(Long id) {
    		//null일 경우 optional로 감싸서 반환
    		// id로 store에서 꺼냄
    		return Optional.ofNullable(store.get(id));
    	}
        
    	@Override
    	public Optional<Member> findByName(String name) {
    		// param으로 넘어온 name과 같은 것 filtering 
    		// filtering된 것을 반환
    		return store.values().stream().filter(member -> member.getName().equals(name)).findAny();
    	}
        
    	@Override
    	public List<Member> findAll() {
    		return new ArrayList<>(store.values());
    	}
    }

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

    1) 테스트 케이스 작성

    JUnit 프레임워크 이용해 테스트 실행

     

    2) 리포지토리 메모리 구현체 테스트

    class MemoryMemberRepositoryTest {
    	// 테스트 하기위한 저장소 생성
    	MemoryMemberRepository repository = new MemoryMemberRepository();
        
    	@AfterEach // 테스트 끝나고 실행
    	public void afterEach() {
    		repository.clearStore(); // 저장소 내용 전부 삭제
    	}
        
    	@Test
    	public void save() {
    		//given
    		Member member = new Member();
    		member.setName("spring");
    		//when
    		repository.save(member);
    		//then
    		// 저장한 member와 저장소에 저장된 멤버가 같은지 확인
    		Member result = repository.findById(member.getId()).get();
    		assertThat(result).isEqualTo(member);
    	}
        
    	@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);
    	}
        
    	@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
    		// list 사이즈와 실제 저장한 멤버 수 2와 같은지 확인
    		assertThat(result.size()).isEqualTo(2);
    	}
    }
    ▶ @AfterEach
    여러 테스트를 실행하면 메모리 DB에 테스트 결과가 남을 수 있기때문에 각 테스트가 종료될 때마다 메모리DB를 비워주는 메서드 실행
    테스트는 각각 독립적으로 실행되어야 한다 순서에 의존관계가 있는 것은 좋은 테스트가 아님

    ▶ assertEquals(기대값, 결과값)
    기대값과 결과가 같은지 확인

    4. 회원 서비스 개발

    public class MemberService {
    	private final MemberRepository memberRepository;
        
    	// 회원가입
    	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);
    	}
    }

    5.  회원 서비스 테스트 

    기존 멤버 서비스에서 메모리 리포지토리 직접 생성, 테스트에서도 생성하면 각각 서로 다른 리포지토리 객체를 사용하게 된다.

    같은 리포지토리 인스턴스로 테스트하기 위해 의존관계 주입(DI)필요

    BeforeEach()에서 리포지토리 생성, 회원 서비스에 넣어주면 같은 리포지토리 인스턴스를 가지고 서비스 테스트 가능 

    ▶ @BeforeEach
    테스트 실행 전에 호출, 테스트가 서로 영향이 없도록 항상 새로운 객체 생성, 의존관계도 새로 맺어준다
    public class MemberService {
    	private final MemberRepository memberRepository;
        
    	public MemberService(MemberRepository memberRepository) {
    		this.memberRepository = memberRepository;
    	}
    }
    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("spring");
    
            // 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("이미 존재하는 회원입니다.");
    
        }
    }

     

    728x90
    댓글