Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 타임리프 - 기본기능
- 자바의 정석 기초편 ch6
- 자바의 정석 기초편 ch1
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch3
- 스프링 입문(무료)
- 스프링 mvc2 - 타임리프
- 스프링 db1 - 스프링과 문제 해결
- 스프링 고급 - 스프링 aop
- @Aspect
- 자바의 정석 기초편 ch7
- 자바의 정석 기초편 ch8
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch9
- 2024 정보처리기사 시나공 필기
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch12
- jpa 활용2 - api 개발 고급
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch2
- 스프링 mvc1 - 서블릿
- 게시글 목록 api
- 자바의 정석 기초편 ch4
- 스프링 mvc1 - 스프링 mvc
- jpa - 객체지향 쿼리 언어
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch13
Archives
- Today
- Total
나구리의 개발공부기록
예제 만들기(순수 Java), 프로젝트 생성, 비즈니스 요구사항과 설계, 회원 도메인 설계 및 개발, 주문과 할인 도메인 설계 및 개발, 도메인 실행과 테스트 본문
인프런 - 스프링 완전정복 코스 로드맵/스프링 핵심원리 - 기본편
예제 만들기(순수 Java), 프로젝트 생성, 비즈니스 요구사항과 설계, 회원 도메인 설계 및 개발, 주문과 할인 도메인 설계 및 개발, 도메인 실행과 테스트
소소한나구리 2024. 1. 26. 16:05 출처 : 인프런 - 스프링 핵심원리 - 기본편(유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
** 참고
- 이번 파트는 스프링의 도움없이 순수하게 Java로만 진행하고 다음 파트에서 스프링을 활용해서 문제점을 해결
- 프로젝트 생성만 스프링부트로 생성
1. 프로젝트 생성
1) spring initializr 활용
- https://start.spring.io/ 접속 후 프로젝트를 생성
(1) Project 생성 정보
- IDE - IntelliJ
- Project : Gradle - Groovy Project
- Spring Boot: 3.x.x -> 정식 버전 중 가장 최신 버전
- Language: Java
- Packaging: Jar
- Java: 21 or 17
- Project Metadata
- group: hello
- artifact: core
- Dependencies: 선택X(순수 자바로 진행)
- IntelliJ 설정
- Build and run using, Run tests using -> IntelliJ IDEA로 변경(조금 빠름)
- 스프링 부트 3.2 부터 스프링이 지원하는 일부 애노테이션의 동작방식이 변경되어 애노테이션을 생략할 때 파라미터를 인식하지 못하는 문제가 있어 build를 Gradle로 설정하는 것을 권장함
2. 비즈니스 요구사항과 설계
1) 회원
- 회원 가입, 조회 기능
- 회원등급 - 일반, VIP
- 회원 데이터 - 자체DB, 외부 시스템 연동(미확정)
2) 주문과 할인 정책
- 회원 - 상품 주문
- 회원등급에 따른 차등 적인 할인 정책 적용
- 할인정책 - 모든 VIP는 1000원 고정 할인 적용(나중에 변경 가능성 있음)
- 할인정책은 변경 가능성이 높음 - 기본 할인 정책은 오픈 전까지 미정, 최악의 경우 할인 적용 안할 수 있음(미확정)
3. 회원 도메인 설계
1) 회원 도메인 협력 관계 설정
2) 회원 클래스 다이어 그램 - 정적
3) 회원 객체 다이어 그램(실제 인스턴스끼리의 참조)
- 동적 다이어그램
4. 회원 도메인 개발
1) 회원 엔터티
(1) 회원등급 - enum
- member 패키지를 생성 후 작성
public enum Grade {
BASIC,
VIP
}
(2) 회원 - class
- 변수 선언 및 생성자, getter setter 생성
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;
// getter, setter 생성
}
2) 회원 저장소
(1) 회원 저장소 인터페이스
- 회원을 저장하는 save메서드와 회원의 id값을 갖고 회원을 조회하는 findById메서드를 선언
public interface MemberRepository {
void save(Member member);
Member findById(Long memberId);
}
(2) 메모리 회원 저장소 구현체
- 데이터베이스가 아직 확정이 되지 않았으므로 가장 단순한 메모리 회원 저장소를 구현해서 우선 개발을 진행하는 컨셉
- 회원 저장소 인터페이스를 구현하는 클래스
- 인터페이스 메서드들의 동작을 구현
** 참고
- 실무에서는 HashMap은 동시성 이슈가 발생할 수 있기 때문에 ConcurrentHashMap을 이용해야함
public class MemoryMemberRepository implements MemberRepository{
// 원래는 동시성 문제 때문에 ConcurrentHashMap을 써야함
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);
}
}
3) 회원 서비스
(1) 회원 서비스 인터페이스
public interface MemberService {
void join(Member member);
Member findMember(Long memberId);
}
(2) 회원 서비스 구현체
- 메모리 리포지토리 객체를 생성하여, 비즈니스 로직인 회원 가입과 회원 조회 기능을 구현
// 구현체가 하나만 있을 때 클래스명 뒤에 Impl이라고 많이 씀(관례)
public class MemberServiceImpl implements MemberService {
// 구현 객체 선택
private final MemberRepository memberRepository = new MemoryMemberRepository();
public void join(Member member) {
memberRepository.save(member);
}
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
5. 도메인 실행과 테스트
1) 회원 도메인
(1) 회원 가입 main
- ~/hello.core의 경로에 작성
- 회원객체를 1개를 생성한 후 main메서드에서 MemberServiceImpl의 객체를 생성하여 비즈니스로직의 동작들을 확인
- 그러나 이렇게 애플리케이션 로직으로 직접 테스트 하는 것은 좋은 방법이 아니므로 JUnit 테스트를 사용해야함
public class MemberApp {
//main method에서 테스트 진행
public static void main(String[] args) {
MemberService memberService = new MemberServiceImpl();
Member member = new Member(1L,"memberA", Grade.VIP);
memberService.join(member);
Member finMember = memberService.findMember(1L);
System.out.println("new member = " + member.getName());
System.out.println("find Member = " + finMember.getName());
}
}
(2) 회원 가입 테스트(JUnit Test)
- ~/test/java/hello.core 경로에 member 패키지를 생성 후 class를 작성
- 테스트에서 생성한 member를 join메서드로 저장한 후 findMember 메서드로 찾은 후 Assertions.assertThat으로 member와 findMember가 동일한지 검증
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);
}
}
6. 주문과 할인 도메인 설계
1) 주문 도메인 협력, 역할, 책임
- 주문 생성 : 클라이언트 -> 주문서비스에 주문 생성을 요청
- 회원 조회 : 할인을 위해서 회원등급이 필요, 주문서비스는 회원 저장소에서 회원을 조회
- 할인 적용 : 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임
- 주문 결과 반환 : 주문서비스는 할인결과를 포함한 주문 결과를 반환
** 참고
- 실제로는 주문데이터를 DB에 저장하겠지만 이번 예제에서는 생략하고 주문 결과만 반환
2) 주문 도메인 전체와 주문 도메인 클래스 다이어그램
- 역할과 구현을 분리하여 자유롭게 구현 객체를 조립할 수 있도록 설계
- 저장소와 할인 정책을 유연하게 변경할 수 있음
3) 주문 도메인 객체 다이어그램
- 회원을 메모리 / DB에서 조회하고 할인 정책을 변경하여도 주문 서비스를 변경하지 않아도 된다
7. 주문과 할인 도메인 개발
1) 할인 정책
(1) 할인 정책 인터페이스
- discount 패키지를 생성 후 할인 메서드를 가진 할인 정책 인터페이스 생성
- 할인 메서드에는 회원과 가격정보가 필요함
package hello.core.discount;
public interface DiscountPolicy {
int discount(Member member, int price);
}
(2) 정액 할인 정책 구현체
- VIP = 1000원 할인
- VIP가 아니면 할인 없음
package hello.core.discount;
public class FixDiscountPolicy implements DiscountPolicy {
private int discountFixAmount = 1000;
@Override
public int discount(Member member, int price) {
// Enum타입은 비교시 == 써야함
if (member.getGrade() == Grade.VIP) {
return discountFixAmount;
} else {
return 0;
}
}
}
2) 주문
(1) 주문 엔터티
- order패키지 추가 후 작성
- 변수, 생성자, getter setter작성
- 아이템 가격에서 할인 가격을 빼서 반환하는 calculatePrice 메서드 정의
- IDE의 도움을 받아 자동생성 기능을 사용하여 toString()를 생성
package hello.core.order;
public class Order {
// 변수선언
private Long memberId;
private String itemName;
private int itemPrice;
private int discountPrice;
public int calculatePrice() {
return itemPrice - discountPrice;
}
// getter, setter, 필드 초기화 생성자 생략
// toString 생성(IDE 자동 생성 기능)
@Override
public String toString() {
// 구현 코드 생략
}
}
(2) 주문 서비스 인터페이스
- 주문 기능을하는 createOrder메서드 선언
package hello.core.order;
public interface OrderService {
Order createOrder(Long memberId, String itemName, int itemPrice);
}
(3) 주문 서비스 구현체
- 메모리 회원 저장소와 고정 할인 정책을 객체로 생성하고 createOrder메서드를 구현하는 구현체 생성
- 저장소에서 회원을 조회하고 등급을 확인하고 할인을 적용한뒤 주문 객체를 생성하여 새로운 주문을 생성하여 반환
package hello.core.order;
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);
}
}
8. 주문과 할인 도메인 실행과 테스트
1) 주문과 할인 정책
(1) 실행(main)
- 애플리케이션에서 실행하는 좋지 않은 테스트이며 연습이기에 회원 정책과 동일하게 작성하여 테스트
- 출력결과를 확인해보면 주문을 생성할때 넘긴 정보과 출력 정보가 동일하게 주문이 잘 생성된 것을 확인할 수 있음
package hello.core;
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.toString() = " + order.toString());
System.out.println("order.calculatePrice = " + order.calculatePrice());
}
}
/* 출력결과
order.toString() = Order{memberId=1, itemName='itemA', itemPrice=10000, discountPrice=1000}
order.calculatePrice = 9000
*/
(2) 주문과 할인 정책 테스트(JUnit Test)
- 애플리케이션에서 수행한 테스트를 Junit으로 검증하는 테스트
- 동일하게 정상적으로 테스트가 통과됨
package hello.core.order;
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);
}
}
3) 순수 자바로 구현한 애플리케이션의 문제점
(1) 요구사항의 변경으로 할인 정책이 변경됨
- 다른 저장소로 변경이 되었을 때 OCP원칙과 DIP원칙을 지키며 변경할 수 없음
- 의존관계가 인터페이스 뿐아니라 구현까지 모두 의존하는 문제점이 존재함
- 다형성을 활용하여 역할과 구현을 구분하였지만 할인 정책을 정률 할인 정책으로 변경 하였을 때 클라이언트에 영향이 없도록 변경이 가능한지 객체지향적으로 잘 개발 되었는 것인가에 대한 의문이 남아있음