스프링 입문 - Ch 3. 회원 관리 예제 - 백엔드 개발(1)

seren-dev·2022년 3월 20일
0

스프링 입문

목록 보기
3/11
  • 비즈니스 요구사항 정리
  • 회원 도메인과 리포지토리 만들기
    Repository 객체: 회원 도메인 객체를 저장하고 불러올수 있는 저장소
  • 회원 도메인 테스트 케이스 작성
  • 회원 서비스 개발
  • 회원 서비스 테스트: junit 테스트 프레임워크 사용

비즈니스 요구사항 정리

  • 데이터: 회원ID, 이름
  • 기능: 회원 등록, 조회
  • 아직 데이터 저장소가 선정되지 않음(가상의 시나리오)
    관계형 , NoSQL 등등

  • 컨트롤러: 웹 MVC의 컨트롤러 역할
  • 서비스: 비즈니스 도메인 객체를 가지고 핵심 비즈니스 로직 구현하는 계층
    예) 중복 가입x
  • 리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
  • 도메인: 비즈니스 도메인 객체, 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨

회원 비즈니스 로직에는 회원 서비스 클래스가 있음
회원 리포지토리: 회원을 저장하는 클래스, interface로 설계

  • 아직 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계
  • 데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정(MyBatis, JPA 등등)
  • 개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용 (단순하고 빠르게 만들 수 있음)
  • 향후 RDB로 할지 정한 후 바꿔 끼움

회원 도메인과 리포지토리 만들기

1. 회원 객체

  • hello.hellospring 패키지에서 domain 패키지 생성
  • domain 패키지 내에서 Member 클래스 생성

패키지는 비슷한 성격의 자바 클래스들을 모아 놓은 자바의 디렉토리이다.
프로젝트패키지의 모음이다.
https://wikidocs.net/231

Member.java

package hello.hellospring.domain;

public class Member {
    private Long id;
    private String name;

    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;
    }
}

  • id는 시스템이 저장하는 임의의 값

2. 회원 리포지토리 인터페이스

  • hello.hellospring 패키지에서 repository 패키지 생성
  • repository 패키지 내에서 MemberRepository 인터페이스 생성

MemberRepository.java

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.List;
import java.util.Optional;

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    List<Member> findAll();
}
  • Optional: null 값을 반환할 경우 optional로 감싸서 반환

Optional<T>null이 올 수 있는 값을 감싸는 Wrapper 클래스로, 참조하더라도 NPE가 발생하지 않도록 도와준다.
http://www.tcpschool.com/java/java_stream_optional
http://homoefficio.github.io/2019/10/03/Java-Optional-%EB%B0%94%EB%A5%B4%EA%B2%8C-%EC%93%B0%EA%B8%B0/
https://mangkyu.tistory.com/70

Java의 optional이란?
기존의 반환 값 타입 T에 Optional<T>를 Wrapping 하여, null 대신 Optional 안에 빈 타입 객체를 돌려주는 기법이다.

Optional 은 반환값이 없을 수도 있다고 미리 가정하고, 이 반환값을 통제하기 위해 사용한다. 그럼 걍 null을 뱉을 수도 있지않나?

  • 반환값으로 null 을 return 받는 경우, 추후 null을 반환해준 라인과는 상관없는 코드에서 null pointer Exception이 터질 수 있어서 트래킹이 어렵다.
  • 즉, 예외 발생 시 예외 스택 전체를 캡쳐하는 비용을 아낄 수 있다.
  • Optional 안에 빈 타입 객체가 들어 있는 경우, Optional에서 정의된 method 등을 이용하여, 좀 더 유연한 핸들링이 가능하다.

언제 Optional을 사용하는게 좋을까?

  • 값을 반환하지 못할 가능성이 있고, 호출할 때마다 반환값이 없을 가능성을 염두에 둬야 하는 메서드라면 옵셔널 반환을 고려할 수 있음
  • 즉, 반환 값이 없을 수 있고, 클라이언트가 이 상황을 특별하게 처리해야할 경우 Optional 로 반환하는게 좋다.
    그리고 optional로 Wrapping하는 비용은 그리 크지 않음...!

Optional로 당연히 해서는 안될 것
Optional 을 반환하는 메서드에서는 절대 null을 뱉게 하면 안됨(null 뱉으면 optional Wrapping의미가 없으므로..)
https://www.inflearn.com/questions/90949

  • Member save(Member memeber) : 회원을 저장소에 저장, 회원을 저장하면 저장된 회원 반환
  • findById, findByName: 저장소에서 회원을 찾음
  • findAll: 지금까지 저장된 모든 회원 리스트 반환

