비즈니스 요구사항 ( 최대한 간단히 개발 )

  1. 데이터: id, name
  2. 기능: 회원가입, 조회
  3. 아직 어떤 db를 쓸 지 정하지 않았음 -> interface로 구현

김영한 강사님 pdf에서 캡쳐

 

 

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

김영한 강사님 pdf에서 캡쳐

  • 도메인: db에 저장할 객체
  • 리포지토리: db에 접근, 도메인 객체를 db에 저장하고 관리
  • 서비스: 비즈니스 기능 로직(비즈니스 요구사항에서 요구한)
  • 컨트롤러: MVC의 Controller

[main 코드 작성]

구조

 

1. domain/Member

package hello.helloSpring.domain;

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

DB에 저장할 멤버를 class로 구현한다.

id는 시스템에서 정해주고 name은 회원가입할 때 사용자가 입력한다.

 

2. repository/MemberRepository, repository/MemoryMemberRepository

//MemberRepository
package hello.helloSpring.repository;

import java.util.List;
import hello.helloSpring.domain.Member;
import java.util.Optional;

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    List<Member> findAll();
}

MemoryMemberRepository에서 사용할 메서드를 적어준다.

#MemoryMemberRepository
package hello.helloSpring.repository;
import hello.helloSpring.domain.Member;
import java.util.*;

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

    @Override
    public Optional<Member> findById(Long id) {
    	//get을 했을때 null일 수 있어서 Optional로 감싸고 return 한다.
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
    	//변수 store의 value는 Member 객체이기 때문에 다 돌면서 name과 같은 member가 있는지 확인
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
    	// store의 모든 member를 List로 반환
        return new ArrayList<>(store.values());
    }

    public void clearStore(){
    	// store를 깨끗이 비운다~
        store.clear();
    }
}

 

 

여기서 꿀팁이 있다면 implements MemberRepository를 쓰고 alt + Enter를 누르고 Implement methods를 누르면

알아서 생성해야할 메서드들의 틀을 잡아준다. 

비어있는 메서드들을 알아서 써준다!!

 

3. service/MemberService

package hello.helloSpring.service;

import hello.helloSpring.domain.Member;
import hello.helloSpring.repository.MemberRepository;

import java.util.*;

public class MemberService {
    private final MemberRepository memberRepository;

    //Dependency Injection
    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);
    }
}

Dependency Injection이란?

쉽게 말하면

public class MemberService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();
}

선언과 동시에 생성을 해주는게 아니라

public class MemberService {
    private final MemberRepository memberRepository;

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

생성자를 통해 인자로 받아서 넣어주는 방식이다.

MemberService 입장에서는 injection되는 입장이라 Dependency Injection이라고 한다.

 

validateDuplicateMember 메소드에서 드래그한 코드와 안한 코드가 기능적으로는 같다.

하지만 Optional로 바로 뽑아서 사용하는(드래그한 코드) 건 별로 안좋다고 한다.

따라서 체이닝 메서드를 이용하여 코드를 작성했다.


[테스트 코드 작성]

구조

테스트 코드는 service와 repository를 테스트하기 위해 작성하였다.

테스트하기위한 클래스에 Test라는 문자열을 붙여서 이름을 짓는게 일반적이라고 한다.. 절대적인가? 하여튼!!

테스트 코드를 작성할 때, 어떤 상태가 주어지고->given(), 어떤 행동을 할 때->when(), 그러면 이런 결과가 나와야한다-> then() 를 작성하면 된다고 한다. (아래 코드 참조)

 

1. repository/MemoryMemberRepositoryTest

package hello.helloSpring.repository;

import hello.helloSpring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.*;

class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    // 각 test를 마치고 항상 호출되는 메서드
    @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(); //get()을 통해 Optional에서 Member를 뽑아냄
        assertThat(member).isEqualTo(result);
        //Assertions.assertEquals(member, result); -> static으로 import 해줘서 윗 줄처럼 사용가능
    }

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

}

 

2. service/MemberServiceTest

package hello.helloSpring.service;

import hello.helloSpring.domain.Member;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertThrows;

import hello.helloSpring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.*;


class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

	// 각 test가 시작되기 전 호출되는 메서드
    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

	//// 각 test를 마치고 항상 호출되는 메서드
    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }

    @Test
    void join() {
        //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()); //member.getName()대신 member도 가능
    }

    @Test
    public void 중복_회원_예외() { //test코드는 메서드 이름을 한글로 해도 무방하다고 한다!!
        //given(같은 이름을 가진 회원이 존재한다면)
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        //when(join할 때 중복 회원 검증인 validateDuplicateMember할 때 터져야 정상)
        memberService.join(member1);
        // 원래 exception error는 try-catch로 잡는데, 이걸 편하게 해주는게 assertThrows()
//        try {
//            memberService.join(member2);
//        }catch (IllegalStateException e){
//            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        }
        // 에러 메세지 비교를 통한 test
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join((member2)));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

        //then

    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

 

IntelliJ 꿀팁 단축어

  • alt + insert -> getter, setter, constructor 등 자동 생성
  • ctrl + alt + m -> 메소드 추출하기
  • shift + F6 -> 같은 단어 찾고 바꿀 수 있음