Spring Framework

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

na_o 2022. 1. 22. 16:59
728x90

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

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

 

 

 

클래스 의존 관계

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

 

 


https://github.com/NayoungBae/springIntroduction/commit/a31e0c9017991ab941b5083872dc59a6b5c2695d?diff=split 

 

회원 관리 예제 - 회원 도메인과 리포지토리 만들기 · NayoungBae/springIntroduction@a31e0c9

Permalink This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. Browse files 회원 관리 예제 - 회원 도메인과 리포지토리 만들기 Loading branch information Showing 3 changed files

github.com

package hello.hellospring.repository;

import hello.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();
}
  • Optional : Java8 이상 있는 기능. null로 반환하는 경우 주로 Optional로 감싸서 반환함
  • Optional의 ofNullable()을 사용하면 null이 나와도 에러가 나지 않고 처리 가능

 

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository{

    //DB 역할
    private static Map<Long, Member> store = new HashMap<>();
    //PK 역할
    private static long sequence = 0L;

...

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

...

}
  • ofNullable(): null이 나와도 에러가 나지 않고 처리 가능

 

 


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

개발한 기능을 실행해서 테스트 할 때 자바의 main 메소드를 통해 실행하거나

웹 어플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다

이런 방법은 준비하고 실행하는 데 오래 걸리고, 

반복 실행하기 어려우며 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다

자바는 JUnit 이라는 프레임워크로 테스트를 실행해

이러한 문제를 해결한다

 

스프링은 테스트코드를 작성할 수 있도록 환경 세팅을 프로젝트 생성 시 미리 해준다

test 패키지에 작성

https://github.com/NayoungBae/springIntroduction/commit/10883b623cd424854a9924efbae467c42df72e1c

 

CRUD 테스트코드 작성 - findByName() 정상작동 안함 · NayoungBae/springIntroduction@10883b6

Permalink This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. Browse files CRUD 테스트코드 작성 - findByName() 정상작동 안함 Loading branch information Showing 1 changed file with 5

github.com

findByName() 함수에서 오류 발생. 왜?

 

CRUD 테스트코드 작성 - findByName() 에러 해결 · NayoungBae/springIntroduction@64d6a83

Permalink This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. Browse files CRUD 테스트코드 작성 - findByName() 에러 해결 Loading branch information Showing 2 changed files with 19 add

github.com

  • 리포지토리에서 HashMap을 비우는 함수 clear를 실행시키는 메소드를 만들고
  •     public void clearStore() {
            store.clear();
        }
  • 테스트코드 작성 시 @AfterEach 어노테이션을 이용해 clearStore 함수를 매번 실행
  •     @AfterEach //하나의 테스트가 끝난 뒤 실행
        public void afterEach() {
            repository.clearStore();
        }

 

 

 

지금까지 리포지토리를 작성하고, 만든 리포지토리를 테스트하기 위해 테스트코드를 작성했다

하지만 반대로 하는 방법도 있다

테스트코드를 작성한 다음, 그 틀에 맞추어 실제로 쓰이는 코드를 작성하는 방법

이것을 TDD(테스트 주도 개발)라고 함

https://nazero.tistory.com/136

 

[TIL] 2021.11.22 최종 프로젝트 진행중 - TDD란? / 오늘의 후회 / 내일 해야할 일 / Tomcat SSL

TDD는 이것이다!!!의미를 정확히 알고있자 지금까지는 설계를 하고 개발을 한 뒤에 test를 했다 하지만 테스트를 하다 보니까 설계를 수정해야 할 필요가 생겨서 다시 설계쪽으로 넘어왔다 edge 케

nazero.tistory.com

 

 

 


회원 서비스 개발

https://github.com/NayoungBae/springIntroduction/commit/ac37897b81613886b36dd3de3de6cdf78cf39194

 

회원가입/ 전체 회원조회 로직 추가 · NayoungBae/springIntroduction@ac37897

Permalink This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. Browse files 회원가입/ 전체 회원조회 로직 추가 Loading branch information Showing 1 changed file with 43 additions and

github.com

회원가입 로직

  • 같은 이름이 있는 중복 회원은 회원가입 불가
  •     public Long join(Member member) {
            validateDuplicateMember(member); //중복 회원 검증
            memberRepository.save(member);
            return member.getId();
        }
    
        private void validateDuplicateMember(Member member) {
            //같은 이름이 있는 중복 회원은 안된다
            memberRepository.findByName(member.getName())
                    .ifPresent(member1 -> {
                        throw new IllegalStateException("이미 존재하는 회원입니다.");
                    });
        }
  • ifPresent 사용법 : https://www.whiteship.me/optional-ifpresent/
 

