주노 님의 블로그

[Spring] 4. 회원관리 예제 (비즈니스 요구사항 정리) 본문

공부/Spring

[Spring] 4. 회원관리 예제 (비즈니스 요구사항 정리)

juno0432 2024. 7. 28. 14:43

시작하기에 앞서

아래 내용은 김영한님의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB접근기술에서 발췌한 내용입니다.

 

 

강의자료 일부를 발췌하는건 괜찮다고 하셨다.

무료강의로 지식을 공유해주신 김영한님께 다시한번 감사의 인사를 올리겠습니다

(_ _) (유료도 샀어요!)

 

회원 관리 예제 비즈니스 요구사항

데이터 : 회원ID, 이름

기능 : 회원 등록, 회원 조회

아직 데이터 저장소가 선정되지 않음(가상의 시나리오)

출처 : 인프런 김영한 지식공유자님 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB접근기술 섹션 3 비즈니스 요구사항 정리 발췌

 

컨트롤러 : 웹 MVC 컨트롤러 역할을 한다

>> 사용자의 요청을 받고, 그 요청을 처리한 후, 적절한 응답을 제공.

서비스 : 핵심 비즈니스 로직 구현

>> 데이터 처리 로직

리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리

>> 데이터베이스에 접근하여, 데이터를 저장하거나 불러오는 역할

도메인 : 비즈니스 도메인 객체, 예)회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장되고 관리됨

>> 데이터베이스에 저장되는것들.

출처 : 인프런 김영한 지식공유자님 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB접근기술 섹션 3 비즈니스 요구사항 정리 발췌

 

아직 데이터 저장소가 선정되지 않아서 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계

데이터 저장소는 RDB(관계형데이터베이스), NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정

개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용

 

도메인 - 데이터베이스에 저장되는것 엔티티라고 생각하면 된다!

 

package springProject.hello_spring.member_management.domain;

public class Member {
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

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

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

 

요구사항에 정의된 id와 name을 정의하고

그에맞는 getter와 setter를 정의한다

 

리포지토리 :데이터베이스에 접근, 저장, 변경등을 수행한다

package springProject.hello_spring.member_management.repository;

import springProject.hello_spring.member_management.domain.Member;

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

public interface MemberRepository {
    //회원 저장
    Member save(Member member);
    //특정 id를 가진 도메인을 조회한다
    Optional<Member> findById(Long id);
    //특정 이름을 가진 도메인을 조회한다.
    Optional<Member> findByName(String name);
    //저장된 모든 내용을 불러온다
    List<Member> findAll();
}

 

package springProject.hello_spring.member_management.repository;

import springProject.hello_spring.member_management.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) {
        //store에서 하나하나 꺼내라.
//        return store.get(id);
        //없으면 어떻게>>? optional로 감싸자
        return Optional.ofNullable((store.get(id)));
    }

    @Override
    public Optional<Member> findByName(String name) {
        //store에서, member에서 위 파라미터에서 넘어온 name과 같다면 반환한다.
        //findAny는 하나만 찾는것 있다? 뒤를 조사 안하고 나오기..
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        //store에 있는 member들을 모두 꺼낸다.
        return new ArrayList<>(store.values());
    }
}

 

 

여기서 한번 코드가 잘 돌아가는지 테스트 해볼 수 없나??

테스트 케이스를 추가하면 된다!

테스트 케이스 사용법

 

https://juno0432.tistory.com/29

 

 

회원 서비스 개발

서비스는 비즈니스의 핵심 로직을 구현하는 단계이다

기능인 회원 등록과 회원 조회를 구현해본다.

 

먼저 service 패키지에 MemberService를 만들고.

 

public Long join(Member member)
{}

회원가입을 하는 join을 만들어준다

 

 
//조건 추가 : 같은 이름이 있는 중복 회원은 안된다!
Optional<Member> result = memberRepository.findByName(member.getName());
//기존 멤버가 있으면, if null이 아니면<< 아래 에러를 던져준다
// null일 가능성이 있으면 요즘은 optional로 감싼다. result.orElseGet() << 있으면? 없으면?
result.ifPresent(m -> { throw new IllegalStateException("이미 존재하는 회원이다"); });

