안녕하세요.

오늘부턴 제대로 기능을 구현해보려고 합니다. 와아아아아아!!!!!!!!!!

갑자기 네이버의 대표님이 나에게 딱 와서 종이 하나만 딱 던져주고 갔다고 가정해볼게요.

(이리와서 이것좀 만들어볼래요?)

거기엔 우리가 이제 네이버 쇼핑 플랫폼을 만들게 될 것이라고 적혀있네요. 그리고 해당 쇼핑 플랫폼의 비즈니스 요구사항이 적혀있습니다.

막막하고 뭐부터 해야할 지 모르겠는 이 상황. 하나하나 시작해봅시다!

👉이전 글 보러가기


요구사항을 알아보자

우리가 무슨 기능을 구현해야 할 지 알아봐야겠죠? 대표님이 남기고 간 종이를 확인해봅시다.

회원

  • 회원을 가입하고 조회할 수 있다.
  • 회원은 일반과 VIP 두 가지 등급이 있다.
  • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)

주문과 할인 정책

  • 회원은 상품을 주문할 수 있다.
  • 회원 등급에 따라 할인 정책을 적용할 수 있다.
  • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있다.)
  • 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)

(킹받아!!!)

너무 킹받지 않나요? 뭐 정해진게 하나도 없네요. 기능은 회원 관련된거와 주문만 구현하면 된다지만 죄다 미확정이라니… 다 구현해놓고 바꾸라고 할게 뻔히 보이네요… 어쩔수없죠. 먹고살려면 해야죠…

네 이게 저희의 요구사항입니다. 이 요구사항을 Spring Boot를 사용해서 만들었을 때 얼마나 효율적으로 작성할 수 있는지 알아가봅시다!


엔티티 구현하기

오늘은 회원 기능을 만들고 퇴근해봅시다.

회원 기능의 요구사항을 다시 살펴볼까요?

회원

  • 회원을 가입하고 조회할 수 있다.
  • 회원은 일반과 VIP 두 가지 등급이 있다.
  • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)

회원 가입과 조회는 천천히 만들어야 할 것 같고, DB구축도 나중에 해야 할 것 같으니 먼저 이전에 2번째 글에서 가렌을 정의했던 것처럼 회원을 정의하고, 등급 기능을 만드는걸 먼저 하면 좋을것같아요.

그리고 회원과 관련된 애들을 모아놓는 패키지를 저희의 shopping 프로젝트 안에 미리 만들어봅시다.

com.naver.shopping을 우클릭하여 패키지를 새로 만들어주세요.

패키지 이름은 member로 해봅시다.


회원 등급

먼저 간단하게 등급을 만들어봅시다.  JAVA의 열거형을 사용해서 만들어볼게요.

직전에 만든 memeber 패키지 안에 Java 클래스를 만들어줍시다.

등급이니 이름은 Grade로 해줍시다. 밑에 클래스, 인터페이스, 레코드, 열거형, 어노테이션이 보이는데 저 중 열거형이 바로 enum입니다. enum은 간단하게 설명드리면 그냥 간단하게 관련되어있는 값들을 쭉 나열해서 갖다쓸 수 있게 만든것입니다. 아마 JAVA 공부하시고 오셨을테니까 넘어갈게요…?

이런 구조로 프로젝트가 구성되었다면 잘 된것입니다. 자 이제 등급들을 열거해봅시다. 등급에서 중요한 부분은 일반과 VIP 두 등급이 있다는겁니다. 일반을 BASIC, VIP는 VIP로 해서 등급 두개를 만들어봅시다.

package com.naver.shopping.member;
 
public enum Grade {
    BASIC,
    VIP
}

간단하죠? 이제 저희는 Grade를 써서 BASIC과 VIP 두 등급을 불러올 수 있게 되었습니다.


회원 엔티티

자 이제 회원에 대해 정의해봅시다. 회원이 필요할 것 같은 값으로는 이름과 등급 그리고 각각의 고유값을 주기위한 id값 이렇게 3개가 필요해 보입니다.

그럼 이번엔 열거형이 아니라 class로 Member 엔티티를 만들어봅시다.

(똑같이 member패키지에서 Java 클래스 새로만들기)

(이번엔 클래스 형식으로 Member 파일 만들기)