자바 Optional 의 ifPresent 활용하기

자바 8에 추가된 Optional이 제공하는 ifPresent를 사용해 null을 체크하는 if 문을 줄이는 방법에 대해 설명합니다.

www.whiteship.me

 

요즘에는 

if(member != null) {

} else {

}

이런 식으로 짜는 게 아닌

Optional<Member> result = ...;
result.ifPresent(member -> {

});

이런 식으로 Optional에서 제공해주는 함수를 사용한다

get 함수를 이용해서 결과 데이터를 직접 꺼내는 것은 권장하지 않는다

orElseGet()도 자주 사용한다고 한다

 

    • Optional로 바로 반환하는 건 좋지 않음
    • //개선 전 코드
          private void validateDuplicateMember(Member member) {
              //같은 이름이 있는 중복 회원은 안된다
              Optional<Member> result = memberRepository.findByName(member.getName())
              result.ifPresent(member1 -> {
                  throw new IllegalStateException("이미 존재하는 회원입니다.");
              });
          }
    • //개선된 코드
          private void validateDuplicateMember(Member member) {
              //같은 이름이 있는 중복 회원은 안된다
              memberRepository.findByName(member.getName())
                      .ifPresent(member1 -> {
                          throw new IllegalStateException("이미 존재하는 회원입니다.");
                      });
          }

 

 

 


회원 서비스 테스트

https://github.com/NayoungBae/springIntroduction/commit/a6b098570a9de26c53fd7bd15ac0d85c141f40d3

 

MemberService 테스트코드 - 회원가입 · NayoungBae/springIntroduction@a6b0985

Permalink This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. Browse files MemberService 테스트코드 - 회원가입 Loading branch information Showing 1 changed file with 36 additions and 0

github.com

  • 테스트 코드 작성 시 given, when, then 순서대로 적으면 좋음

 

 

정상 케이스 작성도 중요, 예외 케이스도 중요!

https://github.com/NayoungBae/springIntroduction/commit/fbbc2adc5d532b7dff0f1411530f60e854c61bac

 

MemberService 테스트코드 - 회원가입 중복 회원 예외 · NayoungBae/springIntroduction@fbbc2ad

Permalink This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. Browse files MemberService 테스트코드 - 회원가입 중복 회원 예외 Loading branch information Showing 1 changed file wit

github.com

  • 예외처리 테스트 시 제공하는 문법이 있으니, try-catch를 쓸 필요 없음
  • //개선 전
    
            //when
            memberService.join(member1);
            try {
                memberService.join(member2);
                fail("예외가 발생해야 합니다.");
            } catch(IllegalStateException e) {
                assertEquals(e.getMessage(), "이미 존재하는 회원입니다.");
            }
  • //개선 후
    
            //when
            memberService.join(member1);
            //이 로직 실핼 시 이런 에러를 기대한다
            IllegalStateException e = assertThrows(IllegalStateException.class,
                                                    () -> memberService.join(member2));
    
            //then
            //메시지 검증
            assertEquals(e.getMessage(), "이미 존재하는 회원입니다.");

https://github.com/NayoungBae/springIntroduction/commit/6befb96e65f280b79772bf8fd4f05b7029ac78be

 

MemberService 테스트코드 - 메소드 실행 뒤 저장소 데이터 삭제 필요 · NayoungBae/springIntroduction@6befb96

Permalink This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. Browse files MemberService 테스트코드 - 메소드 실행 뒤 저장소 데이터 삭제 필요 Loading branch information Showi

github.com

 

 

현재 MemberService와 MemberServiceTest에서 쓰이는 리포지토리가 각자 다름!

따지고보면 각자 다른 저장소를 쓰고 있는 것임!

한 프로젝트에서 똑같은 저장소를 써야 하는데 다른 저장소를 새로 만들어서 사용하는 꼴이 되어버림

class MemberServiceTest {

    //여기 안에 또다른 MemoryMemberRepository가 있음
    MemberService memberService = new MemberService();
    MemoryMemberRepository memberRepository = new MemoryMemberRepository();
    
    ...
}
public class MemberService {
    private final MemberRepository memberRepository = 
                                    new MemoryMemberRepository();
    ...
}

 

 

해결 방법 : 의존성 주입! DI!

 

 

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }
    ...
}
public class MemberService {
    private final MemberRepository memberRepository;

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