jae_coding

[Spring core] 스프링 회원정책 프로젝트 (순수 java) 본문

Spring, java/Spring_core

[Spring core] 스프링 회원정책 프로젝트 (순수 java)

재코딩 2022. 8. 25. 16:25
반응형

 

본 리뷰는 인프런 김영한씨의 스프링 핵심원리 - 기본편 리뷰를 한 포스팅입니다.

 

목차

  • 프로젝트 생성 및 설정
  • 프로젝트 요구사항과 설계
  • 회원 도메인 설계 및 개발
  • 주문과 할인 도메인 설계 및 개발

 

1. 프로젝트 생성 및 설정

스프링 부트 스타터(https://start.spring.io)

 

이 외의 스프링 설정은 하지 않은채 GENERATE 해주었습니다.

아마 처음에는 프로젝트를 열때, 라이브러리들을 설치해주기때문에 처음에는 오래걸립니다.

 

Build.gradle

plugins {
	id 'org.springframework.boot' version '2.7.3'
	id 'io.spring.dependency-management' version '1.0.13.RELEASE'
	id 'java'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

CoreApplication을 실행하면 정상적으로 실행이 된 것을 확인할 수 있습니다.

Preference > gradle 설정변경

 

2. 비지니스 요구사항과 설계

회원

  • 회원 가입
  • 회원 조회
  • 회원은 일반회원과 VIP회원 두가지 등급이 존재
  • 회원데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동 (아직은 미확정)

주문과 할인 정책

  • 상품 주문
  • 회원 등급에 따른 할인 정책 적용
  • 모든 VIP는 1000원씩 고정적으로 할인을 적용 (추후에 변경가능)
  • 할인 정책 변경 가능성은 높다.

가정: 객체 지향을 설계를 이용하여 인터페이스를 만들고 언제든지 구현체를 갈아끼울 수 있도록 설계하면 될 것이다.

 

 

3. 회원 도메인 설계 및 개발

 

회원 도메인 협력 관계

 

회원 클래스 다이어그램 (구현)

 

회원 객체 다이어그램

: 객체 간의 메모리 참조

회원서비스: MemberserviceImpl

 

회원엔티티

  • 회원 등급
package hello.core.member;

public enum Grade {
    BASIC, VIP
}
  • 회원
package hello.core.member;

public class Member {

    private Long id;
    private String name;
    private Grade grade;

    //생성자 만들기 (커멘드 N)
    public Member(Long id, String name, Grade grade) {
        this.id = id;
        this.name = name;
        this.grade = grade;
    }

    //Getter, Setter 만들기
    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;
    }
}

회원저장소

  • 회원저장소 인터페이스
package hello.core.member;

public interface MemberRepository {

    void save(Member member);

    Member findById(Long memberId);

}
  • 회원저장소 구현체 (메모리)
package hello.core.member;

import java.util.HashMap;
import java.util.Map;


//db를 연결하지 않고 메모리를 사용하는 것
public class MemoryMemberRepository implements MemberRepository{
    private static Map<Long, Member> store = new HashMap<>();

    @Override
    public void save(Member member) {
        store.put(member.getId(), member);
    }

    @Override
    public Member findById(Long memberId) {
        return store.get(memberId);
    }
}

회원서비스

  • 회원서비스 인터페이스
package hello.core.member;

public interface MemberService {
    //회원가입
    void join(Member member);

    //회원조회
    Member findMember(Long memberId);
}
  • 회원서비스 구현체
package hello.core.member;

//인터페이스의 구현체 구현
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();

    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

이런식으로 인터페이스와 구현체를 분리하여 인터페이스에서 어떠한 구현체를 이용할 것이라는 것을 명시해주면 DB가 확정이 되어있지 않고 기능이 확정이 되지 않은 상황에서 개발이 가능해 모두가 즐거운 개발환경을 만들어 줄 수 있습니다.

 

 

스프링없이 테스트

프로젝트 메인과 같은 패키지에 새로운 클레스를 하나 만들어주어 기능을 테스트

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 {
    public static void main(String[] args) {
        MemberService memberService = new MemberServiceImpl();
        Member member = new Member(1L, "memberA", Grade.VIP);
        memberService.join(member);
        Member findMember = memberService.findMember(1L);
        System.out.println("new member = " + member.getName());
        System.out.println("find Member = " + findMember.getName());
    }
}

테스트 결과

결과적으로 멤버를 가입하고 가입한 멤버와 우리가 가입하려고한 멤버가 동일한 것을 확인할 수 있다.

 

 

Junit으로 테스트

 

테스트코드 경로는 위와 같습니다.

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(member.getId());

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

    }

}