만들어진 클래스에 Member가 필요하다 했던 값을 넣을 수 있도록 해봅시다.

package com.naver.shopping.member;
 
public class Member {
    private Long id;
    private String name;
    private Grade grade;
}

일단 만들긴 했는데 다른곳에서 Member를 사용하면서 값을 지정할 수 없겠죠? 그럴 때 우린 생성자를 사용합니다. 여기서 IntelliJ의 기능을 써봅시다. 바로 생성 기능입니다.

빈곳에서 우클릭을 해서 생성…을 클릭하시거나, Alt+Insert를 눌러보세요.

Alt+Insert 많이 쓰니 외우시면 좋아요

생성자를 클릭해보세요

Ctrl+A 나 컨트롤 클릭으로 전체 다 선택해 주신 후 확인!

// 생성자 생성 결과
	public Member(Long id, String name, Grade grade) {
        this.id = id;
        this.name = name;
        this.grade = grade;
    }

위와 같이 생성자 코드가 자동으로 저희의 Member 클래스에 추가된 것을 확인하실 수 있습니다!

그럼 생성 기능을 한번 더 써볼까요? 이번엔 외부에서 Member 클래스의 id, name, grade 값을 불러오거나 변경할 수 있게 getter, setter함수를 만들어보겠습니다. 자동으로요!

이번엔 Alt+Insert 후 getter 밑 setter 클릭

Ctrl+A 나 컨트롤 클릭으로 전체 다 선택해 주신 후 확인!

// getter setter 생성 결과
	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;
    }
 
    public void setGrade(Grade grade) {
        this.grade = grade;
    }

위와 같이 외부에서 사용할 수 있는 getter/setter 함수들이 자동으로 추가된 것을 보실 수 있습니다. 직접 하게 된다면 중요한 작업도 아니고 귀찮은데 에디터가 만들어주면 엄청 편해집니다~

그럼 여기까지 저희가 만든 Member 클래스를 알아보겠습니다.

// 전체 Member 엔티티 클래스
package com.naver.shopping.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;
    }
 
    public void setGrade(Grade grade) {
        this.grade = grade;
    }
}

좋아요. 이제 회원을 정의할 수 있게 되었습니다.


저장소 구현하기

이제 회원도 정의할 수 있는데 회원가입이 되면 회원을 저장할 공간을 만들어볼까요? 그래야 회원가입/조회 기능을 만들고 테스트해볼 수 있을테니까요.

그런데 아직 DB도 안정해졌다고 했었죠… 음… 회원가입과 조회 기능을 테스트해볼 용도로 임시로 저희의 로컬 공간에 회원정보가 저장되도록 로컬 안의 회원 저장소를 만들어 봅시다.

그 전에 먼저!! 저희가 같은 기능을 가지지만 자주 바뀔 클래스들 사이에서 인터페이스를 만든다고 했었죠?

이전에 가렌 클래스와 다리우스 클래스를 쉽게 바꾸던 것 같이 나중에 만들어질 DB 저장소를 위해 로컬 저장소를 만들기 전 저장소 인터페이스를 만들어봅시다!


저장소 인터페이스 생성

회원 데이터가 저장될 저장소가 가져야 할 기능들을 생각해봅시다.

저장

회원가입을 할 때 가입한 정보들을 저장소에 저장하도록 하는 함수가 필요할 것입니다. 이름은 save로 해서 저희가 구현한 Member 클래스 형식대로 인자를 받도록 해봅시다.

탐색

회원 조회를 하는 기능을 만들 때 저장소에 저장되어있는 회원들 중 알맞은 회원의 정보를 return받는 함수가 필요할 것입니다. 고유값인 id값을 이용해 탐색하면 정확히 한 사용자의 정보만 가져 올 수 있으니 id값을 인자로 받도록 해 봅시다. 이름은 findById가 좋을 것 같아요.

그럼 만들러 가봅시다.

위와 같이 member패키지에서 우클릭 해서 새로만들기로 Java 클래스를 만들어주세요.

(저장소는 Repository라는 용어로 많이 사용합니다.)

그리고 위에서 말한 함수들을 구현하도록 넣어줄까요?

 
package com.naver.shopping.member;
 
