/* 이 글은 김영한님의 강의를 보고 정리하려고 작성한 글입니다. */
스프링 핵심 원리 - 기본편 | 김영한 - 인프런 (inflearn.com)
스프링 핵심 원리 - 기본편 | 김영한 - 인프런
김영한 | 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보
www.inflearn.com
이제 본격적으로 순수 자바코드로 아래 요구사항에 대한 프로젝트를 할 것이다.!
회원 도메인
1. 회원 도메인 설계
// Member.class
public class Member {
//클래스의 멤버변수는 private 처리를 해서 클래스 내에서만 사용 가능하도록, 하지만 getter, setter 를 사용해 외부에서 읽어올 수 있음
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) / 외부 시스템 연동 회원 저장소 ]
이 둘 중 어떤 저장소를 사용할 지 미확정이기 때문에
➔ 메모리 회원 저장소를 만들어 개발을 진행한다.(로컬 개발, 테스트 진행용)
회원 도메인 협력 관계
// MemberService.class 회원서비스 역할
public interface MemberService {
void join(Member member);
Member findMember(Long memberId);
}
// MemberServiceImpl.class 회원 서비스 구현체
@Component // 각 클래스가 컴포넌트 스캔의 대상이 되도록 에노테이션을 붙여줌
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
@Autowired // 생성자에서 여러 의존관계도 한번에 주입 가능
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
} //생성자
/**
* 회원가입
*/
@Override
public void join(Member member) {
memberRepository.save(member);
}
/**
* 회원조회
*/
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
//테스트 용도
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
// MemberRepository.class 회원 저장소 역할
public interface MemberRepository {
void save(Member member) ;
Member findById(Long memberId);
}
◾ 회원 서비스 = MemberService <interface>
◾ 회원 서비스의 구현체 = MemberServiceImpl
◾ 회원 저장소 = MemberRepository <interface>
◾ 회원 저장소의 구현체 = MemoryMemberRepository / DbMemberRepository
➔ MemoryMemberRepository 사용
💡 여기서 궁금했던 점
: 저장소는 나중에 교체하기위해 Interface로 추상화 하는건 이해가 가나 Service는 교체 가능성이 없는데 추상화를 하는 이유는? 이 궁금했는데 누군가가 인프런에 질문을 했고, 김영한님이 답변한게 있어서 참조
객체 지향 설계와 스프링 마지막에서 김영한님이 말씀했던
* 이상적으로는 모든 설계에 인터페이스를 부여하자
라는 개념으로 인해 서비스에도 추상화를 사용한 것이다.
실무 고민
* 하지만 인터페이스를 도입하면 추상화라는 비용이 발생한다.
* 기능을 확장할 가능성이 없다면, 구체 클래스를 직접 사용하고,
향후 꼭 필요할 때 리팩터링해서 인터페이스를 도입하는 것도 방법이다.
강의에서는 이상적으로 역할과 구현을 분리하는 것에 초점을 맞추어서 이런 부분들도 분리했다.
회원 객체 다이어그램
◾ 객체 다이어 그램 : 실제 서버에 올라왔을 때 객체들의 메모리 참조를 그린 것
◾ 결과적으로 우리가 만들어야 하는 그림
2. 회원 도메인 개발
MemoryMemberRepository - 회원 저장소의 구현체
// MemoryMemberRepository.class 회원 저장소의 구현체
@Component // 각 클래스가 컴포넌트 스캔의 대상이 되도록 에노테이션을 붙여줌
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);
}
}
💡 궁금증 1
: store를 만들 때 static을 사용한 이유는?
➔ static 을 사용하면 객체를 생성하지 않고 클래스 이름으로 직접 접근할 수 있으며, 오로지 단 한 번만 생성되기 때문에 모든 객체에서 동일한 값으로 사용할 수 있다.
➔ 따라서 다른 객체 간에 값을 전달하거나 공유하는데 용이하다.
➔ 이러한 static의 특징을 이용하여 단 하나의 Map을 사용하기 위해 static으로 Map을 생성한 것.
➔ 만약 static을 제거한다면 new MemberRepository(); 를 작성할 때 마다 새로운 Map이 생성되어 객체마다 다른 값의 Map을 가지게 된다.
💡 궁금증 2
: store를 HashMap으로 생성했는데 일반적으로 동시성 이슈를 방지하기 위해 ConcurrentHashMap으로 생성하는게 좋다고 하셨다. 예제를 단순하게 하기 위해 HashMap으로 생성했는데 둘의 차이점은?
➔ HashMap과 ConcurrentHashMap은 둘다 'key-value' 형태의 데이터를 저장하는 자료구조이다.
HashMap
- 데이터의 삽입, 삭제, 검색등의 연산이 빠르다
- null 값을 저장할 수 있다
- 싱글스레드 환경에서 안전하게 사용할 수 있다
ConcurrentHashMap
- 내부적으로 데이터를 분할하여 처리하기 때문에 멀티스레드 환경해서 HashMap보다 더 빠른 속도로 동작한다.
- null 값을 저장할 수 없다
- 멀티스레트 환경에서 안전하게 사용할 수 있으며 여러 스레드에서 동시에 접근해도 일관성을 유지한다.
MemberServiceImpl
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 null;
}
}
💡 궁금증 1
: memberRepository를 만들 때 final로 선언한 이유?
◾ 불변성(Immutability): final 필드는 한 번 초기화되면 값을 변경할 수 없다. 이것은 필드에 대한 안정성을 제공하며, 필드 값을 보호한다. memberRepository가 한 번 설정되면 다른 객체로 변경되지 않도록 보장한다.
◾ 스레드 안정성(Thread Safety): final 필드는 다중 스레드 환경에서 안전하게 사용된다. 여러 스레드가 동시에 memberRepository를 수정하려고 시도하는 것을 방지하며, 동시 접근에 대한 문제를 방지한다.
◾ 가독성과 유지보수성: final 키워드를 사용하면 코드의 의도가 명확해진다. memberRepository가 한 번 설정되고 변경되지 않는다는 사실은 코드를 읽는 사람에게 중요한 정보를 전달한다.
◾ 오류 방지: final로 선언된 필드는 컴파일 시간에 한 번 초기화되며, 런타임 중에 다시 설정되는 오류를 방지한다.
3. 테스트 코드 작성
위치 중요
◾ 나중에 빌드하면 테스트 코드는 빠진다고 함
MemberServiceTest
public class MemberServiceTest {
MemberService memberService;
// 테스트가 실행하기 전에 무조건 실행
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
@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);
}
}
◾ Assertions Import할 때 "import org.assertj.core.api.Assertions;"
💡 궁금증 1
: Assertions의 기능과 assertThat의 기능 종류
➔ Assertions은 테스트 코드에서 사용되며 특정 조건이 참이어야 테스트가 계속 진행되도록 하는 기능이며
➔ assertThat은 다양한 비교 및 검증작업을 수행할 때 사용한다.
➔ assertThat 메서드 종류
String ex1 = "hello wolrd";
String ex2 = "hello wolrd";
◾ assertThat(ex1).isEqualTo(ex2); / isNotEqualTo(ex1); : 두 값이 같은지 / 다른지 비교
◾ assertThat(ex1).isNull(); / isNotNull(); : 값이 null인지 / null이 아닌지 확인
◾ assertThat(ex1).istrue(); / isfalse(); : 값이 true인지 / false인지 확인
◾ assertThat(ex1).startWith("hello") / endsWith("wolrd") : 문자열의 시작, 끝 확인
◾ assertThat(ex1).contains("hello") : 문자열 특정부분에 포함되는지 확인
등등 많으니까 찾아보기
4. 설계의 문제점
◾ 이 코드는 의존관계가 인터페이스 뿐만 아니라 구현체까지 모두 의존하는 문제점이 있음
private final MemberRepository memberRepository = new MemoryMemberRepository();
➔ 여기서 MemberRepository는 인터페이스에 의존하지만 실제 할당하는 부분은 구현체를 의존하고 있음
➔ 추상화에도 의존하고 구현체에도 의존하고 있는 상태 = DIP 위반
➔ 해결 방안은 '주문'까지 만들고 나서 설명해주신다고 함
'Spring' 카테고리의 다른 글
AWS : AWS IAM, AWS CLI (0) | 2024.06.21 |
---|---|
[스프링 핵심 원리 - 기본편] #4 인프런 강의 정리(객체 지향 원리 적용 - 새로운 할인 정책 개발, 적용, 문제점) (2) | 2024.06.11 |
[스프링 핵심 원리 - 기본편] #2 인프런 강의 정리(예제만들기 - 프로젝트 생성, 비즈니스 요구사항과 설계) (0) | 2024.06.10 |
[스프링 핵심 원리 - 기본편] #1 인프런 강의 정리(객체 지향 설계의 5가지 원칙 SOLID, 객체지향 설계와 스프링) (1) | 2024.06.10 |
상속 추상화, 캡슐화, 다형성 개념 (0) | 2024.03.29 |