일단 null이 들어갈 수 있으면 optional로 감싼다

회원가입을 하는데 동일한 아이디가 많으면 안되니까 조정을해준다

 

ifPresent<< 를 사용한다

만약에 있다면 익셉션을 던져준다

 

private void validateDuplicateMember(Member member) {
    memberRepository.findByName(member.getName())
                    .ifPresent(m-> {
                        throw new IllegalAccessException("이미 존재하는 회원입니다..");
                    });
}

 

가독성있게 메서드를 분리해준다.

데이터에서 findByName을 하여 지금 등록된 id와 데이터베이스에 아이디가 존재하면 오류를 반환해준다

 

public List<Member> findMembers()
{
    memberRepository.findAll();
}

 

전체 회원 조회는 findAll()을 사용한다

 

public Optional<Member> findOne(Long memberId)
{
    return memberRepository.findById(memberId);
}

 

한 회원 조회는 위와 같이 설계한다.

 

Service 테스트하기

repository는 직접 폴더를 생성해서, 클래스를 생성했지만 간단한 기능이 있다

윈도우 기준 ctrl + shift + t를 입력하면

아래와 같은 창이 뜬다

 

 

모든 메서드를 테스트 해야하니 다 체크하고 ok를 누르면

 

package springProject.hello_spring.member_management.service;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    @Test
    void join() {
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

 

위와같이 자동으로 생성해준다.

 

테스트 에서는 메서드 이름을 한글로 설정해도 된다.

빌드할때 들어가지 않을 뿐더러 테스트용이니까 이해하기 쉽게쉽게..

 

그리고 service test코드를 작성할때 Given - when - then 패턴을 사용하여 구현하면 좋은데

 

Given : 테스트의 사전 조건 설정

Wehn : 테스트할 실제 동작 수행

Then : 테스트 결과를 검증하는 부분.

으로 구성된다

@Test
void 회원가입()
{
    //given - when - then패턴을 사용하면 가독성과 유지보수가 좋다!
    //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());
}

 

위 예제를 보면

회원가입 로직을 테스트하는데

given에서는 member를 세팅해준다

when에서는 회원가입을 수행해준다

then에는 원하는 결과와 실제 결과를 비교해준다

 

그런데 테스트 에서는

잘 실행되는지 뿐만아니라

예외사항에서 나는 대처도 해야한다.

 

@Test
    public void 중복_회원_예외(){
        //given
        Member member1 = new Member();
        member1.setName("Spring");

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

        //when >> validate에서 걸려야함
        memberService.join(member1);
//        try {
//            memberService.join(member2); //실행이된다면
//            fail(); // 이쪽으로 넘어갈 일이 없음!
//        }catch (IllegalArgumentException e){
//            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원이다");
//        }
        //try catch 보다 더 좋은 방법
        IllegalStateException e =
                assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원이다");

        //then
        //예외 처리가 되어야함..
    }

 

만약 중복된 이름이 등록이 되었을 시, 에러를 표시하거나 처리해야한다

위 try-catch문과 아래 IllegalState~은 같은 예이다

 

 

 

실행을 하면 정상적으로 처리되는 것을 볼 수 있다

에러가 발생했을텐데 왜 정상처리냐!

assertThat은 기댓값과, 실제값의 일치 여부를 계산하기때문에

익셉션 메세지와 내가 지정한 메세지가 같으면 정상이라는 뜻이다.

 

 

 

 

물론 전체를 테스트하면 똑같은 오류가 뜰 것이다..

중복 회원 예외에서 spring을 등록하였고, 회원가입에서 또 등록을 하고 있다.

 

//어떤 행동이 끝나면 실행되는 메서드
@AfterEach
public void afterEach()
{
    memberRepository.clearStore();
}

 

똑같이 한 메서드의 실행이 끝나면 데이터를 초기화해준다.