본문 바로가기

Backend/Spring

[Spring] 순수 자바 예제 2) - 회원 도메인 개발&테스트

728x90

 

  • 프로젝트 생성
  • 비즈니스 요구사항과 설계
  • 회원 도메인 설계
  • 회원 도메인 개발
  • 회원 도메인 실행과 테스트
  • 주문과 할인 도메인 설계
  • 주문과 할인 도메인 개발
  • 주문과 할인 도메인 실행과 테스트

 

 

회원 도메인 개발

 

이전 게시글의 설계대로 개발을 해볼 차례다.

정적임 

 

인터페이스

'어떤 역할'을 수행할 것인지 명시하는 인터페이스

회원관련 서비스의 역할을 하는 MemberService Interface

회원관련 데이터를 저장하고, 데이터에 접근하여 처리해주는 역할을 하는 MemberRepository Interface

 

구현체

이 역할들을 실제로 구현해낸 구현체

MemberSerivceImpl

MemoryMemberRepository

 

를 구현해 보도록 하자.

 

 

 

member

먼저 회원과 관련된 내용만을 모아두기 위해 hello.core하위에 member package를 만든다.

이제 이 하위에 회원과 관련된 인터페이스, 클래스 등을 작성해본다. 

 

 

 

Grade & Member(Entity)

 

Grade

다음 요구사항을 만족시키기 위해, Grade(Basic, VIP)

  • 회원은 일반과 VIP 두 가지 등급이 있다.

서로 관련된 상수(Basic, VIP)이며, 타입이 공통적으로 Grade 이기 때문에 enums로 선언해준다.

 

enum Grade 

package hello.core.member;


public enum Grade {
    BASIC,
    VIP
}

 

 

 

다음으로 회원 Entity를 만들어준다. 

회원은 3가지 attribute를 가진다.

요구사항에 있는 name, grade 와 시스템상에서 필요한 id이다.

 

추가로 생성자와 getter/setter를 만들어준다.

 

Member

package hello.core.member;


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


    public Member(Long id, String name, Grade grade){
        this.id = id;
        this.name = name;
        this.grade = grade;
    }


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

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }
}

 

 

 

인터페이스(역할)

 

 

설계대로 

  • MemberService : 실제 회원이 하려는 작업을 수행하는 역할
  • MemberRepository :  db에 저장된 회원관련 데이터에 접근하여 특정 행위를 수행하는 역할

두가지 인터페이스를 필요한 역할을 넣어 만들어본다.

 

MemberRepository
package hello.core.member;

public interface MemberRepository {

    //회원을 db에 저장하는 기능
    void save(Member member);


    //id를 이용해 회원을 찾는 기능
    Member findById(Long memberId);

}

 

 

MemberService

설계를 보면, 회원은 회원가입 / 회원조회를 할 수 있어야한다.

즉, 두가지 기능을 하는 메소드들이 필요하다.

package hello.core.member;

public interface MemberService {

    //회원가입 
    void join(Member member);

    //회원 조회 
    Member findMember(Long memberId);

}

 

 

 

 

 

구현체(Class)

 

 

사실 인터페이스와 구현체의 패키지를 분리하는 것이 좋으나, 여기서는 예제의 단순함을 위해 같은 패지키에 구현한다.

 

MemoryMemberRepository

저장소가 확정되지 않았으므로, 간단한 Memory레포지토리를 만든 것이며, Map을 이용한다. 

테스트용으로 

package hello.core.member;
import java.util.HashMap;
import java.util.Map;
public class MemoryMemberRepository implements MemberRepository {


    // 저장소를 만듬 -> 저장소 확정이 안되었기에 임시로 map으로 구현
    private static Map<Long, Member> store = new HashMap<>();


    //인터페이스를 구현한 것이므로 Override
    @Override
    public void save(Member member) {
        store.put(member.getId(), member); //데이터를 store에 저장
    }

    @Override
    public Member findById(Long memberId) {

        return store.get(memberId); //id를 이용해 db에서 조회함
    }

}

 

 

 

MemberServiceImpl

어떤 인터페이스에 대하여, 구현체가 하나 존재하는 경우 "인터페이스이름+Impl"이라는 이름을 주로 사용한다.

package hello.core.member;


public class MemberServiceImpl implements MemberService {

    // " 인터페이스 = 구현체 " 형태
    // 의존관계에는 구현체가 아닌 인터페이스를 의존해야하며,
    // 구현객체가 없으면 nullpointException이므로 구현체를 선택해줘야한다.
    private final MemberRepository memberRepository = new MemoryMemberRepository();

    @Override
    public void join(Member member) {
        memberRepository.save(member); //Repository에서 save함수를 입력받은 member매개변수를 가지고 실행
    }


    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId); //Repository에서 findById함수를 입력받은 memberId 매개변수를 가지고 실행
    }



}

개발은 이것으로 완료

 

 

 

 

 

회원 도메인 실행과 테스트

 

