[Spring] 기본편 #2 - 예제 만들기

strongmhk·2023년 4월 11일
0

Spring

목록 보기
2/25
post-thumbnail

1. Spring project 만들기


Spring project를 만들기 위해 가장 먼저 해야할 일은
https://start.spring.io/ <- 이 주소로 들어가는 것이다
이 주소로 들어가면 아래와 같은 화면이 나온다


환경설정

  • Project : Gradle - Groovy
  • Language : Java
  • Spring Boot : 2.7.10 (SNAPSHOT과 M2라고 써져있는 버전은 아직 안정적이지 않은 버전이기 때문에 이 둘을 제외한 가장 최신 버전인 3.0.5를 사용하려 했으나 3.0 이상의 버전을 사용하려면 다음의 요구사항이 충족돼야한다.
    • jdk 버전 17 이상 사용
    • javax 패키지 jakarta로 변경
    • H2 database 2.1.214 버전 사용
  • Group : 임의로 지어주면 됨, 간단하게 Hello라고 하겠다
  • Artifact(=Name) : 이것 역시 프로젝트의 이름 지어주기
  • Dependencies : 아무 설정없이 하면됨


위의 사항들을 설정하고 하단에 GENERATE 버튼을 누르면 zip 파일이 다운로드되고 원하는 디렉토리에 압축을 풀어주고, IDE로 해당 폴더를 열면 아래와 같은 화면이 보여진다. Spring 프로젝트가 잘 생성됐는지 점검해보기위해 아래 절차를 따르면된다.







build.gradle 파일 확인

프로젝트에 대해 여러 설정 내용들이 담겨있는 build.gradle 파일부터 먼저 확인해봐야한다.


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

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') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}
tasks.named('test') {
	useJUnitPlatform()
}

위와 같이 스프링 부트의 버전과 프로젝트 생성시에 지정했던 group, 그리고 jdk 버전이 맞는지 확인해주자











동작 확인

src/main/java/Group/Name/ 디렉토리로 가면 NameApplication.java 파일이 있을 것이다 ( 여기서 Group과 Name은 초기에 설정했던 값이다)
이 파일을 실행해주면 디폴트값만 설정돼있기 때문에 위와 같이 실행되고 바로 종료된다









실행속도 개선

  • 실행속도를 개선하기 위해서는 윈도우 기준 화면 왼쪽 상단의 File>Settings로 들어간다
  • 왼쪽 상단의 검색 창에 gradle을 입력한다
  • Build and run using: Gradle -> IntelliJ IDEA
    Run tests using: Gradle -> IntelliJ IDEA
    로 바꿔준다

자 이제 설정도 대략했으니 직접 예제를 만들어보자!









비즈니스 요구사항과 설계


기획자가 와서 다음의 요구사항들을 알려준다고 생각해보자


  • 회원
    • 회원을 가입하고 조회할 수 있다
    • 회원은 일반과 VIP 두 가지 등급이 있다
    • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동 가능(미확정)
  • 주문과 할인 정책
    • 회원은 상품을 주문할 수 있다
    • 회원 등급에 따라 할인 정책을 적용할 수 있다
    • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용(나중에 변경 될 수도 있음..)

요구사항들을 보면 회원 데이터, 할인 정책 같은 부분은 지금 결정하기 어려운 부분이다. 그렇다고 이 사항들이 결정되기 전까지 무기한 기다릴 수는 없는 노릇이다..

앞의 포스팅에서 언급했듯이 역할과 구현으로 나누어 역할을 인터페이스를 만들어 역할을 정하고 구현체를 언제든지 갈아끼울 수 있도록 설계하면 된다.

참고로 앞에서 스프링 부트 환경설정만 진행한 것이고, 이번 포스팅은 스프링없이 순수 자바로만 진행한다!









회원 도메인 설계

  • 회원 도메인 요구사항
    • 회원을 가입하고 조회할 수 있다
    • 회원은 일반과 VIP 두 가지 등급이 있다
    • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동 가능(미확정)

회원 도메인 요구사항을 바탕으로

  • 회원 도메인 협력 관계 다이어그램( 기획자들이 확인 )
  • 회원 클래스 다이어그램( 개발자들이 구현하기 위해 그림, 정적)
  • 회원 객체 다이어그램( 클래스가 아닌 인스턴스가 참조하는 메모리 구조 , 동적 -> 인스턴스가 생성되어야 하기 때문)








회원 도메인 개발


회원 등급

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;

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


회원 저장소


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

public interface MemberRepository {

    void save(Member meber);

    Member findById(Long memberId);
}

  • 메모리 회원 저장소 구현체
package Hello.core.member;

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

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 memeberId); // 조회
}



회원 서비스 구현체

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

  • 회원가입 서비스 : 가입과 조회기능 포함








회원 도메인


  • 회원가입 main
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); // Long 타입이라서 L붙여줘야함
        memberService.join(member);

        Member findMember = memberService.findMember(1L);
        System.out.println("new member = " + member.getName());
        System.out.println("findMember = " + 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();
    // 인터페이스와 구현에 모두 의존하고있음(DIP 위반)

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

문제점

  • 다른 저장소로 변경할 때 OCP 원칙을 준수하지 못함
  • DIP를 지키지 못함(인터페이스와 구현에 모두 의존하고 있음)






주문과 할인 도메인 설계

  • 주문과 할인 정책
    • 회원은 상품을 주문할 수 있다
    • 회원 등급에 따라 할인 정책을 적용할 수 있다
    • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용(나중에 변경 될 수도 있음..)





도메인 협력 관계 다이어그램

  1. 주문 생성 : 클라이언트는 주문 서비스에 주문 생성을 요청
  2. 회원 조회 : 할인을 위한 회원 등급이 필요, 주문 서비스는 회원 저장소에 접근해 회원을 조회(회원 id 이용)
  3. 할인 적용 : 주문 서비스가 회원 등급에 따른 할인 여부를 할인 정책에 넘겨줌
  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{

    private int discountFixAmount = 1000; // 1000원 할인
    @Override
    public int discount(Member member, int price) {
        if(member.getGrade() == Grade.VIP){ // VIP만 1000원 (정액) 할인
            return discountFixAmount;
        } else {
            return 0;
        }
    }
}

주문 엔티티


package Hello.core.order;

public class Order {

    private Long memberId;
    private String itemName;
    private int itemPrice;
    private int discountPrice;

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

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

    @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); // order에 영향을 미치지 않고 discount에 대해서만 바꾸면됨
        return new Order(memberId, itemName, itemPrice, discountPrice);
    }


}




주문과 할인 도메인 실행과 테스트


package Hello.core.order;


import Hello.core.member.Grade;
import Hello.core.member.Member;
import Hello.core.member.MemberService;
import Hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

public 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);
    }
}
profile
저 커서 개발자가 될래요!

0개의 댓글