3. 회원 리포지토리 메모리 구현체

  • repository 패키지 내 MemoryMemberRepository 클래스 생성

MemoryMemberRepository.java

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository{

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }
}
  • 회원을 저장할 Map 생성
  • 키는 Long, 값은 Member
  • 간단한 HashMap
  • sequence 변수는 0,1,2... key 값 생성
  • 실무에서는 동시성 문제가 있을 수 있으므로 공유되는 변수는 ConcurrentHashMap, 그리고 AtomicLong 사용 고려
  • Optional.ofNullable(store.get(id));
    null이어도 감싸서 반환한다.
    그럼 클라이언트에서 무언가 처리 할 수 있다.
  • store.values : Collection<Member>

Static으로 선언한 이유

수업 예제에서도 Map을 static으로 생성하였습니다. 이렇게 되면 서비스에서 new MeberRepository(); 하든, 컨트롤러에서 new MemberRepository() 하든 테스트에서 new MemberRepository() 하든 애플리케이션이 시작되고 종료될 때까지 Map은 오로지 단 한번만 생성됩니다. 그리고 이 Map을 모든 인스턴스가 공동으로 사용하게 됩니다.
예제에서는 바로 이러한 static의 특성을 이용하여 '단 하나의 Map'만 사용하기 위해 static으로 Map을 생성한 것입니다. 만약 Map에 붙은 static을 제거하게 되면 new MemberRepository() 를 작성할 때마다 각각의 인스턴스가 자기만의 Map 을 갖게 됩니다. 이 Map은 인스턴스끼리 공유하지 않고 자기 혼자만 사용하는 Map이 됩니다.
https://www.inflearn.com/questions/240845

Map store = new HashMap()의 이점

  1. Map 인터페이스의 제약을 따르겠다는 의도를 명확하게 드러냅니다.
  2. 사용하는 코드가 Map 인터페이스 제약을 따르기 때문에 향후 변경시에 사용코드를 변경하지 않아도 됩니다.
  3. HashMap을 다른 클래스로 변경이 필요하면 선언하는 코드만 변경하면 됩니다. 사용하는 코드를 고민하지 않아도 됩니다.
  4. 다른 개발자들이 이 코드를 나중에 더 성능이 좋거나 동시성 처리가 가능한 종류의 구체적인 Map으로 변경해야 할 때 HashMap store = new HashMap()이라고 되어 있다면, 변경 시점에 상당히 많은 고민을 해야 하지만 Map store = new HashMap()으로 선언이 되어 있다면 편안하게 선언부를 변경할 수 있습니다.
  5. 개발은 무의미한 자유도를 제공하는 것 보다, 제약을 부여하는 것이 혼란을 줄이고, 유지보수하기 쉽습니다.
  6. 만약 정말 HashMap의 구체적인 기능을 사용해야 한다면 HashMap store = new HashMap() 이라고 선언하는 것이 맞습니다.
  7. class MemberRepository {private Map store;}
    new MemberRepository(new HashMap());
    new MemberRepository(new ConcurrentHashMap()); 이렇게 해서 MemberRepository를 전혀 변경하지 않고, 외부에서 구현 객체를 생성해서 파라미터로 넘길 수 있습니다. 이런 것을 의존관계 주입( DI )이라고 하는데요. 이런 방법도 다형성 덕분에 사용할 수 있습니다.

Long과 long 차이

new Member() 처럼 Member 객체를 생성하는 시점에는 id 값이 없어야 합니다. 그래서 없다는 표현을 null로 하는 것이 좋습니다.
그런데 long을 사용하면 null을 입력할 수 없고, 0이라는 값을 넣어두어야 합니다.
이런 점 때문에 Long을 사용함


Stream

스트림(Stream)은 자바 8에서 추가된 기능으로 함수형 인터페이스인 람다(lambda)를 활용할 수 있는 기술입니다. 예전에는 배열이나 컬렉션을 반복문을 순회하면서 요소를 하나씩 꺼내 여러가지 코드를(예를 들어 if 조건문 등) 섞어서 작성했다면 스트림과 람다를 이용하여 코드의 양을 대폭 줄이고 조금 더 간결하게 코드를 작성할 수 있습니다.
또한 스트림을 이용하면 멀티 스레드 환경에 필요한 코드를 작성하지 않고도 데이터를 병렬로 처리할 수 있습니다. 그러니까 스레드를 이용하여 많은 데이터들을 빠르게 처리할 수 있지요. 기존의 반복문을 사용한다면 synchronized와 같은 병렬성을 위한 동기화 코드를 관리해야 합니다.

  • 자바에서는 실무에서 List 많이 쓰임
  • 검증을 위해 테스트케이스를 작성하자!

0개의 댓글