스프링 입문 (김영한) 강의 요약 (2) -

Jeuk Oh·2021년 9월 25일
1

스프링

목록 보기
2/3

김영한님의 스프링 입문 강의를 듣고있습니다. 필기 용 요약 본.

제가 쌩초보라서 ㅎㅎ; 자바에 익숙한 사람이라면 답답하기만 하고 별로 도움이 안될 것입니다.

회원 관리 예제 - 백엔드 개발

1. 비즈니스 요구사항

앞 선 강의에서 간단하게 Controller와 Viewtemplate 사용법을 배웠으니 바로 예제에 돌입한다. 빠른 진행 좋습니다.

구현해야하는 기능 및 데이터 요구사항으로 다음과 같은 항목이 왔다고 가정하자.

데이터 : 회원ID, 이름
기능 : 회원 등록, 조회
아직 어떤 DB를 사용할지 선정되지 않은 사항

웹 애플리케이션 계층 구조를 먼저 생각하고 코드를 작성한다.

먼저 웹 MVC의 컨트롤러 구조
핵심 비즈니스 로직을 작성하는 서비스 구조
회원 데이터, 주문 데이터 등 비즈니스 객체를 작성하는 도메인 구조
그리고 DB에 접근하여 도메인 객체를 저장하고 관리하는 리포지토리 가 있다.

먼저 도메인에서 회원(Member)에 대한 클래스를 작성하고
리포지토리에서 Member 객체를 저장하고 불러들이는 클래스(MemberRepository) 를 작성한다.

이때 아직 DB가 선정되지 않았기 때문에
먼저 MemberRepsoitory는 인터페이스로 설계하여 이후 DB가 선정되었을 시 만들어 질 리포지토리가 어떤 메서드를 가져야할 지 미리 가이드를 주고, 임시로 DB 역할을 하는 메모리만을 사용하는 MemoryMemberRepository를 MemberRepsoitory를 implements해서 구현한다.

서비스를 구현하는 MemberService 클래스에서는 회원 조회라던가, 가입 시 Repository가 필요하므로, MemberRepository 클래스에 의존하게 된다.

결론은 컨트롤러에서 요청을 받으면 그에 맞는 서비스를 실행시켜주고 반환,

서비스는 리포지토리와 연결되어 핵심 비즈니스 기능을 수행하고 실행 결과 및 수행 중 필요한 정보는 리포지토리를 활용하여 요청 및 저장

리포지토리는 DB와 연결되는 메서드들을 작성하여 사용가능

그리고 이와 같은 모든 기능에 사용될 기본 단위? 요소가 되는 도메인 객체가 있다. 이렇게 정리할 수 있었다.


나중에 Service 구현 시에 Repository를 임포트하고 객체를 만들 때

private final MemberRepository memberRepository = new MemoryMemberRepository();

다음과 같이 인터페이스를 타입으로 MemoryRepository를 선언하고 할당은 MemoryMemberRepository 객체로 받을 수 있다. 만약 이후 DB가 정해져서 예를 들어 MybatisMemberRepository 클래스를 작성하였다면 저 부분만 바꿔주면 되는 것이 아닌가.

파이썬만 하다가 자바를 와서 인터페이스를 보고 잉 뭐지 했는데 인터페이스를 활용하는 이유를 별다른 부연설명 없어도 쉽게 이해할 수 있었다.

이 부분은 짧게 5분만에 설명하셨지만 굉장히 중요한 부분이라고 생각하였습니다. 주어진 비즈니스 데이터에서 계층 구조를 분리하여 어떻게 구현할 것이고 어떻게 접근할지? 무엇이 필요한지? 등 큰 그림을 그리는 것을 먼저 보여주면서 웹 개발 시 어떻게 접근해야할지 큰 도움이 되었습니다.

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

여기서부터는 앞서 생각한 구조를 순서대로 구현하는 부분이라 길게 작성하지 않도록 하겠습니다. 다만 들으면서 인상 깊었던 단축키 사용법 등 개발 스킬과 자바만의 특징에 대해서 작성하려고 합니다.

