📙

[스프링 입문] 3. 백엔드 개발

작성일자
Oct 5, 2022
태그
SUB PAGE
프로젝트
스프링 입문
책 종류
본 게시글은 하단 강의를 듣고 학습한 내용을 제 생각으로 요약, 정리한 글입니다.

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

1) 비즈니스 요구사항

  • 데이터: 회원Id, 이름
  • 기능: 회원 등록, 조회
  • 시나리오: 아직 db 선정 x
 

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

notion image
  • 컨트롤러: 웹 MVC의 컨트롤러 역할. API 만들 때 컨트롤러 역할.
  • 서비스: 서비스 클래스의 핵심 비즈니스 로직이 들어가 있음.
    • ex) 회원 중복 가입 안됨
  • 도메인: 회원, 주문, 쿠폰처럼 db에 주로 저장되고 관리되는 비즈니스 도메인 객체.
  • 리포지토리: 비즈니스 도메인 객체를 가지고 핵심 비즈니스 로직이 동작하도록 구현한 계층. 도매인 객체를 DB에 저장하고 관리.
 

3) 클래스 의존관계

notion image
  • 회원 비즈니스 로직에는 회원 서비스가 있음
회원 리포지토리(회원을 저장)는 인터페이스로 설계할 거임
  • 이유) 아직 db가 선정되지 않아서 리포지토리를 인터페이스로 만들고 구현체를 우선은 메모리 구현체로 만들거임(메모리 기반의 데이터 저장소 사용). 향후, 구체적인 db기술 선정되고 나면 이를 바꿔끼울 것임. 바꿔 끼우려면 인터페이스가 필요해서 인터페이스 정의해둔 것임.
  • 정리) 인터페이스로 구현 클래스를 변경할 수 있도록 설계
 

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

1) 회원 객체

  • 도메인: 회원, 주문, 쿠폰처럼 db에 주로 저장되고 관리되는 비즈니스 도메인 객체.
  1. 패키지 만들기 (hello.hellospring.domain)
  1. 패키지 안에 Java 클래스 만들기 (Member)
    1. src/main/java/hello.hellospring.domain/Member.java
  1. 요구사항 구현
    1. src/main/java/hello.hellospring.domain/Member.java
      getter, setter → id, name 둘 다 체크해서 만들기
      notion image
 

2) 회원 리포지토리 (인터페이스, 구현체)

  • 리포지토리: 도메인 객체를 DB에 저장하고 관리. (리포지토리 뜻: 저장소)
  • 회원 리포지토리 인터페이스
      1. 패키지 만들기 (hello.hellospring.repository)
      1. 패키지 안에 인터페이스 만들기 (MemberRepository)
        1. src/main/java/hello.hellospring.repository/MemberRepository.java
      1. 기능 구현
        1. src/main/java/hello.hellospring.repository/MemberRepository.java
          • Optional
            • 자바 8의 기능.
            • findById 가져오는데 없으면 null 반환하는데 이때, null을 그대로 반환하지 않고 optional이란 걸로 감싸서 반환함.
          • 리포지토리의 4가지 기능 구현함
            • save → 회원이 저장소에 저장됨
            • findById, findByName → 회원을 저장소에서 찾아옴
            • findAll → 지금까지 저장된 모든 회원 리스트를 반환함
  • 회원 리포지토리 메모리 구현체
      1. 패키지 안에 Java 클래스 만들기 (MemoryMemberRepository)
        1. src/main/java/hello.hellospring.repository/MemoryMemberRepository.java
      1. 인터페이스를 implements하기
        1. src/main/java/hello.hellospring.repository/MemoryMemberRepository.java
          • implement methods (ctrl + i) → 전부 선택해서 ok하기
          notion image
      1. 구현하기
        1. src/main/java/hello.hellospring.repository/MemoryMemberRepository.java
          • private static Map<Long, Member> store= new HashMap<>();
            • 저장할 곳.
            • key는 회원 아이디니까 Long으로 함. 값은 Member.
            • 실무에선 동시성 문제 있을 수 있어서 공유되는 변수일 땐 ConcurrentHashMap을 써야하는데 여기선 예제니까 단순하게 HashMap씀
          • private static long sequence= 0L;
            • sequence는 0,1,2 같은 key값 생성해주는 애임.
            • 실무에선 동시성 문제 고려해서 long보단 AtomicLong 씀.
          • 람다, arrayList부분 같은 자바 문법 따로 공부하기
           
           

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

