이 글은 강의 : 김영한님의 - "스프링 핵심원리 - 기본편"을 듣고 정리한 내용입니다. 😁😁
이전까지는 요구사항을 확인했고, 이번에는 회원 도메인에 대해 설계해보자.
요구사항을 토대로 도메인 간 협력 관계를 그려보면 이렇게 나온다.
클라이언트와 회원 서비스, 회원 저장소는 역할이라고 보면 된다. 회원 저장소 역할은 이전에도 봤듯이 구현이 변경될 수 있으므로 실제 구현은 메모리, DB, 외부 시스템 연동 회원 이렇게 저장소가 세 개가 있다.
클래스 다이어그램은 프로그램을 실행하지 않고 클래스만 분석해서 볼 수 있는 그림이다.
구현 레벨. 즉 코드 레벨까지 보고 설계를 하면 클래스 다이어그램이 그려지는 것이다.
MemberService라는 인터페이스가 있고, 이를 구현한 MemberServiceImpl이 있다.
이는 MemberRepository라는 역할에 의존하고 있다. 그리고 이 MemberRepository는 두 개의 구현체(MemoryMemberRepository, DbMemberRepository)를 갖는다.
마지막은 회원 객체 다이어그램이다. 이것은 실제 서비스가 서버에 올라갔을 경우에 객체간에 연관관계를 보여준다. 이렇게 설계가 끝났으니 이를 구현해보자.
이제 실제로 코딩을 하는 단계이다.
회원 등급은 enum으로 만든다. class 대신 enum으로 선언하면 이 객체는 상수의 집합이라는 것을 명시하는 것! 기억하자.
package core.order.member;
public enum Grade {
BASIC,
VIP
}
package hello.core.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;
}
}
package hello.core.member;
public interface MemberRepository {
// 회원 저장 메서드
void save(Member member);
// 회원의 id로 회원을 조회하는 메서드
Member findById(Long memberId);
}
DB가 아직 확정되지 않았으므로, 가장 단순한 메모리 회원 저장소를 구현해서 개발 진행
package hello.core.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); // hashmap에 (id, member) 쌍으로 저장
}
@Override
public Member findById(Long memberId) {
return store.get(memberId); // id(key)값으로 member 얻어오기
}
}
HashMap으로 메모리에 멤버를 저장하는 기능을 한다.
사실 ConCurrent HashMap을 써야 동시성 문제를 방지할 수 있으나 예제이기 때문에 그냥 HashMap을 사용한다.
package hello.core.member;
public interface MemberService {
//회원 가입 메서드
void join(Member member);
// 회원 조회 메서드
Member findMember(Long memberId);
}
package hello.core.member;
public class MemberServiceImpl implements MemberService{
// 가입과 조회를 하기 위해 MemberRepository의 구현체를 생성
// MemberServiceImpl은 MemberRepository, MemoryMemberRepository 모두 의존(DIP 위반)
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);
}
}
여기서(MemberServiceImpl) 어떤 저장소 역할을 사용할지를 결정하게 된다. 지금은 OCP, DIP를 위반하지만 MemoryMemberRepository를 직접 생성해서 의존한다.
여기까지 기본적인 회원 도메인의 개발은 끝난다.
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
public class MemberApp {
public static void main(String[] args) {
MemberService memberService = new MemberServiceImpl();
Member member = new Member(1L, "memberA", Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("new member = " + member.getName());
System.out.println("findMember = " + findMember.getName());
}
}
애플리케이션 로직으로 main함수에서 이렇게 테스트하는 것은 좋은 방법이 아니다. JUnit 테스트로 테스트하자!
package core.order.member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
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);
}
}
given, when, then 으로 테스트를 나눠볼 수 있다.
given 은 이렇게 주어진 조건에서, when 이렇게 동작을 하면, then 이런 결과가 나온다는 것이다. 이것도 실행시켜보면 정상적으로 테스트가 통과하는 것을 확인할 수 있다.
동작은 정상적으로 하지만 문제가 있다는 것을 알 수 있다.
public class MemberServiceImpl implements MemberService{
// 가입과 조회를 하기 위해 MemberRepository의 구현체를 생성
// MemberServiceImpl은 MemberRepository, MemoryMemberRepository 모두 의존(DIP 위반)
private final MemberRepository memberRepository = new MemoryMemberRepository();
...
}
MemberService 인터페이스의 구현체인 MemberServiceImpl 클래스는
MemberRepository 와 MemoryMemberRepository를 모두 의존하고 있다.
즉, 인터페이스(역할)뿐 아니라 구현까지 모두 의존하는 문제가 있다. (OCP, DIP 모두 위배)