배웠던 내용을 Project화 하기 위하여, 각색한 상황들을 정리했다.
알아야할 기본적인 웹 어플리케이션 계층 구조 (김영한님의 스프링 입문 강의자료에서 퍼왔습니다. https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8)
여기서 부연 설명을 하자면, Controller는 앞 게시글에서 설명했으므로 넘어가겠다.
서비스 : 핵심 비즈니스 로직이란, 비즈니스 도메인 객체를 바탕으로 회원의 중복가입 방지, 어떻게 쿠폰 Number를 부여할 것인지 에 관한 정보들을 말한다.
기본적으로 DB가 정해져있지 않으므로, MemberRepository라는 Interface를 두고, Interface가 가지고 있는 다형성을 활용하여 실제 구현 repository를 만드는 방식을 구현하였다.
(실제로는 jdbc, jpa 많은 db가 존재)
먼저, 아주 간단하게 실제 구현 repository를 memory를 활용하면서 감을 잡아보자.
여기에서는 회원 id (데이터 구분을 위함 primary key), name, 받은 쿠폰 number를 멤버 변수로 설정했다.
package hello.hellospring.domain;
public class Member {
private Long id; //임의의 값, 데이터 구분을 위함.
private String name;
private Integer couponNum;
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 Integer getCouponNum() {
return couponNum;
}
public void setCouponNum(Integer couponNum) {
this.couponNum = couponNum;
}
}
여기에서는 남은 쿠폰 개수가 멤버 변수로 설정되었다.
package hello.hellospring.domain;
public class Coupon {
private Integer num = 10; //초기 쿠폰 수 10개로 설정.
public Integer getNum() {
return num;
}
public void setNum() {
this.num = this.num == 0 ? null:this.num-1;
}
}
일단은 repository를 Member, Coupon 도메인 모두를 포함시켰다.
향후 수정할 수도 있겠다.
package Goat.CouponCheck.repository;
import hello.hellospring.domain.Member;
import hello.hellospring.domain.Coupon;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
//Member domain 관련 구현 repository
Member saveMember(Member member); //회원 저장
Optional<Member> findById(Long id); //id로 회원 찾기
Optional<Member> findByName(String name); //name으로 회원 찾기
List<Member> findAll(); //회원정보 모두출력
//Coupon domain 관련 구현 repository
Coupon saveCoupon(Coupon coupon); // 쿠폰 남은 개수 저장
}
Optional 이란?
findById , findByName으로 구성 도메인을 가져왔을 때, null 값을 가져올 수도 있기 때문에 null을 그대로 반환하는 대신 Optional로 감싸면, null값을 controll 할 수 있게 한다.
package Goat.CouponCheck.repository;
import hello.hellospring.domain.Coupon;
import hello.hellospring.domain.Member;
import java.util.*;
public class MemoryRepository implements Repository{
//Member 정보 저장 자료구조
private static Map<Long,Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member saveMember(Member member) {
member.setId(++sequence);
member.setCouponNum(0); //초기에는 쿠폰을 발급받지 않았으므로 0으로 set
store.put(member.getId(),member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member->member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
@Override
public Optional<Integer> saveCoupon(Coupon coupon) {
coupon.setNum();
Optional<Integer> returnVal = Optional.ofNullable(coupon.getNum());
if (returnVal.isPresent()){
return Optional.ofNullable(coupon.getNum());
}
else{
System.err.println("쿠폰수가 0장입니다.");
return Optional.ofNullable(coupon.getNum());
}
}
}
간단한 구현이므로, 실무에서 사용하는 동시성 문제는 전혀 고려되어 있지 않다는 사실에 유의하자.
findByName 메소드 설명 :
store value들의 stream으로 loop를 돌면서, parameter로 넘어온 name과 일치하는지 과정을 거쳐서 loop를 돌고, 찾으면 findAny로 반환, 만약 없다면 Null값이 반환되지만, Optional로 처리할 수 있다.
saveCoupon 메소드 설명 : (+coupon domain 부연설명)
coupon domain 에서 쿠폰수를 애당초 10장으로 초기화했으므로, 쿠폰수를 setNum으로 변경할 때에는, parameter를 전달할 필요가 없이, 회원들이 한장씩 가져갈 것이므로 한장씩 감소시키는 방향으로 코드를 작성했다. 다만, 쿠폰이 0장일 경우에는 쿠폰을 발급받을 수 없으므로, 해당 상황에서 null을 반환시켰다.
이를 활용해서 MemoryRepository 구현체에서, saveCoupon 메소드의 반환형을 Optional로 감싸고, null값이 나올 경우에는 에러메세지를 출력해야 겠다.
이부분은 수정이 많이 필요해 보인다.
솔직히 잘될지는 모르겠다. 안되면 수정합시다. 그래서 branch 따로 파서 작업