2.2 회원 도메인 설계 및 개발

회원 비즈니스 요구사항 설계

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

회원 데이터, 할인정책 같은 부분은 변경가능성이 상당히 높다. -> 객체지향 설계 방법을 사용하여, 인터페이스를 만들고 구현체를 언제든지 갈아끼울 수 있도록 설계하자.

참고

프로젝트 환경설정을 위해 스프링 부트를 사용했으나,
지금은 스프링 없이 순수한 JAVA로 프로젝트를 개발중이다.


회원 도메인 설계

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

회원 도메인 협력 관계

도메인 설계(역할을 정의한다)

Pasted image 20250313111121.png

  • 회원 저장소 라는 역할(인터페이스)과 메모리 회원 저장소 / DB 회원 저장소 / 외부 연동 저장소 라는 구현(구현체) 미 확정 요소에 대하여 개발 이후 갈아끼울 수 있도록 역할과 구현을 분리

  • 기획자들도 볼 수 있는 그림이다.

구현 레벨 설계(회원 클래스 다이어그램)

Pasted image 20250313111614.png

  • implements : 구현 이라는 뜻으로, 역할(interface)를 실제로 구현하는 것이다. interface에 대해서 구현체가 단 1개만 존재할 때, 'Impl' 을 붙이는 관행이 있다.

  • 도메인 설계를 구체화 하여, 클래스 다이어그램을 만들어 낸다.

회원 객체 다이어그램

Pasted image 20250313111817.png

  • 객체 간, 메모리간의 참조들이 어떠한 방식으로 그려지는지.

  • 회원 서비스 : MemberServiceImpl / 메모리 회원 저장소 : MemoryMemberRepository(클래스)구현체

  • 객체 다이어그램은 서버가 올라간 이후 (ex. new 해서 참조하는 것) 동적으로 정해지는 것들에 대한, new로 만들어진 인스턴스끼리의 참조


회원 도메인 개발

보러가기 ▶ SpringCore GitHub


생성자, getter / setter 자동 설정하기

  • 인텔리J셋팅에서 - Generate.. 단축키 설정 Pasted image 20250313114207.png
package hello.core.member;  
  
public class Member {  
  
    private Long id;  
    private String name;  
    private Grade grade;  

	//작성 후 위에서 설정한 단축키
}

Pasted image 20250313114322.png Pasted image 20250313114332.png

  • 생성된 모습
package hello.core.member;  
  
public class Member {  
  
    private Long id;  
    private String name;  
    private Grade grade;  

	//생성자 및 getter/setter 자동 생성
    public Member(Long id, String name, Grade grade) {  
        this.id = id;  
        this.name = name;  
        this.grade = grade;  
    }
      
    ...
    
    public void setName(String name) {  
	    this.name = name;  
	}  
  
	public void setGrade(Grade grade) {  
	    this.grade = grade;  
	}
}

회원 서비스 프로젝트 구조

Pasted image 20250313151739.png

서비스 로직

package hello.core.member;  
  
public class MemberServiceImpl implements MemberService {  
  
    // 레파지토리 구현체 생성  
    private final MemberRepository memberRepository = new MemoryMemberRepository();  
  
    @Override  
    public void join(Member member) {  
        // memberRepository 를 호출 하여도, 다형성에 의해서 MemoryMemberRepository 의 save 호출  
        memberRepository.save(member);  
    }  
  
    @Override  
    public Member findMember(Long memberId) {  
        return memberRepository.findById(memberId);  
    }  
}

회원 서비스 테스트

MemberApp 클래스 생성 (테스트용)

Pasted image 20250313152221.png

단축키 psvm 후 enter 시 메인 생성 가능

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 {  
    //*단축키 psvm 후 enter 시 메인 생성 가능*  
    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());  
    }  
}
// 출력
new member = memberA
findMember = memberA
  • 순수한 자바 코드로 애플리케이션을 개발/테스트 완료.

Junit 사용하여 테스트 하기

(기본 디펜던시에 포함)

  • 테스트 패키지에 MemberServiceTest 생성하기 Pasted image 20250313152958.png

assertj API 사용

  • 객체 비교 Pasted image 20250313153455.png
package hello.core.member;  
  
import org.assertj.core.api.Assertions;  
import org.junit.jupiter.api.Test;  
  
public class MemberServiceTest {  
  
    MemberService memberService = new MemberServiceImpl();  
  
    @Test  
    void join() {  
        //give (~한 환경에서)  
        Member member = new Member(1L, "memberA", Grade.VIP);  
  
        //when (~ 했을 때)  
        memberService.join(member);  
        Member findMember = memberService.findMember(1L);  
  
        //then (~ 결과가 나온다)  
        Assertions.assertThat(member).isEqualTo(findMember);  
    }  
}
  • 테스트 실행 Pasted image 20250313153605.png

  • 통과 Pasted image 20250313153630.png

  • 값 변경시 실패 확인 - memberId : 2L Pasted image 20250313153740.png


회원 도메인 설계의 문제점?

  1. 다른 저장소로 변경할 때 OCP 원칙을 잘 준수하고 있는가?
  2. DIP를 잘 지키고 있는가?
'의존관계가 인터페이스 뿐만 아니라, 구현까지 모두 의존하는 문제점이 있음'
private final MemberRepository memberRepository = new MemoryMemberRepository();
  • MemberService에서, 분명히 interface(MemberRepository)를 의존하지만, 실제 할당하는 부분에서 구현체에 의존하고 있다.

=> 추상화에도 의존하고 구체화에도 의존하고 있다. => DIP(의존관계역전원칙)를 위반하고 있다.