1) 테스트 코드 쓰는 이유

  • 방금 만든 회원 리포지토리 클래스가 정상 동작하는지 검증하기 위함
  • 원래) 개발한 기능을 실행해서 테스트 할 때 main 메서드를 통해서 실행하거나 컨트롤러로 해당 기능을 실행해볼 수 있음.
    • 문제점) 반복적 실행 어려움. 어려 테스트 한번에 실행하기 어려움
  • 대안) JUnit이라는 프레임워크로 테스트 실행
    • main 메서드 쓰는 거랑 비슷함
  • 여러 명이 함께 개발할 때 필수적임

2) 테스트케이스 작성

  1. Test/java에 패키지 만들기(hello.hellospring.repository)
  1. 패키지 안에 java class 만들기(MemoryMemberRepositoryTest)
  1. MemberRepository 클래스의 객체 만들기
    1. src/test/java/hello.hellospring.repository/MemoryMemberRepositoryTest.java
  1. 멤버 저장 기능 확인할 save() 만들기
    1. src/test/java/hello.hellospring.repository/MemoryMemberRepositoryTest.java
  1. Run save()
      • 기존에 돌리던 거 빨간 사각형 눌러서 끄고 public void save()옆에 초록 삼각형 눌러서 실행하기
  1. save()로 멤버 저장 기능 잘 동작하는지 테스트하기 + import 꿀팁 + 리포지토리에서 store 출력해서 확인
    1. src/test/java/hello.hellospring.repository/MemoryMemberRepositoryTest.java
      에러 (import Assertions)
      • import org.junit.jupiter.api.Assertions; 사용해서 assertThat 사용하려하면 에러 났음
      • 해결) Assertions를 import할 때 assertj로 하기
        • 잘못 넣은 import문 지우고서 Assertions.assertThat … 이 문장의 Assertions에 대해 alt+enter해서 import해주니
        • import org.assertj.core.api.Assertions; 가 import되면서 해결됐음
      • 정리
        • import org.junit.jupiter.api.Assertions;assertEquals 사용에 필요했음
        • import org.assertj.core.api.Assertions;assertThat 사용에 필요했음
          • assertThat이 좀 더 편하게 쓸 수 있게 해줌
        • 두 개 import 동시에 선언하는 건 불가능.
      • Assertions를 alt + enter 해서 static import하면 다음부턴 assertThat 바로 칠 수 있음
      src/main/java/hello.hellospring.repository/MemoryMemberRepository.java (store 출력)
      • store 직접 출력해보기
        • notion image
  1. findByName()으로 이름으로 멤버 찾는 기능 잘 동작하는지 테스트하기
    1. src/test/java/hello.hellospring.repository/MemoryMemberRepositoryTest.java
      • 정상 동작할 때
        • notion image
      • 정상 동작x일 때 : 다른 객체라고 뜸
        • notion image
           
  1. 한 번에 실행 가능
    1. 클래스 레벨에서 돌리거나 hello.hellospring을 통째로 실행가능. (테스트 코드 장점)
      notion image
      • 두 개 같이 도는 모습
  1. findAll() 테스트하기
    1. src/test/java/hello.hellospring.repository/MemoryMemberRepositoryTest.java
  1. 세 개를 한 번에 실행했더니 오류 발생 (중요)
    1. 원인) 다른 테스트가 findByName테스트에 영향을 미쳐서 발생
      notion image
      • 테스트 순서는 보장 안됨 (모든 테스트는 순서와 상관 없이 method별로 따로 동작하게 설계해야함)
      • findByName만 단독 실행할 땐 문제 없었지만,, 순서에 의존적으로 설계해서 발생한 문제임
      • findAll()이 먼저 실행됐더니 findByName했더니 이전에 저장했던 다른 객체가 나와버린 것임.
      해결) 테스트 끝날 때마다 데이터를 깔끔하게 clear 해주어야 함
      notion image
      src/test/java/hello.hellospring.repository/MemoryMemberRepositoryTest.java
      • @AfterEach: method실행 끝날 때마다 어떤 동작을 하는 call back method
      src/main/java/hello.hellospring.repository/MemoryMemberRepository.java
      • 원래였으면 MemoryRepository까지 고쳐야 하는데 테스트에서 만드는 객체의 클래스를 MemoryMemberRepository로 고침으로써 MemoryMemberRepository만 고치면 되게 됨
       
      • 테스트가 수십, 수백 개면 hello.hellospring run하거나 gradlew 띄우고 테스트하면 됨. 다 자동으로 돌려줌

