본문 바로가기
Spring/스프링 MVC

[스프링 MVC 1편] 3 - (1) 웹 애플리케이션 요구사항

by Poorm 푸름 2023. 11. 5.

* 스프링 입문은 Window로 스프링 MVC 1편은 Mac으로 진행합니다  
 *  진도 : 섹션3 - (1)
 *          : 자바 클래스명,         : 코드,         : 단축키

 

1. 회원 관리 웹 애플리케이션 요구사항 _ [스프링 입문] 3 - (1), (3) 편 참고

 

회원 정보

  • 이름: username
  • 나이: age

 

기능 요구사항

  • 회원 저장
  • 회원 목록 조회

 

▶ 웹 애플리케이션 계층 구조

 

1. 도메인

 

더보기

웹을 설계할때 도메인을 먼저 정하는게 중요하다

 

Domain

  • 내가 개발하고자 하는 영역의 모델(객체)
  • 온라인 쇼핑몰을 예를 든다면 주문 (핵심 기능), 회원, 결제, 배송, 리뷰로 도메인을 나눌 수 있다
    이 도메인 중 또 하위 도메인으로 나눌 수 있다

Domain Model

  1.  Entity
    • 식별자를 가진다
      • 식별자 이외의 데이터가 변경이 되어도 그 객체가 다른 객체가 되는것이 아니다
        예) Member = member_id 식별자라면, Member에서 닉네임을 변경해도 Member 불변
  2.  Value
    • 식별자를 가지지 않고 값 그  자체
      • 데이터가 변경되면 아예 다른 객체가 된다
      • 따라서 value를 immutable로 구현하는게 좋다
      • 이를 위해 값을 생성자를 통해서만 받고 setter를 구현하지 않는다
      • 기존 객체를 변경하고 싶으면 아예 새로운 객체를 만든다

 

2. 레포지토리 

 

더보기
  • Entity에 의해 생성된 DB에 접근하는 메서드(ex) findAll()) 들을 사용하기 위한 인터페이스
    위에서 엔티티를 선언함으로써 데이터베이스 구조를 만들었다면 여기에 어떤 값을 넣거나, 넣어진 값을 조회하는 등의 CRUD(Create, Read, Update, Delete)를 정의
    • DB에 접근하는 객체(MyBatis 사용시에 DAO or Mapper 사용)
    • Repository라고도 부름(JPA 사용시 Repository 사용)
    • Service와 Controller를 연결
  • DAO(Data Access Object)

 

사용하는 코드 함수 예시

  • Optional
    • null이 올 수 있는 값을 감싸는 Wrapper 클래스
    • 참조하더라도 NPE가 발생하지 않도록 한다
    • Value에 값을 저장하기 때문에 값이 null이더라도 바로 NPE가 발생 X
    • NPE(NullPointerException)이란?
      • 개발을 할 때 가장 많이 발생하는 예외
      • NPE를 피하려면 null 여부를 검사해야 하는데, null 검사를 해야하는 변수가 많은 경우 코드가 복잡해지고 번거롭다 그래서 null 대신 초기값을 사용하길 권장하기도 한다
  • @Repository
    • @Component가 포함된 annotation이다
    • 싱글톤 클래스 빈을 생성하는 어노테이션이다
    • 해당 어노테이션은 "이 클래스를 정의했으니 빈(자바 객체(POJO))으로 등록해줘." 라는 뜻
  •  db에 저장을 하지 않고 메모리에 저장 (Map 사용)
    • 실무에선 동시성 문제 때문에 공유되는 변수일때는 ConcurrnetHashMap을 사용
  • public Member save(Member member)
    • 파라미터로 member 객체를 받아서 그 객체에 id를 sequence를 1증가시키고 넣어주기
    • store라는 Hashmap을 생성해서 실제로 메모리에 저장하는데 id와 객체를 각각넣는다
      • new = 생성, put = 추가, 삭제 = remove(key) / clear()
    • 멤버 객체를 반환
  • public Optional<Member> findByName(String name)
    • stream 이란?
      • 데이터의 흐름
      • 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있다
      • 람다를 이용해서 코드의 양을 줄이고 간결하게 표현 (배열과 컬렉션을 함수형으로 처리 가능)
      • 병렬처리 가능 (하나의 작업을 둘 이상의 작업으로 잘게 나눠서 동시에 진행 = 스레드 이용)
    • Lambda 란?
      • 람다 함수는 함수형 프로그래밍 언어에서 사용되는 개념으로 익명 함수
      • 람다식의 표현
        • ( 파라미터 ) -> { 몸체 }
    • filter 란?
      • filter()는 인자로 함수를 받으며, 어떤 조건으로 Stream의 요소들을 필터링
  • public List<Member> findAll()
    • 모든 멤버를 반환

 

 3. 컨트롤러

 

더보기
  • 브라우저상의 웹 클라이언트의 요청 및 응답을 처리하는 레이어
  • 서비스계층, 데이터 엑세스 계층에서 발생하는 Exception을 처리
  • @Controller annotation을 사용하여 작성된 Controller 클래스
  • Controller 클래스안에 @RequestBody를 사용해서 REST API로도 만들질 수 있다

 

사용하는 코드 함수 예시

  • @Controller
    • @Component가 포함된 annotation
    • 싱글톤 클래스 빈을 생성하는 어노테이션
    • 즉, 패키지 스캔 안에 이 어노테이션은 "이 클래스를 정의했으니 빈(자바 객체(POJO))으로 등록해줘." 라는 뜻
  • private final MemberService memberService;
    • 멤버 서비스 객체를 final로 선언.
  • @Autowired
    • Controller 이랑 Service를 연결 (Controller가 Service를 의존하는 관계(DI))

 

 4. 서비스

 

