[스프링 입문] 3. 회원 관리 예제 만들기

코린이서현이·2023년 11월 2일
0

😊들어가면서😊

이게 얼마나 오랜만인지? 미뤄왔단 김영한 커리를 다시 들어야겠다.

📌 개발 프로세스

1. 비즈니스 요구사항 정리
2. 회원 도메인 리포지토리 만들기
3. 회원 리포지토리 테스트 케이스 작성
4. 회원 서비스 개발 
5. 회원 서비스 테스트

🎯 비즈니스 요구 사항

  • 데이터 : 회원 ID, 이름
  • 기능 : 회원 등록, 조회
  • 아직 데이터 저장소가 선정되지 않음

📌컨트롤러
웹 MVC의 컨트롤러 역할
클라이언트의 요청을 처리하고 응답을 반환하는 역할을 담당한다.

📌서비스
핵심 비즈니스 로직 구현
비즈니스 로직을 수행하는 계층으로, 일반적으로 컨트롤러에서 호출되어 실행된다. 서비스 계층은 비즈니스 로직을 처리하고, 필요한 경우 리포지토리를 사용하여 데이터베이스와 상호작용한다.

💡 비즈니스 로직이란
현실 문제, 즉 비즈니스에 대한 의사 결정을 하고 있는가?

계좌 잔액이 충분한지 확인 -> 송금이 가능한지에 대한 의사결정
송금 수수료를 계산 -> 송금에 드는 비용을 정책에 따라서 결정
사용자의 잔액을 감소시킨다 -> 송금이라는 서비스를 수행
비즈니스로직이 아닌 것

유효하지 않으면 에러 메시지를 띄운다 -> UI
송금 수수료를 결제하도록 외부 결제 서비스에 요청한다. -> 외부 서비스와의 네트워킹
잔액을 DB에 저장한다. -> Persistence(영속성)

📌리포지토리
데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
CRUD 작업과 같은 데이터베이스 작업을 캡슐화한다. 스프링 데이터 JPA를 사용하면, 인터페이스를 통해 기본적인 CRUD 작업을 자동으로 처리할 수 있다.
📌도메인
비즈니스 도메인 객체

클래스 의존 관계

  • 데이터 저장소를 사용하지 않아서 인터페이스로 클래스를 변경할 수 있도록 설계

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

❓ 아까 리포지토리가 뭐라고 했지요??

데이터 베이스에 접근하고 도메인 객체를 데이터베이스에 저장하고 관리하는 것!!

데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
CRUD 작업과 같은 데이터베이스 작업을 캡슐화한다. 
스프링 데이터 JPA를 사용하면, 인터페이스를 통해 기본적인 CRUD 작업을 자동으로 처리할 수 있다.

회원 도메인을 저장하는 리포지토리를 만들자!! (대신 아까 말했듯이 인터페이스로 클래스를 변경할 수 있도록 설계)

👉 회원도메인을 먼저 만들어야겠지요?

📂 domain
📄 Member

  • 회원 객체 : id 와 name을 가진다.
  • SetterGetter를 가진다. (근데 사용 의미나 용도는 이후에 또 고민해야함)
public class Member {
  //비즈니스 요구사항 id와 닉네임
  private Long id;
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Long getId() {
    return id;
  }

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

👉 리포지토리를 만들어볼까요?

📂 repositoty
📐MemberRepository 인터페이스

public interface MemberRepository {
  // 저장메서드
  Member save(Member member);
  Optional<Member> findById(Long id);
  Optional<Member> findByName(String name);
  List<Member> findAll();
}

📄 MemoryMemberRepository 클래스

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의 ID 셋팅
    member.setId(++sequence);
    store.put(member.getId(), member);
    return member;
  }

  @Override
  public Optional<Member> findById(Long 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();
  }
}

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

개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해서 실행하거나, 웹 애플리케이션의
컨트롤러를 통해서 해당 기능을 실행한다. 이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기
어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다. 자바는 JUnit이라는 프레임워크로 테스트를
실행해서 이러한 문제를 해결한다.

🗂️ test
📂 repository
📄 MemoryMemberRepositoryTest

  • MemoryMemberRepository 클래스의 테스트 코드 작성
class MemoryMemberRepositoryTest {

  MemoryMemberRepository repository = new MemoryMemberRepository();

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

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

    repository.save(member);

    Member result =  repository.findById(member.getId()).get();

    //Assertions.assertEquals(member,null);
    assertThat(member).isEqualTo(result);
  }

  @Test
  public void findByName() {
    Member member1 = new Member();
    member1.setName("spring1");
    repository.save(member1);

    Member member2 = new Member();
    member2.setName("spring2");
    repository.save(member2);

    Member result = repository.findByName("spring1").get();

    assertThat(result).isEqualTo(member1);
  }

  @Test
  public void findALL() {
    Member member1 = new Member();
    member1.setName("spring1");
    repository.save(member1);

    Member member2 = new Member();
    member2.setName("spring2");
    repository.save(member2);

    List<Member> result =  repository.findAll();

    assertThat(result.size()).isEqualTo(2);
  }


}

📌@AfterEach

테스트 메소드 이후에 필수로 수행되는 메소드 지정
테스트가 실패하더라도 수행된다.

