이 게시글은 인프런의 유료 강의 스프링 핵심 원리 - 기본편을 수강하며 공부한 내용을 담고있습니다.
저작권에 의해 강의내용에 해당하는 전체 코드는 공개할 수 없습니다.
저작권에 의해 모든 내용을 담을 수 없습니다. 중복 및 반복되는 내용은 생략했습니다.
비즈니스 요구사항과 설계
다음 요구사항을 보자
- 회원
- 회원을 가입하고 조회할 수 있다.
- 회원은 일반, VIP 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.(미확정)
- 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- VIP 는 1000원 고정 금액 할인 정책을 적용해달라.(미확정)
- 할인 정책은 변경가능성이 높고, 오픈 직전까지 미루고싶다. 최악의 경우 할인 정책을 적용하지 않을 수 있다.
위 요구사항을 보면 미확정된 요구사항이 존재한다.
이 경우 역할과 구현을 나눠 설계를 해야 나중에 수정을 빠르게 할 수 있다.
예를 들어, 회원 데이터는 MySQL 을 사용해 개발을 하되 나중에 바로 Oracle 로 바꿔도 문제 없도록 설계한다.
회원 도메인 설계
- 회원
- 회원을 가입하고 조회할 수 있다.
- 회원은 일반, VIP 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.(미확정)

요구 사항만 보고 대략적인 설계를 하면 위와 같은 설계가 될 것이다.
클라이언트는 회원 서비스를 호출하고, 회원 서비스는 회원 저장소를 호출하는 관계를 가진다.
회원 저장소는 어떤 DB를 가지게 될 지 모르지만 차후 사용하게 될 것이라 예상되는 구현 객체들을 표시해준다.
위 그림과 요구 사항을 바탕으로 클래스 다이어그램과 객체 다이어그램을 작성한다.

회원 클래스 다이어그램
MemberServiceImpl 은 MemberService 를 구현(implement)한 구현체를 의미한다.
인터페이스를 구현한 구현체라는 의미이다. MemberRepository의 구현체는 세 개이지만 편의상 하나는 생략했다.

회원 객체 다이어그램
클래스 다이어그램만 보면 프로그램 구동시에 어떤 구현체가 동작할지 모른다.
객체 다이어그램에는 실제 동작하는 구현체를 객체로 설정해서 알아보기 쉽게한다.
MemberService 의 경우 구현할 수 있는 구현체가 하나이므로 생각할 필요없이 MemberServiceImpl이 되고, MemberRepository의 경우 세 개의 구현체 중 MemoryMemberRepository 를 구현체로 결정하였기 때문에 MemoryMemberRepository를 객체 다이어그램에 작성해주었다.
MemoryMemberRepository 를 구현체로 결정한 이유
처음에는 Memory 에 회원 데이터를 저장하는 코드로 구현 후 추후 DB 가 결정되면 구현체를 변경하는 예제이기 때문이다.
회원 도메인 개발
개발은 클래스 다이어그램을 보면서 하면된다.
일단 Member 패키지를 만든다. Member 랑 관련된 것들을 넣는 곳이다. Member, MemberService, MemberServiceImple 등..


src > main > java > 패키지명 을 우클릭 > new > package 를 누르고 member 패키지를 생성한다.

방금 만든 member 패키지를 우클릭하여 new > Java Class > enum 을 선택하고 Grade 란 이름으로 생성한다.
그리고 BASIC, VIP을 작성한다.
(enum(열거형) 에 대한 설명은 생략한다)

이번에는 member 패키지를 우클릭하여 new > Java Class > class 를 선택하고 Member 라는 이름으로 생성한다.
그리고 회원id, 회원name, 회원Grade 를 멤버변수로 작성하고 생성자, Getter and Setter 를 작성한다.

이제 위에서 만든 Member 객체를 저장할 수 있는 저장소(Repository)를 작성한다.
클래스 다이어그램에서 미리 설계했듯이 인터페이스와 구현체를 각각 만들어준다.(인터페이스 코드는 생략한다.)
인터페이스에서는 간단하게 회원을 저장하고, 회원정보 꺼내오는 메소드를 작성한다.
구현체(MemoryMemberRepository)에서는 Map 자료구조를 사용해 회원정보를 Key:Value 형태로 값을 저장하고 꺼낼 수 있도록 구현한다.
이렇게 만든 이유
회원 정보를 저장할 때 어떤 방법을 사용할 지 아직 정하지 않았기 때문에 인터페이스만 제대로 만들어두고 구현체는 어떻게든 값을 저장할 수 있도록 구현한다.