더보기
  • 로직 처리와 도메인 모델의 적합성 검증
  • 프레젠테이션 계층과 데이터 엑세스 계층 사이를 연결하는 역할
    두 계층이 직접적으로 통신하지 않게 한다
  • Service 구현 클래스
  • Controller 가 Service를 통해 회원가입과 데이터를 가져올 수 있게 된다 (컨트롤러가 서비스를 의존하는 관계)
  • 트렌젝션 관리
    • 예) (1) 판매처에 돈보내기, (2) 판매처에서 돈 받기 이 있다고 하면, (1)은 성공했지만 (2)가 실패를 하게되면 작업의 실행하기 전 상태로 돌리(rollback)는것을 하는것이 트렌젝션
    • 트랜잭션 ACID
      • Atomicity; 원자성: 트랜잭션 내의 작업들은 모두 성공 또는 모두 실패한다.
      • Consistency; 일관성: 모든 트랜잭션은 일관성 있는 DB 상태를 유지한다. (ex: DB의 무결성 제약 조건 항상 만족)
      • Isolation; 격리성: 동시에 실행되는 트랜잭션들은 서로 영향을 미치지 않는다. (ex: 동시에 같은 데이터 수정 X)
      • Durability; 지속성: 트랜잭션이 성공적으로 끝나면 그 결과는 항상 기록되어야 한다.

 

 

2. Domain 모델 만들기
[ src - java - hello - servlet - domain - member - Member ]

@Getter @Setter
public class Member {
    private  Long id;
    private String username;
    private int age;

    public Member() {
    }

    public Member(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

 

  • Getter, Setter 롬복 생성
  • 필드 : id, username, age 추가
  • 생성자 : 기본 생성자, username, age 갖는 생성자

 

3. Repository 저장소 만들기 
[ src - java - hello - servlet - domain - member - MemberRepository ]

private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L; 
private static final MemberRepository instance = new MemberRepository();
public static MemberRepository getInstance() {
       return instance;
}
private MemberRepository() {
}

 

  • 실무에서 고려 사향 : 동시성 문제 (ConcurrentHashMap, AtomicLong 사용 고려)
  • Map<key,value> → key = id, value = member 로 저장
  • id 증가를 의미하는 sequence 생성
  • 싱글톤 만들기
  • 싱글톤 패턴은 객체를 단 하나만 생생해서 공유해야 하므로 생성자를 private 접근자로 막아둔다

 

public Member save(Member member) {
       member.setId(++sequence);
       store.put(member.getId(), member);
       return member;
}
    
public Member findById(Long id) {
       return store.get(id);
}      
 
 public List<Member> findAll() {
        return new ArrayList<>(store.values());
 }
      
      public void clearStore() {
          store.clear();
      }
}

 

  • save 만들기
    • member.setId(++sequence)
      • set 할 때 마다 sequence 값 하나씩 올려주기
        (name은 모두 넘어온 상태 name은 내가 적는 값, id는 시스템이 정해주는 값으로서 id는 이 부분에서 설정)
    • store.put(member.getId(), member)
      • member 객체(Map)에 id 넣어주고 member 다시 store에 저장
  • findById 만들기
    • id로 회원 찾기
  • findAll
    • List
      • option + enter_java.util
    • return new ArrayList<>(store.values())
      • 스토어에 있는 모든 값 ( = Member ) List에 넣기
      • 이렇게 하면 arraylist 조작해도 store는 영향 받지 않는다
  •  store.clear
    • test에 쓸 메서드
    • store 날리기
 

 

 4. 회원 저장소 테스트 코드
[ src - test - java - hello - servlet - domain - member - MemberRepositoryTest ]

 
class MemberRepositoryTest { //test는 public 생략가능

    //싱글톤이라 new 안써준다
    MemberRepository memberRepository = MemberRepository.getInstance();

    @AfterEach
    void afterEach(){
        memberRepository.clearStore();
    }

 

  • 테스트할 대상 불러오기
  • @AfterEach : 테스트 메서드 실행 이후에 수행
  • 테스트 순서를 올바르게 하기 위해서 test가 끝날 때마다 초기화 하는 메서드 사용

 

    @Test // 저장 테스트
    void save(){
        //given
        Member member = new Member("hello", 20);

        //when
        Member savedMember = memberRepository.save(member);

        //then
        Member findMember = memberRepository.findById(savedMember.getId());
        assertThat(findMember).isEqualTo(savedMember);
    }

 

  • 멤버 만들기
  • 리포지토리에 member 저장
  • 저장했던 member에서 id 꺼내 찾기
  • Assertions
    • option + enter 로 assertj 설정
  • 저장된 값과 찾아온 값이 같은지 확인

 

    @Test // 모든거 조회 테스트

    void findAll(){
        //given
        Member member1 = new Member("member1", 20);
        Member member2 = new Member("member2", 30);

        memberRepository.save(member1);
        memberRepository.save(member2);

        //when
        List<Member> result = memberRepository.findAll();


        //then
        assertThat(result.size()).isEqualTo(2); //option+enter
        assertThat(result).contains(member1,member2);
    }
}

 

  • 리포지토리에 member 1 , 2 저장
  • memberRepository.findAll()
    • option + command + v
    • 검증단계 
  • Assertions
    • option + enter 로 Add ~ static import 설정
  • 사이즈 2개인지 확인
    result 안에 멤버 데이터 있는지도 확인