2023. 9. 16. 16:57ㆍSpring/Spring 김영한
📌 목차 (이번 챕터는 예제 만들기이다.)
- 프로젝트 생성
- 비즈니스 요구사항과 설계
- 회원 도메인 설계
- 회원 도메인 개발
- 회원 도메인 실행과 테스트
- 주문과 할인 도메인 설계
- 주문과 할인 도메인 개발
- 주문과 할인 도메인 실행과 테스트
👊 가봅시다!..
❓프로젝트 생성방법
- Project : Gradle -Groovy
- Language : Java
- Spring Boot : 2.xx (3.00 version 이상부터는 JAVA 17 이상을 사용해야 합니다.
- Dependencies : 선택 X(지금 예제에서는 스프링을 사용하지 않는다. 순수 자바로만 사용)
각 항목들을 체크해 주었다면 GENERATE를 눌러 프로젝트 파일을 다운로드하고 IDE에서 프로젝트를 오픈합니다!
초기에는 bulid 하느라 시간이 조금 걸릴 수 있음.
CoreApplication.java를 실행해서 테스트합니다.
아래처럼 출력이 되면 성공입니다!
❓비즈니스 요구사항과 설계
- 회원
- 회원을 가입하고 조회할 수 있다.
- 회원은 일반과 VIP 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)
- 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 VIP만 1000원을 할인해 주는 고정 금액 할인을 적용해 달라. (변경가능)
- 아직 미정인 부분들이 많다. 객체지향이 필요한 이유이다. 추후에 부품을 갈아 끼우듯이 쉽게 변경할 수 있게끔 설계해야 한다!
❓ 회원 도메인 설계
- 회원
- 회원을 가입하고 조회할 수 있다.
- 회원은 일반과 VIP 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)
클라이언트는 회원을 가입하고 조회할 수 있어야 하지만, 내부의 저장소는 알 필요가 없다. 회원 저장소는 아직 미정이다.
❓회원 도메인 개발
회원 엔티티
회원 등급 클래스
일반회원과 VIP를 구분하기 위해 enum을 이용했다.
package hello.spring_core.member;
public enum Grade {
BASIC,
VIP
}
회원 엔티티
- 회원 이름, 회원 ID , 회원등급의 정보를 저장하고 불러옵니다!
- get, set함수, 생성자를 같이 구현합니다.
package hello.spring_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 void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Grade getGrade() {
return grade;
}
}
❓ 회원 저장소
회원 저장소 인터페이스
-> 인터페이스의 공통속성이나 기능을 적어두고 SubClass에서 이를 상속하여 재사용하는 방식입니다.
package hello.spring_core.member;
public interface MemberRepository {
void save(Member memeber);
Member findByID(Long mebmerId);
}
❓ 메모리 회원 구현체 (class)
아직 데이터베이스가 확정되지 않았다! 그러므로 가장 단순한, 메모리 회원 저장소를 구현.
HashMap을 이용해서 저장하고, 회원가입과 조회기능을 위한 save()와 join() 함수를 오버라이딩 하였다!
package hello.spring_core.member;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
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.spring_core.member;
public interface MemberService {
void join(Member member);
Member findMember(Long memberId);
}
회원 서비스 구현체
인터페이스를 상속하고 메서드 오버라이딩을 통해 join과 findMember함수를 재구성한다. 또한 인터페이스의 구현체가 하나뿐일 때 보통 인터페이스 뒤에 Impl을 붙인다고 하셨다.
package hello.spring_core.member;
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findByID(memberId);
}
}
❓ 회원 도메인 실행과 테스트
회원 도메인 - main에서 회원가입을 하고 findMember를 통해 객체를 return 받고 출력하여 비교해 보자.
package hello.spring_core;
import hello.spring_core.member.*;
public class MemberApp {
public static void main(String[] args) {
MemberService memberService = new MebmerServiceImpl();
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 테스트를 사용하자. (일일이 눈으로 확인해봐야 하기 때문)
❓회원 도메인 - 회원 가입 테스트
-UnitTest란? : 아래 Test코드와 같이 순수 자바로만 테스트 하는 단위를 말합니다. 이런 단위 테스트는 실행시간이 짧기 때문에 UnitTest를 잘 짜는 것이 중요하다고 하심!
- Assertions.assertThat(member) : org.assertj.core.... 을 import 해야 한다. (JUNIT 말고)
- assertion을 제공해 주는 라이브러리로 에러 메시지와 테스트 코드의 가독성을 매우 높여주고 각자 좋아하는 IDE에서 쓰기 굉장히 쉽다.
- 예시코드
@Test void a_few_simple_assertions() {
assertThat("The Lord of the Rings").isNotNull()
.startsWith("The")
.contains("Lord")
.endsWith("Rings");
}
한눈에 어떤 것을 하려는지 보일 정도로 가독성이 좋다. 테스트 코다는 assertion으로 시작해서 assertion으로 끝난다고 한다.
아래와 같이 StaticImport를 하면 클래스 이름 없이 바로 사용할 수 있어 더욱 좋다!
테스트코드 작성 방법
- //given :어떤 것이 주어졌을 때 (이 데이터를 기반)
- //when : 어떤 상황에서 (실행시키고자 하는 내용)
- //then : 이런 결과가 나와야 한다. (기댓값)
package hello.spring_core.member;
import hello.spring_core.AppConfig;
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);
}
}
실행화면
이렇게 초록불로 성공 시에 출력해 주고 에러가 나면 빨간색으로 표시해 준다!
회원 도메인 설계의 문제점
- 다른 저장소로 변경하려고 할 때 OCP원칙을 잘 준수했나?
- DIP를 잘 지키고 있을까요?
- 의존관계가 인터페이스뿐만 아니라 구현까지 모두 의존하고 있음!
- MemberServiceImpl에서 저장소인 MemoryMemberRepository를 new로 동적할당 하고 있지만, 이는 인터페이스가 아닌 class에서 의존관계를 결정하므로 인터페이스에 의존하는 것이 아닌 하위 클래스에 의존하고 있기 때문에 , DIP를 위반했다고 볼 수 있다. 뭔가 더 필요하다! -> 이후에 다룰 예정.
'Spring > Spring 김영한' 카테고리의 다른 글
스프링 핵심 원리 (기본편) 강의 정리 - 3 객체 지향 적용 (0) | 2023.09.19 |
---|---|
스프링 핵심 원리(기본편) 강의정리 - 1 (0) | 2023.09.12 |