본문 바로가기

WINK-(Web & App)/Spring Boot 스터디

[Spring Boot 스터디] 이지원 #2 주차 - 섹션 3

반응형

회원 domain / repository / service 구축하기

 

Domain

일종의 객체 모델링이라 볼 수 있을 것 같다.

 

Repository

서비스 구축을 위해 보통 DB(데이터베이스)가 필요하지만 설계 및 개발 과정에서 DB가 준비되지 않았거나 결정되지 않았을 경우가 있다. 이를 위해 메모리를 사용하는 가상의 데이터베이스를 만들고 추후 DB와의 호환을 위해 미리 Interface를 정의하고 설계한다.

 

Service

실제 서비스에 사용되는 비즈니스 로직이 포함된다. 예를 들어 회원가입 시 중복 아이디의 생성을 방지하거나 특정 시간동안 작성할 수 있는 게시글을 제한하는 등의 작업이 포함된다.

 

 

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

 

멤버 도메인에는 각각 ID와 name의 Getter Setter가 있다.

 

 

 

Repository 생성

이 Member을 저장하고 불러올 저장소를 구축한다. 실제로는 DB에 저장되겠지만 개발 과정에서 빠른 테스트를 위해 가상의 MemoryRepository를 생성한다.

 

MemberRepository Interface

 

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

 

 

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) {
        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();
    }
}

 

먼저 추후 DB와의 호환을 위해 MemberRepository라는 Interface를 작성한다. 이후 이 Interface를 구현한 MemoryMemberRepository를 작성하여 데이터의 save, load, search를 할 수 있는 가상 DB의 역할을 구현한다.

 

 

 

Service 생성

이제 실제 Member를 생성하고 저장하는 모든 과정을 처리할 service를 구현한다. 이 과정은 실제 비즈니스 로직을 포함한다.

 

package hello.hellospring.service;

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

import java.util.List;
import java.util.Optional;

public class MemberService {
    private final MemberRepository memberRepository;

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

    public Long join(Member member) {
        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);
    }
}

 

 

 

Test Case 실행

이제 작성한 코드가 정상적으로 작동하는지, 오류는 없는지를 판단해야 한다. 이를 위해 Test폴더 내부에 여러 테스트케이스를 작성하고 실행한다.

 

일반적으로 테스트 파일은 테스트하려는 파일의 이름에 Test를 붙여서 만든다.

 

각각의 파일에 대해 테스트를 진행하게 된다.

MemoryMemberRepository의 test파일 예시는 아래와 같다.

 

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.*;

public 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();
        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);
    }
}

 

@Test가 붙은 메소드들은 모두 JUnit을 이용하여 테스트를 진행할때 실제 실행되는 코드이다. 이 코드들은 각각 별도의 테스트 함수이다. 테스트 실행 이후 각 테스트가 성공했는지, 실패했는지 여부를 확인할 수 있다.

 

하지만 모든 테스트케이스는 한번의 런타임에서 실행되기 때문에 이전에 다른 테스트가 실행되었다면 메모리가 초기상태가 아닐 수 있다.

이를 위해 각 테스트를 실행한 이후 다시 초기화 작업을 할 수 있도록 구현해야하며 @AfterEach를 이용해 목표를 달성할 수 있다.

 

@AfterEach가 붙은 메소드들은 각각의 테스트가 하나씩 실행된 이후 호출된다. 이를 통해 테스트를 진행하여 변경된 메모리를 다시 다른 테스트가 진행될 수 있는 상태로 적절히 처리할 수 있다.

반응형