결과

녹색불이 정상적으로 작동하는 것을 확인하시면 테스트가 정상적으로 통과되었다는 것입니다. 이런식으로 Junit을 확인하면 매번 테스트 코드를 입력하고 반복작업을 줄여주는 것을 방지할 수 있습니다. 또한 눈으로 검증하지 않고 오류가 발생하였을 때는 오류가 어떤 것인지에 대해서도 알려줍니다! 요즘 테스트 코드 작성은 필수 !

 

 

4. 주문과 할인 도메인 설계 및 개발

주문 도메인

 

 

주문 도메인 클래스 다이어그램

 

 

 

주문 도메인 객체 다이어그램

이때 메모리가 아닌 DB에서 조회하고 할인을 지원해주는 서비스를 변형하지 않고 재사용이 가능하다.

 

 

할인 정책

  • 할인 정책 인터페이스
package hello.core.discount;

import hello.core.member.Member;

public interface DiscountPolicy {

    /**
     * @return 할인 대상 금액
     */
    int discount(Member member, int price);
}
  • 정액 할인 정책 구현체
package hello.core.discount;

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

public class FixDiscountPolicy implements DiscountPolicy{

    // 1000원 할인
    private int discountFixAmount = 1000;

    @Override
    public int discount(Member member, int price) {
        //vip면 1000원 아니면 x
        if(member.getGrade() == Grade.VIP){
            return discountFixAmount;
        }
        else{
            return 0;
        }
    }
}

주문

  • 주문 엔티티
package hello.core.order;

public class Order {
    private Long memberId;
    private String itemName;
    private int itemPrice;
    private int discountPrice;

    //Construct
    public Order(Long memberId, String itemName, int itemPrice, int discountPrice) {
        this.memberId = memberId;
        this.itemName = itemName;
        this.itemPrice = itemPrice;
        this.discountPrice = discountPrice;
    }

    public int calculatePrice(){
        return itemPrice - discountPrice;
    }

    //Getter & Setter
    public Long getMemberId() {
        return memberId;
    }

    public void setMemberId(Long memberId) {
        this.memberId = memberId;
    }

    public String getItemName() {
        return itemName;
    }

    public void setItemName(String itemName) {
        this.itemName = itemName;
    }

    public int getItemPrice() {
        return itemPrice;
    }

    public void setItemPrice(int itemPrice) {
        this.itemPrice = itemPrice;
    }

    public int getDiscountPrice() {
        return discountPrice;
    }

    public void setDiscountPrice(int discountPrice) {
        this.discountPrice = discountPrice;
    }

    //출력을 편리하게 toString 만들어줌
    @Override
    public String toString() {
        return "Order{" +
                "memberId=" + memberId +
                ", itemName='" + itemName + '\'' +
                ", itemPrice=" + itemPrice +
                ", discountPrice=" + discountPrice +
                '}';
    }
}
  • 주문 서비스 인터페이스
package hello.core.order;

public interface OrderService {
    Order createOrder(Long memberId, String itemName, int itemPrice);
}
  • 주문 서비스 구현체
package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;

public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        //회원 조회
        Member member = memberRepository.findById(memberId);
        //할인 정책 가격 조회
        int discountPrice = discountPolicy.discount(member, itemPrice);

        //주문 반환
        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

 

Junit으로 테스트

package hello.core.service;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

class OrderServiceTest {
    MemberService memberService = new MemberServiceImpl();
    OrderService orderService = new OrderServiceImpl();
    @Test
    void createOrder() {
        long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);
        Order order = orderService.createOrder(memberId, "itemA", 10000);
        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }
}

 

테스트 결과

만약 vip가 아닌 basic회원이라면 오류가 발생한다.

반응형
Comments