3) TDD: 테스트 코드 작성 후 개발

  • 테스트 주도 개발
 

4. 회원 서비스 개발

1) 회원 서비스

  • 회원 리포지토리와 도메인을 활용해서 실제 비즈니스 로직을 작성하는 부분
  1. 패키지 만들기 (hello.hellospring.service)
  1. 패키지 안에 클래스 만들기 (MemberService.js)
    1. src/main/java/hello.hellospring.service.MemberService.js
  1. 회원 리포지토리 만들기
    1. src/main/java/hello.hellospring.service.MemberService.js
  1. 회원 가입 만들기
    1. src/main/java/hello.hellospring.service.MemberService.js
      람다, throw
      Optional
      • optional이란 거 안에 멤버 객체가 있는 거임. 과거엔 null일 가능성 있으면 if null헀지만, 지금은 optional로 감싸서 반환하면
      • 값 꺼내고 싶으면 result.get 하면 됨. 다만 result.orElseGet() 같은 거 더 많이 사용함. 값이 있으면 꺼내고 값 없으면 default값 넣어 꺼냄
  1. 전체 회원 조회 만들기
    1. src/main/java/hello.hellospring.service.MemberService.js
 

2) 네이밍으로 알아보는 리포지토리와 서비스 차이점

  • 리포지토리: save, findById, findByName, findAll처럼 단순히 저장소에 넣었다 뺐다하는 느낌 듬
    • 개발스럽게 용어 선택해 설계함.
  • 서비스: join, findMembers처럼 비즈니스에 가까운 느낌 듬.
    • 비즈니스에 의존적으로 설계함.
    •  

5. 회원 서비스 테스트

  1. Member Service 클래스에서 ctrl+shift+t해서 Create new test
    1. : 테스트 패키지, 클래스, method 만드는 거 한 번에 하는 법
      notion image
      • findMembers()를 public이 아닌 private으로 작성해둬서 안 떴었음 ㅎ,,
      • Member Service클래스와 똑같은 패키지에 만들어짐
      src/test/java/hello.hellospring.service/MemberServiceTest.java
      • 테스트 method이름은 한글로 바꿔도 됨. build될 때 실제 코드에 포함 안됨
  1. MemberService클래스의 객체 memberService만들기
    1. src/test/java/hello.hellospring.service/MemberServiceTest.java
      • 테스트 method이름은 한글로 바꿔도 됨. build될 때 실제 코드에 포함 안됨
  1. 회원가입 정상 테스트
    1. give, when, then 방법 → 주석 처리해두는 것
      • 테스트는 어떤 상황이 주어졌을 때 이를 실행해서 어떤 결과가 나와야 하는 것임
      • given: 기반하는 데이터, when: 검증하는 것, then: 검증부
      src/test/java/hello.hellospring.service/MemberServiceTest.java
      • 테스트 method이름은 한글로 바꿔도 됨. build될 때 실제 코드에 포함 안됨
      store 출력
      src/main/java/hello.hellospring.repository/MemoryMemberRepositry.java → store 출력
      src/test/java/hello.hellospring.service/MemberServiceTest.java → 회원가입 횟수 추가
      • 결과: 첫 회원가입 전, 후 + 두 번째 회원가입 전, 후 store 내부 모습
        • notion image
  1. 회원가입 예외 테스트
    1. src/test/java/hello.hellospring.service/MemberServiceTest.java
      • 테스트 method이름은 한글로 바꿔도 됨. build될 때 실제 코드에 포함 안됨
  1. Clear 넣기
    1. src/test/java/hello.hellospring.service/MemberServiceTest.java
  1. 같은 memberRepository 객체 쓰게 만들기(DI)
    1. src/test/java/hello.hellospring.service/MemberServiceTest.java → BeforeEach 이용
      src/main/java/hello.hellospring.service/MemberService → 생성자 이용
      • DI: MemberService 입장에서 봤을 때 memberRepository를 직접 new하지 않고 외부에서 넣어주는 것
[스프링 입문] 3. 백엔드 개발