티스토리 뷰
김영한 - 스프링 핵심 원리 기본편을 듣고 정리한 내용입니다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
✔목차
2. 스프링 핵심 원리 이해 1 - 예제 만들기
- 프로젝트 생성
- 비즈니스 요구사항과 설계
- 회원 도메인 설계
- 회원 도메인 개발
- 회원 도메인 실행과 테스트
- 주문과 할인 도메인 설계
- 주문과 할인 도메인 개발
- 주문과 할인 도메인 실행과 테스트
✔프로젝트 생성
✔환경설정
- java 11
- IntelliJ
- 스프링 부트 스타터 사이트로 스프링 프로젝트 생성
- 프로젝트 선택
- project : Gradle Project
- Spring Boot : 2.7.2
- Language : Java
- Packaging : Java
- Project Metadata
- groupld : hello
- artifactld : core
- Dependencies : 선택X
✔Gradle 전체 설정
✔build.gralde
plugins {
id 'org.springframework.boot' version '2.7.2'
id 'io.spring.dependency-management' version '1.0.12.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.main())
- IntelliJ Gradle 대신에 자바 직접 실행
- 최근에 인텔리제이 버전은 Gradle을 통해서 실행 하는 것이 기본 설정이다. 이렇게 하는 것 보다 자바로 실행하는 것이 실행속도가 더 빠르다.
- Preferences -> Build, Execution, Deployment -> Build Tools -> Gradle
- Build and run using : Gradle -> IntelliJ IDEA
- Run tests using : Gradle - > IntelliJ IDEA
✔비지니스 요구사항과 설계
- 회원
- 회원을 가입하고 조회할 수 있다.
- 회원은 일반과 VIP 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.
- 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 정욕해 달라.
- 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다.
※ 인터페이스를 만들고 구현체를 언제든 바꿀수 있게 설계
p.s. 이번프로젝트는 스프링없이 자바로만 개발
✔회원 도메인 설계
- 회원 도메인 요구사항
- 회원을 가입하고 조회할 수 있다.
- 회원은 일반과 VIP 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.
✔회원 도메인 협력 관계

✔회원 클래스 다이어그램

- 회원 서비스 : MemberServicelmpl
✔회원 도메인 개발
✔회원 엔티티
✔회원 등급
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;
}
}
✔회원 저장소
✔회원 저장소 인터페이스
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;
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);
}
}
- 메모리 회원 저장소를 구현해 틀을 짠다.
- 참고 : HashMap은 동시성 이슈가 발생할 수 있다. 이런 경우 ConcurrentHashMap을 사용하자.
✔회원 서비스
✔회원 서비스 인터페이스
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);
}
}
✔회원 도메인 실행과 테스트
✔회원 도메인 - 회원가입 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);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("new member = " + member.getName());
System.out.println("find Member = " + findMember.getName());
}
}
- 좋은방법 x 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(1L);
//then
Assertions.assertThat(member).isEqualTo(findMember);
}
}
✔회원 도메인 설계의 문제점
- 이 코드의 설계상 문제점은 무엇일까?
- 다른 저장소로 변경할 때 OCP 원칙을 잘 준수 할까?
- DIP를 잘 지키고 있을까?
- 의존관계가 인터페이스 뿐만 아니라 구현까지 모두 의존하는 문제가 있다.
- -> 주문까지 만들고나서 문제점과 해결 방안을 설명
✔주문과 할인 도메인 설계
- 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 모든 VIP는 100원을 할인해주는 고정 금액 할인을 적용해라.
- 할인 정책은 변경 가능성이 높아 회사의 기본 할인 정책을 아직 못정했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 있다.
✔주문 도메인 협력, 역할, 책임

- 주문 생성: 클라이언트는 주문 서비스에 주문 생성을 요청한다.
- 회원 조회: 할인을 위해서는 회원 등급이 필요하다. 그래서 주문 서비스는 회원 저장소에서 회원을 조회한다.
- 할인 적용: 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임한다.
- 주문 결과 반환: 주문 서비스는 할인 결과를 포함한 주문 결과를 반환한다.
✔주문 도메인 전체

역할과 구현을 분리해서 자유롭게 구현 객체를 조립할 수 있게 설계했다.
=> 회원 저장소, 할인정책을 유연하게 변경 가능
✔주문 도메인 클래스 다이어그램

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

회원을 메모리에서 조회하고, 정책 할인 정책을 지원해도 주문 서비스를 변경하지 않아도 된다.
역할들의 협력 관계를 그대로 재사용 할 수 있다.
✔주문 도메인 객체 다이어그램2

회원을 메모리가 아닌 실제 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) {
return discountFixAmount;
} else {
return 0;
}
}
}
Vip1천원 할인, 아니면 할인X
✔주문 엔티티
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);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
주문 생성 요청이 오면, 회원 정보를 조회하고, 할인 정책을 정욕한 후 다음 주문 객체를 생성해서 반환한다.
메모리 회원 레포지토리와 고정 금액 할인 정책을 구현체로 생성한다.
✔주문과 할인 도메인 실행과 테스트
✔주문과 할인 정책 실행
package hello.core;
import hello.core.Order.Order;
import hello.core.Order.OrderService;
import hello.core.Order.OrderServiceImpl;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
public class OrderApp {
public static void main(String[] args) {
MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
System.out.println("order = " + order);
}
}
✔결과
order = Order{memberId=1, itemName='itemA', itemPrice=10000, discountPrice=1000}
✔주문과 할인 정책 테스트
package hello.core.order;
import hello.core.Order.Order;
import hello.core.Order.OrderService;
import hello.core.Order.OrderServiceImpl;
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);
}
}
'스프링 > 스프링 핵심원리 기본편' 카테고리의 다른 글
Spring -[컴포넌트 스캔] #6 (0) | 2022.08.26 |
---|---|
Spring -[싱글톤 컨테이너] #5 (0) | 2022.08.25 |
Spring -[스프링 컨테이너와 스프링 빈 ] #4 (0) | 2022.08.18 |
Spring -[스프링 핵심 원리 이해 2 - 객체 지향 원리 적용] #3 (0) | 2022.08.16 |
Spring -[객체지향 설계와 스프링] #1 (0) | 2022.08.07 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 자바 #자바문법 #자바기초 #참조형 #기본형
- 백준14720 # 그리디 알고리즘 # greedy
- 반복문
- Spring
- java #예외처리 #throw #throws
- 스프링웹개발기초
- level2
- 백준 10828번 # 스택
- 명품 JAVA Programming - 황기태
- heap area #stack area #static area #jvm
- 백준17608번
- 단계10
- 나는야 4학년 #5학년 까지 가보자구
- object 클래스 # java
- static #자바 메모리 구조 #멤버 변수
- 25083번
- 이분탐색
- 백준10871번
- level1 # 입출력과 사칙연산
- 25304번
- level3
- 12605번
- 11034번
- 정보처리기사 실기 #정처기 실기 #2024년 2회 #정처기 2024년 2회 #공부법 # 꿀팁
- 올 겨울은 조금 따뜻할 것 같다.
- 브루트 포스법
- java #추상클래스
- 2798블랙잭
- 백준 18258번
- 백준2720번 #그리디 알고리즘
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
글 보관함