public interface MemberRepository {
    void save(Member member);
    Member findById(Long memberId);
}

이제 회원과 관련된 로컬이든, MySQL이든, PostgreSQL이든 뭐든 간의 저장소 클래스는 위 두개의 함수를 위와 같은 형태로 무조건 구현하도록 설정했습니다.


회원 저장소 구현

이제 MemberRepository 인터페이스를 구현해보도록 하겠습니다. 여기서도 IntelliJ의 편리한 기능을 하나 써보겠습니다.

현재 interface의 이름인 MemberRepository의 아무곳이나 커서를 두고 Alt+Enter를 눌러보시겠어요?

코드의 인터페이스 이름의 아무곳에나 커서를 두면 됩니다

저기에 보시면 인터페이스 구현이라고 되어있는게 있을겁니다. 한번 눌러봅시다.

클래스 명만 MemoryMemeberRepository로 설정해주고 나머지는 그대로 두시면 됩니다.

위와 같은 창이 뜰겁니다. 저희는 로컬 메모리에 저장될 저장소를 구현할 것이니 MemoryMemberRepository로 구현할 클래스 이름을 지정해주시고 확인을 누르시면 됩니다.

save와 findById가 둘 다 선택되어 있는지 확인하세요!

저희가 구현할 모든 함수들이 선택되어 있는지 확인 후 @Override 삽입 부분이 체크되어있게 해 주시고 확인을 눌러주세요.

(구현 클래스 생성 결과)

놀랍게도 자세하게 구현할 부분을 제외하고 자동으로 구현 클래스가 만들어진 것을 확인하실 수 있습니다. 이제 편하게 구현해봅시다!

먼저 store라는 변수를 MemeoryMemberRepository 클래스 내에서 다 쓸수 있도록 하고, 여기에 모든 Member들의 정보가 저장되도록 해볼까요?

store의 자료형은 Map을 사용하는게 좋겠습니다! Map은 가볍게 설명드리면 Key와 Value 형태로 값을 저장되게 할 수 있는 자료형으로, 자세한 것은 검색!!!

key는 회원의 id로 하고 value는 회원 정보 전체를 넣어봅시다.

package com.naver.shopping.member;
 
import java.util.HashMap;
import java.util.Map;
 
public class MemoryMemberRepository implements MemberRepository {
    private static Map<Long, Member> store = new HashMap<>(); // store 탄생
 
    @Override
    public void save(Member member) {
 
    }
 
    @Override
    public Member findById(Long memberId) {
        return null;
    }
}

HashMap에는 get과 put이라는 메서드가 있습니다. 말 그대로 값 가져오는거랑 넣는 메서드인데요.

save와 findById함수에 이제 store변수를 사용해서 값 넣고 값 가져오는거 하면 되겠죠?

package com.naver.shopping.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); // 값 가져오기 구현
    }
}

save에는 store에 지정한 타입에 맞게 key로 id값을 가져와서 넣고, value는 member 그 자체를 넣어서 저장시켰습니다. findById에서는 key값인 id를 get()에 넣어서 해당 key인 member를 return해주고 있습니다.

이렇게 하면 일단 메모리에서 저장하는 회원 저장소 구현도 끝입니다.


서비스 구현하기

Repository는 데이터가 저장되는 곳과 밀접하게 연관되어있는 기능들을 구현하는 곳입니다. 그럼 사용자가 사용할 기능과 연관되어있는 기능을 구현할 곳도 필요하겠죠? 그게 서비스입니다.

서비스는 말 그대로 제공되는 기능이기 때문에 우리는 회원가입, 회원찾기 기능을 구현해보려 합니다.


서비스 인터페이스 생성

역시나 서비스도 저장소와 같이 인터페이스를 먼저 구성해봅시다. 위에서 말했듯이 회원가입, 회원찾기 함수를 가진 인터페이스를 만들어봅시다.

이번에도 마찬가지로 Member 패키지 안에 만들어주세요.

package com.naver.shopping.member;
 
public interface MemberService {
    void join(Member member);
    Member findMember(Long memberId);
}

음? Repository에서 함수 이름 바뀐거 말곤 딱히 없네요… 

