반응형

1. 비즈니스 요구사항 정리

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

일반적인 웹 어플리케이션 계층 구조

- Controller : Web MVC의 Controller 역할 (API 생성 등)
- Service : 핵심 비즈니스 로직 구현 (중복 가입 불가 등 핵심 기능) <- 비즈니스 도메인 객체를 가지고 로직 구현
- Repository : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
- Domain : 비즈니스 도메인 객체. 예시) 회원, 주문, 쿠폰 등등 주로 DB에 저장하고 관리

 

클래스 의존관계

- 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계합니다.
   (저장소를 변경하려면 인터페이스가 필요합니다.)
- 데이터 저장소는 RDB, NoSQL 등 다양한 저장소를 고민 중인 상황으로 가정합니다.
- 개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소를 사용합니다.

 

Domain, Repository

2.1 domain 패키지 생성
2.2 Member.class 생성

package hellospring.hello.domain;

public class Member {
    private Long id; 
    // 시스템이 저장하는 임의의 값 입니다. 고객이 정하는게 아님!
    
    private String name;
    // 단순하게 만들기 때문에 Getter Setter를 사용합니다.

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


2.3 repository 패키지 생성
2.4 MemberRepository.interface 생성

package hellospring.hello.repository;

import hellospring.hello.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 : java8에서 추가 됨.
    // null을 그대로 반환하지 않고 Optional로 감싸서 반환하는 방법
}


2.5 MemoryMemberRepository 생성
 - Option + Enter 메서드 구현을 클릭하시고 메서드를 만드시면 됩니다.

메서드 구현

implements : interface를 상속할 땐 implements를 사용합니다. 여러 개의 interface를 상속받을 수 있으며, 자식 클래스는 부모의 기능을 다시 정의(오버라이딩)해서 사용해야 한다.

-> 사실, "이러한 구현은 부모의 메서드를 어차피 재정의해야 하니 '상속'의 의미가 없지 않나?"라고 할 수 있지만, Java와 c#의 인터페이스 상속은 계약 및 분류의 의미가 강하다고 합니다.

 

메서드 구현
구현된 메서드

간단하게 눌러주었는데 메서드가 구현되었습니다.
이제 메서드에 기능을 추가할 것입니다.

save 기능을 사용하려면 어딘가에 저장을 해야 합니다.

// MemoryMemberRepository.java
// 어딘가 저장을 해야되기 때문에 만든다. 

...

 private static Map<Long, Member> store = new HashMap<>(); 
 // id가  long 타입이라서 long
 
 private static Long sequence = 0L; 
 // sequence는 0,1,2 등 키 값을 생성해주는 역할입니다 id 값이라고 생각하면 편합니다.
 
 ...
여기서 Map이란?
java에서는 자료구조를 Map이라고 이름 지었습니다.
Map에 저장되는 데이터는 ‘key-value’ pair라는 형식을 갖고 있으며 key와 value는 매칭 됩니다.
이는 ‘주민등록번호 - 사람 이름’ 관계와 비슷하다고 보면 됩니다.
주민등록번호는 한 명도 똑같은 사람이 없다. -> key값은 중복되지 않는다.
주민등록번호는 있는데 사람 이름이 없는 경우는 없다. -> key없는 value는 없음
이름이 있는 사람이 주민등록번호가 없는 경우는 없다. -> value없는 key는 없음
주민등록번호가 달라도 사람이름은 똑같을 수 있다.(동명이인) -> value는 중복 가능
참조 : https://onsil-thegreenhouse.github.io/programming/java/2018/02/22/java_tutorial_1-24/
 

[Java] 자바의 자료구조 - Map(HashMap, TreeMap) - Onsil's blog

초짜 개발자 온실의<br> 스터디 블로그

onsil-thegreenhouse.github.io

실무에서는 동시성 문제가 있을 수 있어서 공유되는 변수일 때는 ConcurrentHashMap 을 사용해야 하나 예제라서 단순하게 사용
마찬가지로 sequence의 타입도 long보다는 동시성 문제를 고려해서 AtomicLong 을 사용해줘야 합니다.

 

기능 구현

이제 기능 구현을 하겠습니다.

<save>

// MemoryMemberRepository.java
// 회원 저장 기능
...
@Override
    public Member save(Member member) {
        member.setId(++sequence); // id값이 할당되고 점점 증가한다.
        store.put(member.getId(), member);
        return member; // member에 id와 name이 저장된다.
    } 
...​


<findByID>

// MemoryMemberRepository.java
// 회원 아이디 찾기 기능
...
@Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
        // null 값이 올수도 있으므로 ofNullable로 해준다.
    }
...


<findByName>

// MemoryMemberRepository.java
// 회원 이름으로 찾기 기능

...

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

...

// 람다식으로 사용되었으며 필터기능을 줘서 member.getName 값이 파라미터로 넘어온 name값이랑 같은 지 확인
// name이 같은 경우에는 필터링이 된다.
// findAny()는 결과가 Optional로 반환된다. Map에서 루프를 다 돌면서 하나 찾으면 찾은 값을 반환
// 끝까지 돌렸는데 없으면 Optional에 null이 포함되서 반영된다.


<findAll>

// MemoryMemberRepository.java
// 회원 전체 검색하기
...

@Override
    public List<Member> findAll() {
        // 자바에서 실무에는 List를 많이 사용한다. < 루프 돌리기가 편함.
        return new ArrayList<>(store.values());
        // store.values = member
    }

...


기능 구현은 이걸로 완성되었다. 다음에는 테스트코드로 기능이 제대로 동작하는지 확인!

// MemoryMemberRepository.java 전체 코드

package hellospring.hello.repository;
import hellospring.hello.domain.Member;
import java.util.*;

public class MemoryMemberRepository implements MemberRepository {

    private static Map<Long, Member> store = new HashMap<>();
    // 실무에서는 동시성 문제가 있을 수 있어서 공유되는 변수일 때는 ConcurrentHashMap 을 사용해야 하나 예제라서 단순하게 사용
    private static Long sequence = 0L;
    // sequence는 0,1,2 등 키 값을 생성해준다. 동시성 문제를 고려해서 AtomicLong 을 사용해줘야 합니다.
    @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));
        // null 값이 올수도 있으므로 ofNullable로 해준다.
    }

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

    @Override
    public List<Member> findAll() {
        // 자바에서 실무에는 List를 많이 사용한다. < 루프 돌리기가 편함.
        return new ArrayList<>(store.values());
        // store.values = member
    }
}

 

반응형
복사했습니다!