🎯 회원 서비스 개발

❓ 회원 서비스가 뭐라고 했지요?

회원 리포지토리와 도메인을 활용해서 실제 비즈니스 로직을 작성하는 부분이다.

➕ 서비스의 메소드명은 비즈니스적인 느낌으로

📂 service
📄 MemberService


public class MemberService {
  private final MemberRepository memberRepository;

  public MemberService(MemberRepository memberRepository) {
    this.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);
  }
}

🎯 회원 서비스 테스트

👍테스트 코드 작성 방법

givem : 데이터 부분 - 주어진 것
when : 무엇을 검증
them : 검증 부분

😀 테스트 코드는 한글로 써도 된다함 ㅋ

빌드될 때 테스트 코드는 해당되지 않는다고 함 오 신기방기~

🗂️ test - servoce
📄 MemberServiceTest
❗ 아까처럼 레파지토리를 초기화해줘야하는데, 이 부분을 유의해보자.

class MemberServiceTest {

  MemberService memberService;
  MemoryMemberRepository memberRepository;
  
  /*
  MemberService memberService = new MemberService();
  MemoryMemberRepository memberRepository = new MemoryMemberRepository();
  +
  memberRepository.clearStore(); 를 쓰면 좋지 않은 이유?!!
  -> memberService에서 이미 memberRepository를 사용하고 있고, 
  memberRepository.clearStore()의 memberRepository는 다른 인스턴스 (static이라서 가능하다고 함)
  
  
  
   */

  
  @BeforeEach
  public void beforEach() {
    //DI..!!
    memberRepository = new MemoryMemberRepository();
    memberService = new MemberService(memberRepository);
    //직접 리파지토리를 넣는 구문! 
  }

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

  @Test
  void 회원가입() {
    //givem : 데이터 부분
    Member member = new Member();
    member.setName("spring");

    //when : 무엇을 검증
    Long saveID = memberService.join(member);

    //them : 검증 부분
    Member findMember = memberService.findOne(saveID).get();
    assertThat(member.getName()).isEqualTo(findMember.getName());
  }

  //테스트는 정상 플로우보다도 예외 플로우가 중요하다. 예외가 터지는 상황도 봐야한다.
  @Test
  public void 중복_회원_예외() {
    //giver
    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));
  /*
    try {
      memberService.join(member2);
      fail();
    }catch (IllegalStateException e) {
      assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
    }
  */

    //then
    assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
  }


  @Test
  void findMembers() {
  }

  @Test
  void findOne() {
  }
}

😀마무리하면서😀

어떤가요❓ 조금 이해가 되시나용? 히히 당연히 못했죠?! 
오늘 공부한걸 정리헤보겠습니당 

🤓 요약해보기
일단 짧은 회원관리 예제를 만들어보면서 개발 프로세스를 익혀보았습니당.
아직 모두 만든 것은 아니고, 컨트롤러와 웹을 구현해야합니당.
또 이런 구현 프로세스가 정답은 아닐거에요? 아마 개발자의 능력이나 프로그램 개발 특성에 따라 달라지겠죠?

📌 백엔드가 컨트롤러 -> 서비스 -> 리포지토리 -> DB 로 전개된다는 것을 배웠어요!

📌 도메인을 작성하고 리파지토리를 작성했어요!!

  • 도메인이란 : 객체였어요
  • 리파지토리란 : 객체를 가지고 저장하고 찾는 기능을 제공했어요.
    save() findById() findByName() findByName()가 있었어요

📌 리파지토리를 테스트하는 코드가 있었어요.

테스트 코드의 유용성을 이해하고 테스트하는 메소드인 assertThat()에 대해서 배웠어요.

  • assertThat(result).isEqualTo(member1) : result와 member1가 같은지 확인하는 코드였어요.
    🙆 -> 별일 없이 테스트코드가 마무리 돼요
    🙅‍♀️ -> 테스트코드가 마무리 되지 않고 틀렸다고 말해줘요!!

⭐ 테스트코드는 순서를 보장하지 않고 실행되고 레파지토리를 clear해줘야해요❗
1. MemoryMemberRepository 클래스에서 clear 메소드가 있어야해요.
2. 테스트 코드에서@AfterEach어노테이션을 이용해서 clear를 해줘요
@AfterEach어노테이션은 Test 코드 이후에 실행되게 하는 것!

📌 서비스를 작성했어요!

서비스는 비즈니스로직을 담고 있는!!

📌 서비스를 테스트 하는 코드를 작성했어요!

DI라는 개념 등장 = 의존성주입
회원 서비스가 직접 메모리 회원 리파지토를 생성하는 것이 아니라 ❌
회원 서비스 코드의 리파지토리를 외부에서 생성하게 함 ❗

🔍 추가 공부해야할 것들

  1. import static
  2. 이게 무엇인지?

⚠️ 난데 없는 오류 발생


오랜만에 열어서 라이브러리 깨진거였음 🐘 Graddle refresh해주니까 성공함 😽😚~
BUILD SUCCESSFUL in 33s

profile
24년도까지 프로젝트 두개를 마치고 25년에는 개발 팀장을 할 수 있는 실력이 되자!

0개의 댓글