Spring 사용하기 (Coupon_check 백엔드 (1) - 비즈니스 정리)

김태훈·2022년 12월 26일
0

Spring_CouponCheck

목록 보기
4/14

배웠던 내용을 Project화 하기 위하여, 각색한 상황들을 정리했다.

1. 비즈니스 요구사항 정리

  • 데이터 : 회원 id , 받은 쿠폰 Number, 남은 쿠폰 개수
  • 기능 : id만으로 로그인하여 쿠폰을 발급 받고, 해당 쿠폰 Number를 체크하는 기능. 만일, 남은 쿠폰이 없다면 발급할 수 없음
  • 아직 데이터 저장소가 선정되지 않음. -> 클래스 의존관계를 만드는 이유

알아야할 기본적인 웹 어플리케이션 계층 구조 (김영한님의 스프링 입문 강의자료에서 퍼왔습니다. 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를 활용하면서 감을 잡아보자.

2. 회원 Domain, 쿠폰 Domain & Repository를 만들어 보기

1. 회원 Domain

여기에서는 회원 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;
    }
}

2. 쿠폰 Domain

여기에서는 남은 쿠폰 개수가 멤버 변수로 설정되었다.

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

3. repository 인터페이스 만들기

일단은 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 할 수 있게 한다.

4. 구현체 만들기

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 따로 파서 작업

profile
기록하고, 공유합시다

0개의 댓글