이제 위의 개발이 제대로 완료되었는지 확인할 차례

인스턴스 끼리의 참조관계를 나타냄 -> 동적임! 런해서 실제 어떤 인스턴스를 참조하는지 달라지기 때문

이 그림대로 흘러가야 한다.

런타임에서 클라이언트는 회원 서비스를 사용해야하고, 회원 서비스는 회원 저장소를 사용해야한다.

특히, 그중에서도 서비스는 지금 구현해둔(MemberServiceImpl) 을 사용해야하며, 회원 저장소로는 (MemoryMemberRepository)를 사용해야한다. 

 

 

 

 

순수 자바 코드로 테스트


 

core의 하위에 다음과 같은 JavaClass를 생성해준다.(클라이언트)

 

MemberApp

클라이언트를 만들어  MemberService 인터페이스를 의존시킨다. (연결은 구현체와 해주어야한다)

그 후 회원가입/회원조회 두가지 기능을 테스트해본다. 

package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;

public class MemberApp {


    //psvm (단축)
    public static void main(String[] args) {

        MemberService memberService = new MemberServiceImpl(); // 인터페이스 = 구현체


        // 맴버 객체를 하나 생성
        Member member = new Member(1L, "memberA", Grade.VIP);


        //생성한 멤버를 가입시키기 : MemberService이용
        memberService.join(member);


        //가입시킨 멤버를 검색하기
        Member findMember = memberService.findMember(1L);

        //soutv : 단축 
        System.out.println("new member = " + member);
        System.out.println("findMember = " + findMember);




    }



}

 

main을 실행시키면 다음과 같이 결과가 나온다.

new member = memberA
find Member = memberA

Process finished with exit code 0

같은 이름이 반환된 것으로 보아 둘다 제대로 실행된 것을 알 수 있다.

 

이는 순수한 자바 코드로 만으로 개발한 것이다. (스프링 하나도 안씀)

 

 

스프링 Junit사용 

 

 

테스트 코드는 최종 빌드될 때 제외되며, 테스트 만을 위해 스프링에서 제공하는 라이브러리이다. 

main에서와 유사하게 디렉토리를 구성한다.

하위에 MemberSerivceTest라는 클래스를 만들고, Junit을 이용해 다음과 같이 테스트코드를 작성한다. 

 

 

MemberServiceTest

package hello.core.member;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

public class MemberServiceTest {

    MemberService memberService = new MemberServiceImpl(); // 인터페이스 = 구현체

    @Test
    void join(){
        //given
        // 맴버 객체를 하나 생성
        Member member = new Member(1L, "memberA", Grade.VIP);


        //when
        memberService.join(member);
        Member findMember = memberService.findMember(1L);


        //then
        Assertions.assertThat(member).isEqualTo(findMember);
        

    }




}

2021.09.07 - [Backend/Spring] - [Spring] 회원관리 예제 - 2) 테스트 케이스 작성(검증)

 

[Spring] 회원관리 예제 - 2) 테스트 케이스 작성(검증)

회원 관리 예제 작성해보기 - 백엔드 개발 비즈니스 요구사항 정리 회원 도메인과 레포지토리 제작 회원 레포지토리 테스트 케이스 작성 회원 서비스 개발 회원 서비스 테스트 - Junit이라는 테

iagreebut.tistory.com

자세한 내용에 대해서는 여기 글을 참고 

 

 

 

문제점

 

 

위의 코드들의 문제점 : 

OCP와 DIP를 위반하고 있다. 

인스턴스 끼리의 참조관계를 나타냄 -> 동적임! 런해서 실제 어떤 인스턴스를 참조하는지 달라지기 때문

 

우리는 이를 구현하기 위해, 

클라이언트(MemberApp)을 만들고, 클라이언트가 회원서비스를 사용하게 하기 위해서 다음과 같이 

인터페이스 = 구현체로 불러와 이를 참조해 사용하였다. 

public class MemberApp {


    //psvm (단축)
    public static void main(String[] args) {

        MemberService memberService = new MemberServiceImpl(); // 인터페이스 = 구현체
        
        
        
        ...(생략)
        
}

 

구현체를 의존했으므로, 이는 DIP를 위반했으며,

구현체가 변경될 때 클라이언트에서 이 코드를 수정해야하므로, OCP원칙 또한 위반했다. 

 

자세한 사항은 여기 참고

2021.09.19 - [Backend/Spring] - [Spring] 객체 지향 설계 원칙 - SOLID

 

[Spring] 객체 지향 설계 원칙 - SOLID

면접 질문으로도 가끔 나오는 내용 SOLID 좋은 객체 지향 설계의 5가지 원칙 - 로버트 마틴(클린코드 저자) SRP: 단일 책임 원칙(single responsibility principle) OCP: 개방-폐쇄 원칙 (Open/closed principl..

iagreebut.tistory.com

 

이 문제점은 차후 스프링으로 코드를 변경하면서, 필요성을 깨닫고 고쳐나갈 것 

728x90