2-1 회원 도메인

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

앞서 주문받은 회원 아이디와 이름을 가진 Member 객체, id와 name이 private 접근자로 직접 접근할 수 없으므로 getter setter를 구현해준다.

id, name만 선언해주고
Alt+Insert 단축키로 게터 세터를 타이핑 없이 구현 가능.

long 대신 Long을 쓴 것이 의아했는데, null 값을 사용할 수 있는 wrapper class라고 한다.

프리미티브 타입은 기본값이 0인데 그럼 실제로 id 값이 0인건지, 값이 없는건지 사실 구분하기 어렵습니다. id가 0일 수도 있는거니까요. 그런데 Wrapper 타입인 Long이나 Integer를 쓰면 id가 없는 경우엔 확실히 null이고, 그 자체로 id가 없다는걸 보장할 수 있죠. - 구글링

2-2 리포지토리

다음으로 DB와 상호작용할 리포지토리를 구현, 먼저 인터페이스를 작성한다. 인터페이스엔 어떤 메서드가 필요한지 이름만 작성하고 이후 상속받을 클래스에서 내용을 채우면 된다.

회원을 저장할 save 메서드와, 회원 정보를 찾을 find 관련 메서드들 id와 name으로 찾기, 그리고 모든 회원 정보를 반환하는 findAll 메서드를 작성한다.


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? Long 클래스처럼 래퍼 클래스인 Optional은 NPE (NullPointerException) 예외를 쉽게 처리하기 위해서 사용한다. get 메서드로 내부 값에 접근할 수 있고, isPresent 메서드로 객체가 감싼 값이 null인지 아닌지 boolean으로 반환 가능.

다음으로 MemoryMemberRepository 클래스를 MemberRepository 상속 받아 작성한다.

Alt+Enter 를 활용하여 인터페이스에 작성된 메서드들을 코드 작성 없이 바로 오버라이딩 틀을 만들 수 있다. 단축키에 얼마나 익숙하냐에 따라 생산성에 큰 영향을 준다.
특히 Alt+Enter는 오류에 대한 자동완성 가이드를 주는 단축키로 굉장히 도움이 된다. 또한 코드 작성 중 줄을 마무리를 다하였으면 Ctrl+Alt+Enter로 바로 맨 뒤에 세미콜론을 찍고 넘어갈 수 있다.

Alt+Enter!!

너무 편하다;


MemoryMemberRepository의 메서드들을 작성한다.

먼저 따로 데이터를 저장할 DB가 없으니 store라는 이름의 HashMap을 선언하여 id : name로 저장하게 한다.
멤버의 id를 순차적으로 정할 sequence라는 변수도 준비한다.

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

    public void clearStore() {
        store.clear();
    }

}

마지막 clearStore 메서드는 이 후 테스트를 위해서 추가되었다. 코드에 대한 설명을 따로 않겠다. 다만 findByName 메서드에서 value 값에서 이름을 찾기위해 stream().filter(...) 문법에 익숙하지 않아서 좀 찾아보았다.

이-글
자바 8에서부터 추가된 람다, 스트림 기능들의 활용이다. 위 링크가 굉장히 잘 정리되어 있어서 보기 편하다. 메서드 선언 등에선 겁나 스트릭트하다가 갑자기 이렇게 막 쓰니 좀 느낌이 이상...


다음으로 현재까지 작성한 Repository의 save, findByname, findByAll 등의 메서드를 테스트한다. 스프링 부트에선 자동으로 테스트 환경도 제공하여 매우 편하다. TDD에 대한 얘기도 잠깐 나오고 뭐.. 여기서부턴 다음에~


느낀 점

재밌네요. 강의가 너무 맘에 들어서

결국 다 질러버렸습니다.
다 듣고 내년 상반기 가즈아ㅏㅏㅏ

아마 어느정도 능력이 되면 제 디코봇을 알리는 홈페이지를 만드는 프로젝트를 해볼까 생각중입니다~

profile
개발을 재밌게 하고싶습니다.

0개의 댓글