마찬가지로 클래스 다이어그램을 참고하여 MemberService 인터페이스과 구현체를 작성한다.(인터페이스 코드는 생략한다.)
왜 이렇게 작성하나요?
코드를 보면 의문이 든다. MemberServiceImple 에서 바로 DB에 저장을 하면 되는데, 왜 중복되는 코드를 사용하여 한번 더 거쳐서 작성했는가? 이다.
실제로 Service 에서는 다른 여러 로직을 가지고 있다. 예를 들면 join() 메서드에서는 중복회원 검사를 먼저 하고, 문제가 없다고 판단되면 save() 를 호출해야한다.
하지만 현재 설명하려는 내용에서는 필요없기 때문에 생략한 것이다.
역할과 구현을 보여주는 코드
위 사진의 memberRepository 코드를 보면 역할과 구현이 분리된 모습을 볼 수 있다.
1
2
3
4
5
// 현재
MemberRepository memberRepository = new MemoryMemberRepository;
// 나중에 이렇게 수정
MemberRepository memberRepository = new DBMemberRepository;
현재는 MemomryMemberRepository 객체를 생성해서 memberRepository 에 넣었다. 지금은 DB가 정해지지 않아 개발단계에서 임시로 사용할 구현체를 넣었지만 추후 DB가 정해지면 해당 DB 구현체를 만들고 memberRepository 에 넣어 갈아끼워줄 수 있다.
물론 위 코드는 DIP 원칙을 지키지 않았다. MemberService 가 memberRepository(역할) 과 MemoryMemberRepository(구현체)를 의존하고있기 때문이다. 이것은 추후에 수정하면서 배우게된다.
코드 테스트

이제 위에서 작성한 코드가 잘 돌아가는지 테스트한다.
MemberApp 클래스를 만들고 main 메서드를 작성한다.
main 메서드 안에서 member 객체를 만들어 회원 저장소에 저장도 해보고 꺼내기도 해보고 비교도 하면서 코드가 정상적으로 작동하는지 테스트한다.
테스트 코드 작성하기
위에서는 main 메서드에서 테스트를 진행했다. 하지만 실제 개발 과정에서는 main 메서드나 다른 메서드들에서 테스트 코드를 작성하면 나중에 다 삭제해야한다는 것을 알고있다.
아니면 자잘한 개발 몇 번 해봤다면 테스트 코드를 작성했다가 지우고 조금 수정하면 또 작성했다가 지우고 반복하는 일을 많이 경험해봤을 것이다.
이러한 문제를 해결하기 위해 jUnit 이라는 테스트코드 프레임워크를 사용한다. 테스트 코드를 메인 로직에서 분리하여 영향을 주지 않으면서도 테스트를 수행할 수 있게 된다.
다음은 MemberService 의 join 메서드를 테스트하는 코드이다.

MemberService 에 대응되는 이름 MemberServiceTest 로 클래스를 만든다.(경로는 이미지 참고)
@Test 어노테이션을 붙여 아래 작성될 메서드(join)가 테스트 코드임을 명시한다.
메서드 이름은 join으로 통일한다. 이름을 통일시켜야지 무엇을 테스트 하는지 알기쉽다.

실행을 하면 아래 콘솔창에 녹색 체크표시가 나온다. 오류 없이 잘 수행되면 녹색 체크로 나온다.
아무튼 이제 여기서 테스트를 하면 main 메서드 또는 로직을 담당하는 메서드에서 테스트 코드를 작성할 필요가 없어지고, 작은 단위로 테스트를 할 수도 있게된다.
주문과 할인 정책 도메인
설계
- 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- VIP 는 1000원 고정 금액 할인 정책을 적용해달라.(미확정)
- 할인 정책은 변경가능성이 높고, 오픈 직전까지 미루고싶다. 최악의 경우 할인 정책을 적용하지 않을 수 있다.
주문과 할인 정책 도메인 설계는 회원 도메인 설계를 했던것처럼 진행하면 된다.(중복이라서 생략)
개발
주문과 할인 정책 도메인 설계 과정에서 작성한 클래스 다이어그램을 기반으로 코드를 작성하면된다. (전체적인 코드는 생략)

코드를 살펴보면 SRP(단일 책임 원칙) 를 잘 지킨 모습을 확인할 수 있다.
SRP(단일 책임 원칙) 을 지키지 않고 설계(혹은 개발)을 했다면, 할인 로직을 createOrder 메서드 안에 작성할 수도 있었을 것이다.
만약 그렇다면 할인 로직을 수정해야할 때 DiscountPolicy 구현체도 수정하고 createOrder 메서드도 수정해야하는 불상사가 발생할 것이다.
테스트 코드 작성하기
이것도 마찬가지로 회원 도메인의 테스트 코드 작성하는 방법을 참고하여 주문과 할인 정책 테스트 코드를 작성한다.
이전 포스팅 : 초기세팅
다음 포스팅 : 예제에 객체지향 원리 적용