지금 예시에선 Repository에서 만든 함수를 그대로 사용만 하면 될 정도로 간단한 예시만 다루고 있기 때문에 그렇습니다. Repository는 저장소 예를들어 DB에서 사용할 메서드, Service는 Repository에서 구현한 기능들을 사용하고 필요 시 더 기능들을 구현해서 사용자가 사용하게 되는 메서드를 넣는 차이점이 있다는걸 기억해주세요.


회원 서비스 구현

현재 예시의 서비스 구현은 매우 쉽습니다. 저장소에서 만든 기능들만 불러와서 사용하면 됩니다! 그러기 위해선 먼저 MemoryMemberRepository를 불러와야겠죠? 그리고 각각의 서비스 메서드에 맞게 사용하시면 됩니다.

package com.naver.shopping.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);
    }
}

MemoryMemberRepository를 불러올 때 MemberRepository형으로 불러온 이유도 제 Spring Boot의 2번글에 나와있습니다. Garen을 불러올 때 Garen형이 아닌 Champion으로 불러오니 변경이 수월했던 것 기억하시죠..?

자 그럼 우리의 회원관련 기능들이 모두 완성되었습니다. 와!!!!!!!!!!!


스프링은 어디에…?

일단 우리가 아는 지식으로 구현해보긴 했습니다. 근데 만들고보니 이건 Spring Boot 프로젝트가 아니었어도 똑같이 만들 수 있었을거같지않나요? 그냥 순수 JAVA의 기능만 사용한 것 같습니다.

맞습니다… 우리가 작성한 위의 모든 것들은 그냥 JAVA만 사용하였고 스프링의 기능은 아예 들어가지 않았습니다.

(스프링 없는 스프링 프로젝트 탄생)

현재 간단한 요구사항 구현에선 사실 스프링의 기능이 없이도 큰 불편 없이 구현하실 수 있습니다. 그러나 프로젝트가 점점 커져가면서 여러가지 불편한 상황들이 많이 발생할 것이예요.

그 후에 스프링을 적용시켜 수정해가면서 간결해지는 프로젝트를 보며 스프링의 매력을 더 절실히 느끼시길 원하는 마음에 해당 강의의 강사님은 초반에는 JAVA만으로 프로젝트를 구성하길 원하셨던 것 같습니다.

아마 6~7번 글까진 JAVA로 프로젝트를 구성하지 않을까 싶습니다. 그렇다 하더라도 저와 같이 불편함을 체험하면서 더 Spring Boot의 위대함을 느끼게 되었으면 좋겠습니다.


스프링의 계층구조

저희가 이번에 회원 기능을 구현하면서 하나의 파일만 생성한 것이 아닌 여러 파일들을 만들게 되었습니다. 크게 엔티티와 서비스, 저장소였는데요.

엔티티는 위에서 보셨다시피 사용될 데이터의 형태를 구성합니다. 회원이 가질 형태, ID와 이름과 등급을 가진 Class를 만들고 어디든 불러와 해당 형태를 쓸 수 있게 하는 것입니다.

**저장소(Repository)**는 데이터가 저장될 공간에 데이터를 넣거나 삭제하거나 불러오거나 등등 데이터 저장 공간에 사용 될 기능들을 구현하는 곳입니다.

**서비스(Service)**는 클라이언트가 사용할 기능들을 구현합니다. 구현하면서 데이터 저장 공간에 사용 될 기능들이 필요하면 저장소 기능을 불러와서 사용합니다.

그럼 아래와 같은 형태가 됩니다.

현재 저희는 메모리 회원 저장소만 구현한 상태입니다 저희가 만든 클래스명들을 통해 정리해보면 이렇게 됩니다.

DB를 이용한 Repository는 나중에 개발할 예정입니다.


마치며

다음으로는 요구사항중 아직 구현하지 않은 주문 기능을 구현해보도록 하겠습니다.

주문 기능 또한 회원 기능의 구현과 비슷하게 서비스와 저장소로 나누어 개발을 할 예정이고, 주문 기능은 등급에 따라 다른 할인 정책을 구현하는 것이 핵심입니다.

해당 글에서 추가된 git commit 내용은 아래 링크에서 확인하실 수 있습니다.

ADD Member domain by pure java · KIMB0B/blog_spring@b96db8a

그럼 지금까지 읽어주셔서 감사합니다!!

👉다음 글 보러가기