[인프런 워밍업 클럽 0기 백엔드] 2주차 발자국 (내용 정리 + 과제 + 미니 프로젝트)

[인프런 워밍업 클럽 0기 백엔드] 2주차 발자국 (내용 정리 + 과제 + 미니 프로젝트)

인프런 워밍업 스터디의 2주차가 마무리되어 가고 있는 한 주입니다.

2주차 동안 학습했던 내용들과 과제 수행 관련된 내용까지 한 번에 정리하고자 합니다.

추가로 2주차 동안 학습한 내용, 과제 수행 부분은 개인 블로그에 항상 정리하고 있습니다.

참고하실 분들은 참고해 주시면 좋을 것 같습니다 :)

제 블로그 주소 링크 남겨드리겠습니다.(1~10일차 학습 및 과제 수행 내용)

https://twojun-space.tistory.com/category/%ED%9A%8C%EA%B3%A0/InFlearn%20Warming-up%200%EA%B8%B0%20BE


추가적으로 과제가 마무리 된 후 미니 프로젝트가 시작됩니다.

미니 프로젝트는 제 GitHub에 소스 코드, 기능 설명을 남겨두고자 합니다.

하단은 제 GitHub 주소입니다.

https://github.com/twojun/inflearn_warmingup_be_project


 

 

 

1. [6일 차] - Spring Bean & Spring Container, Spring Container를 다루는 방법, Spring Bean 주입 받기, 스프링 빈의 우선 순위 설정

1-1. Spring Bean?

(1) 스프링 빈에 대한 정의, 스프링 빈이 사용되는 이유

  • 스프링 컨테이너와 연관지어 설명, 스프링 빈의 라이프 사이클 의존성 확인 및 주입을 담당하는 스프링 컨테이너 내부에 스프링 빈이 존재한다.

 

1-2. Spring Container

(1) 스프링 컨테이너가 사용되는 이유

(2) 스프링 컨테이너의 역할

(3) 스프링 컨테이너를 사용할 때와 사용하지 않을 때의 Controller-Service-Repository, 사용 전 후로 달라지는 점

1-3. 의존성 주입(Dependency Injection), 제어의 역전(Inversion of Control, IoC)

  • 스프링 프레임워크의 핵심 기능인 의존성 주입, 제어의 역전이 무엇인지 코드를 통해 직접 알아보며 스프링 프레임워크가 DI, IoC를 제공해 줌으로써 가져다 주는 이점에 대해 정리

     

  • 생성자 주입, Setter 주입 등 스프링 컨테이너가 제공해주는 다양한 기능 정리

 

 

1-4. @Controller, @Service, @Repository를 계층형 아키텍처에 직접 적용

(1) 관련 객체들이 스프링 빈으로 등록되어 컨테이너 내부에서 서로 간의 의존성, 라이프 사이클이 관리됨을 이해한다.

 

 

1-5. 스프링 컨테이너를 다루는 방법, @Configuration, @Bean, @Component, 스프링 빈을 주입받는 방법

  • @Configuration, @Bean이 직접적으로 사용되는 시기에 대해 학습

  • @Component의 효과

  • 스프링 빈 주입 시 Constructor injection 사용(불변 타입의 객체와 함께 사용)

  • Constructor injection 사용 시 테스트 용이

 

 

1-6. @Qualifier, @Primary

(1) 동일한 타입의 빈에 대해서 어떠한 빈을 주입 대상으로 설정할지 우선권을 부여하는 어노테이션

  • 직접 타겟을 텍스트로 지정하는 @Qualifier@Primary보다 우선권이 높다.

 

 

1-7. 6일 차 개인 회고

(1) 스프링 빈, 스프링 컨테이너, DI(Dependency Injection), IoC(Inversion of Control) 등 스프링을 이루고 있는 주요 핵심 기능에 대해 학습할 수 있었다.

 

(2) 스프링을 이루고 있는 핵심 기술인만큼 확실하게 이에 대해 인지하고 있어야 할 필요성을 느끼게 되었다.

 

 

 




2. [6일 차] - 과제 수행 : 계층 구조인 Controller - Service - Repository로 분리하기

(1) 과제 수행 코드 : https://github.com/twojun/InFlearn_WarmingUp_Club_BE_0

 

(2) 6일차 과제수행 리뷰 : (개인 블로그)

 

2-1. 문제 1번 : Fruit 서비스를 각각의 계층구조로 분리하기

(1) 문제 접근 및 해결 방법

  • 기존에 스파게티처럼 얽혀 있던 코드를 각각의 역할에 맞게 수행할 수 있도록 코드를 분리했다.

  • 사용자의 요청을 받아 응답을 반환하는 Controller, 비즈니스 로직을 처리하는 Service, 데이터베이스와의 통신을 담당하는 Repository로 분리했다.

  • 기존 코드도 그렇고, 분리 과정에서 각 계층마다 역할이 뚜렷했기 때문에 분리하는 데 큰 어려움은 없었던 것 같다.

 

 

2-2. 정상 동작 확인

(1) 기존 코드를 계층적으로만 분리해서 조금 더 유지보수성을 높였기 때문에 이전과 동일하게 API 반환, 데이터베이스에 데이터가 적재되는 등 정상적으로 동작하는 부분을 확인했다.

 

 

2-3. 문제 2번 : FruitRepository 코드를 FruitMysqlRepository, FruitMemoryRepository로 구분하고 리포지토리를 변경해가며 코드 실행하기

(1) 문제 접근 및 해결 방법

  • 우선 동일한 타입을 갖는 객체에 대해 FruitMysqlRepository, FruitMemoryRepository 두 개 중, FruitMysqlRepository 클래스에 @Primary 어노테이션을 적용하여 코드를 실행해보니 정상적으로 코드가 동작하는 것을 확인할 수 있었다. 기능은 동일하고 어떤 빈을 주입받을지만 선택하면 되는 부분이었기에 큰 어려움 없이 해결할 수 있는 과제였다.

 

 

2-4. 6일차 과제 개인 회고

(1) 기존의 코드를 Controller - Service - Repository로 분리하며 각 계층이 가져야 하는 역할, 책임에 대해 생각해 볼 수 있었다.

 

(2) 조금 더 좋은 코드를 위해 지켜야 되는 부분은 무엇인지, 공부해야 되는 부분은 무엇인지 찾아볼 수 있었다.

 

 

 

 

 


3. [7일 차] - 지금까지 코드를 작성하며 Repository에 대해 개선할 점, JPA 도입, 추상화 레벨을 높인 Spring Data JPA

 

3-1. Repository 영역에서 지금까지 작성된 코드의 아쉬운 점

(1) 문자열 기반으로 직접 쿼리를 작성하고 있다.

(2) 이 부분에 대한 심각한 문제 : 컴파일 타임이 아닌 런타임 시점에 오류가 발생한 것을 인지하게 된다.

(3) 특정 DB에 대한 종속적 쿼리 발생

(4) 기본적인 CRUD에 대한 반복 작업 등등...

 

 

3-2. 객체와 DB 테이블의 패러다임 불일치 문제

(1) 객체와 데이터베이스 테이블은 서로 간의 패러다임이 완전히 불일치한다.(대표적으로 연관 관계 문제, 상속구조를 표현할 수 없는 문제, 서로 참조할 수 있는 매커니즘이 아예 다른 문제 등)

 

(2) 대부분의 애플리케이션이 객체지향적 설계를 지향한다. 객체와 관계형 데이터베이스를 같이 사용하기 위해서는 이 부분을 어떻게 해결할까? -> JPA 도입

 

 

 

3-2. JPA(Java Persistence API)

(1) JPA의 정의, ORM 기술이 어떠한 기술인지 학습

(2) JPA의 구현체인 하이버네이트의 등장

 

 

3-4. 객체와 테이블을 매핑하기 위한 여러 가지 어노테이션, 기타 옵션

(1) @Id, @GeneratedValue

(2) @Column 등 ...

(3) JPA에 대한 여러 가지 설정 및 옵션 설정 : application.yml

(4) spring.jpa.hibernate.ddl-auto 옵션 : create, create-drop, validate, update, none 존재

(5) spring.jpa.properties.hibernate.show_sql

(6) spring.jpa.properties.hibernate.format_sql

(3) spring.jpa.properties.hibernate.dialect

 

 

 

3-5. Spring Data JPA를 통한 CRUD 작업 공통화, Repository 작성

(1) Spring Data JPA의 대한 개념, 정의 학습

(2) Spring Data JPA는 높은 추상화 레벨을 가진 라이브러리로 스프링에서 데이터베이스에 대한 접근을 단순화하고 효율적으로 처리하며 반복적인 CRUD 작업 또한 추상화하여 개발자가 직접 코드를 작성하지 않고 간단한 CRUD 기능을 수행할 수 있도록 설계되어 있다.

(3) 회원 도메인에 직접 Spring Data JPA 적용해 보기

 

(4) 인터페이스를 사용하는 데 구현체를 생성하지 않았음에도 불구하고 해당 인터페이스를 사용할 수 있는 이유 학습

(5) 스프링 데이터 JPA 쿼리 메서드 작성 방법, 여러 가지 옵션 확인

 

 

 

3-6. 7일 차 학습 내용 개인 회고

(1) Spring Data JPA가 갖는 편리함 덕분에 개발 생산성이 높아졌지만 내부적으로 동작하는 매커니즘을 알기 위해선 JPA에 대한 기초적인 이해가 선행되어야 한다고 생각했다.

 

(2) JPA를 이루는 기초적인 개념을 다시 한 번 정리할 수 있었고 헷갈렸던 부분에 대해 제대로 학습해 볼 수 있었다.

 

 

 

 


4. [7일 차] : 과제 수행 - Spring Data JPA 적용

  1. (1) 과제 수행 GitHub 주소 : https://github.com/twojun/InFlearn_WarmingUp_Club_BE_0

     

     


    (2) 7일 차 과제 정리 블로그 주소 : https://twojun-space.tistory.com/190

 

 

4-1. 과제 수행 : 문제 1번

(1) 기존 Fruit 서비스가 Spring Data JPA Repository를 사용할 수 있도록 FruitRepository 인터페이스를 생성한다. 

(2) 이후 서비스 계층에서 새롭게 생성된 FruitRepository (Interface)를 의존할 수 있도록 설정한다.

(3) 접근법은 매우 간단하다. 인터페이스를 생성하고 서비스 계층에서 해당 인터페이스를 의존할 수 있도록 의존 관계를 변경해 주기만 하면 된다.

 

 

 

4-2. 과제 수행 : 문제 2번

(1) 문제 요구사항 : 특정 과일을 기준으로 판매된(거쳐간) 과일의 개수를 출력하는 API 설계

 

(2) 접근 방법 및 문제 해결 과정

  • 서비스 계층에 로직이 모두 몰리는 것을 막기 위해 과일 상태 변경에 관한 구현 로직을 도메인 계층에서 구현한다.

  • 판매 전 과일 판매 상태는 false, 판매 이후 true로 변경한다.

// 판매 상태 변경
public void changeSalesStatus(boolean salesStatus) {
    this.salesStatus = salesStatus;
}
  • 또한 Setter를 사용하는 것은 좋지 않다. 의미 있는 비즈니스 메서드(changeSalesStatus) 등을 생성하는 것이 좋다.

@Repository
public interface FruitRepository extends JpaRepository<Fruit, Long> {

    // select * from fruit where name = ? and salesStatus = true;
    long countByNameAndSalesStatusIsTrue(String name);
}
  • Repository 영역에 쿼리 메서드를 통해 판매된 과일의 개수를 계산하는 코드를 작성한다.

(3) 기능 테스트

  • 본인은 매번 데이터 로우를 넣는 부분이 번거로워서 별도의 InitDbService를 만들어서 서버가 재시동될 때마다 해당 데이터들을 추가하도록 설정했다.

@Component
@Transactional
@RequiredArgsConstructor
static class InitDbService {

    private final EntityManager em;
    public void initFruitDb() {
        Fruit fruit1 = createFruit("사과", 4000L, LocalDate.of(2024, 02, 01));
        Fruit fruit2 = createFruit("바나나", 2000L, LocalDate.of(2024, 02, 01));
        Fruit fruit3 = createFruit("사과", 6500L, LocalDate.of(2024, 02, 01));
        Fruit fruit4 = createFruit("사과", 7000L, LocalDate.of(2024, 02, 01));
        Fruit fruit5 = createFruit("사과", 3000L, LocalDate.of(2024, 02, 01));
        Fruit fruit6 = createFruit("포도", 12000L, LocalDate.of(2024, 02, 01));
        Fruit fruit7 = createFruit("사과", 2500, LocalDate.of(2024, 02, 01));
        Fruit fruit8 = createFruit("사과", 5000L, LocalDate.of(2024, 02, 01));

        em.persist(fruit1);
        em.persist(fruit2);
        em.persist(fruit3);
        em.persist(fruit4);
        em.persist(fruit5);
        em.persist(fruit6);
        em.persist(fruit7);
        em.persist(fruit8);

    }

    private static Fruit createFruit(String name, long price, LocalDate warehousingDate) {
        return new Fruit(name, price, warehousingDate);
    }
}
  • 테스트 진행 시 판매된 과일의 개수가 정상적으로 출력됨을 확인했다.

4-3. 과제 수행 : 문제 3번

(1) 문제 요구사항

  • 아직 판매되지 않은 특정 금액 이상, 금액 이하의 과일 리스트를 출력하는 API 설계

 

(2) 문제 접근 및 해결 방법 / 테스트

  • Controller에 판매되지 않은 특정 금액, 이상, 이하 과일들, 결과 반환 개수까지 반환하는 메서드를 만든다.

  • 응답 DTO에 판매되지 않은 과일 개수를 나타내기 위해 별도의 resultSize 필드를 추가한다.

이후에 과일의 판매 상태 컬럼을 활용해 판매되지 않은 과일의 리스트를 반환하는 로직을 각각 짜고 결과를 테스트 해 봤을 때 정상적으로 API 반환값이 확인된다.

4-4. 과제 개인 회고

(1) 높은 추상화 덕분에 우리의 개발 생산성이 올라갔지만, 그 뒤에는 우리가 편리한 기술을 사용하기 위해서 뒷받침되는 기술들이 많다는 것을 다시 한 번 느껴볼 수 있었다.

(2) 지금 작성한 코드들도 문제없이 작동하지만 코드를 더 좋은 방향으로 짤 수 있지 않을까라는 생각이 계속 들었다.


5. [8일 차] - 트랜잭션의 정의, 커밋과 롤백(Commit & Rollback), JPA의 Persistence Context, Persistence Context의 여러 가지 기능 살펴보기

(1) 관련 내용 정리 (개인 블로그) : https://twojun-space.tistory.com/191

 

 

5-1. 트랜잭션의 정의와 트랜잭션의 커밋과 롤백

(1) 예시와 함께 트랜잭션의 정의, 성공적인 트랜잭션 후 데이터베이스와의 동기화를 진행하는 커밋 옵션, 트랜잭션 실패 시 작업 이전으로 되돌아가는 롤백 옵션에 대해 확인해 보았다.

5-2. 실제 코드에 선언적 트랜잭션(Declarative transaction) 적용하기

(1) 코드에 @Transaction 어노테이션(선언적 트랜잭션 방법)을 적용하여 서비스 계층에서 메서드가 실행될 때 트랜잭션이 적용될 수 있도록 하고 작업 성공 시 커밋, 실패 시(예외 발생 시) 롤백하게 된다.

5-3. JPA의 Persistence Context, Persistence Context의 여러 가지 기능

(1) Persistence context(영속성 컨텍스트)의 정의에 대해 학습했다.

(2) 첫 번째 기능 : 변경 감지

  • 트랜잭션 커밋 시점에 엔티티에 대한 변경사항을 감지하여 이에 대한 쿼리가 실제 데이터베이스로 전송되는 것을 말함

(3) 두 번째 기능 : 쓰기 지연 저장소

  • 엔티티를 관리하며 발생한 관련 쿼리들을 쓰기 지연 저장소에 두고 트랜잭션 커밋 시점에 모든 쿼리를 데이터베이스로 전송한다.

(4) 세 번째 기능 : 1차 캐시 기능 지원

  • 엔티티의 Identifier를 기준으로 영속성 컨텍스트에 영속화된 엔티티는 1차 캐시에 스냅샷(영속화 당시 엔티티의 정보)이 저장되어 있고 추후 해당 엔티티를 조회하는 경우라면 데이터베이스까지 조회 쿼리를 날리지 않고 1차 캐시에서 조회함에 따라 성능상 조금의 이점이 생긴다.

5-4. 8일차 학습 회고

(1) JPA의 기능들을 무심하게 사용해 오다가 JPA의 내부 매커니즘을 이해할 수 있는 영속성 컨텍스트에 대해 다시 한 번 리마인드 할 수 있었다. 관련 기반 기술의 이해가 선행되어야 해당 기술을 문제없이 잘 사용할 수 있다고 생각하기 때문에 이 부분에 대해 다시 한 번 정리해볼 생각이다.



6. [9일 차]- 추가 기능에 대한 API 개발

학습 내용 관련 블로그 정리 : https://twojun-space.tistory.com/192

6-1. 도서 등록, 대출, 반납 요구사항 추가에 대한 API 개발, 개인 회고

(1) 이전과 동일하게 요구사항을 확인하고 요청/응답 DTO의 스펙, Controller, Service, Repository를 개발했다.

(2) 이를 통해 회원 관리, 도서 등록과 대출 반납에 대한 기능 구현이 모두 완료되었지만 한 가지 의문점이 남는다. 서비스 계층을 중심으로 모든 로직이 돌아가고 절차지향적인 코드라는 느낌이 많이 든다. 각 생성된 엔티티 간의 협력을 통해 개발할 수 없을까? 라는 의문점이 들고 JPA에서 제공하는 연관관계 기능을 사용해서 엔티티의 역할과 협력을 추가적으로 사용해서 조금 더 객체지향적으로 설계해 보는 것이 좋다고 생각했다.


7. [10일 차] - JPA 연관관계와 지연 로딩(Lazy Loading), 객체지향적 설계, 도서 대출/반납 기능 Refactoring

(1) 관련 학습 내용 블로그 정리 : https://twojun-space.tistory.com/193

7-1. 연관관계란 무엇인가?

(1) 연관관계의 정의에 대해 알아봤다.이터베이스 테이블과 객체 간 서로를 참조하는 방식에 대한 패러다임이 불일치하기 때문에 JPA의 연관관계를 통해 객체와 테이블을 정확하게 매핑하는 것이 중요하다.

7-2. 연관관계의 주인

(1) 일대일의 경우 핵심 비즈니스 로직이 존재하는 엔티티를 연관관계의 주인으로 둔다.

(2) 다대일의 경우 외래 키가 존재하는 엔티티를 연관관계의 주인으로 설정한다.

(3) 연관 관계의 주인으로 설정된 엔티티는 객체가 서로 연결될 수 있는 기준이 된다.

(4) 일대일 관계에서는 @OneToOne 어노테이션을 사용한다.

(5) 다대일 관계에서는 @ManyToOne, @OneToMany를 사용한다.

(6) 양방향 연관관계를 가진다면, 한 쪽 엔티티에 대한 변경이 다른 쪽 엔티티에도 반영될 수 있도록 도와주는 메서드인 연관관계 편의 메서드를 만든다.

(7) 양방향 연관관계를 사용하게 된다면, 신중하게 선택해야 한다.

7-3. 실무에서 다대다 연관관계는 사용하지 않는다.

  • 따라서 중간에 별도의 테이블을 생성하여 처리하는 방법으로 매핑한다.

7-4. @ManyToOne은 단방향으로만 사용한다,

  • 1쪽에서 @OneToMany로 참조하는 부분을 없애고 @ManyToOne 단방향으로만 남겨놓는다.

 

 

7-5. 연관관계의 주인이 사용할 수 있는 어노테이션

  • @JoinColumn을 통해 연관관계의 주인이라는 부분을 명시한다.

 

 

7-6. 영속성 전이(Cascade)옵션과 orphanRemoval

(1) cascade 옵션의 경우 연관관계와 관련은 없다.

(2) 부모 엔티티가 자식 엔티티를 관리해야 할 때 사용한다.

(3) orphanRemoval은 고아 객체를 제거할 때 사용하는 옵션으로 부모 엔티티와 자식 엔티티 간 연관관계가 제거된 경우 해당하는 자식 엔티티를 삭제하게 된다.

 

 

7-7. 도서 대출/반납 기능 리팩토링

(1) 지금까지 학습한 내용을 바탕으로 각 엔티티가 협력할 수 있도록 모든 코드를 수정한다.

(2) 엔티티에 도서 대출, 도서 반납 등의 핵심 로직을 설계한다.

(3) 이를 통해 도메인 간 협업이 가능하도록 설계하여 서비스 계층의 로직이 단순해지고 특정 기능이 필요한 경우 엔티티의 기능을 직접 호출하도록 설계한 것을 확인할 수 있었다.

7-8. 9일차에서 소개되지 않은 영속성 컨텍스트의 기능 : 지연 로딩(Lazy Loading)

(1) 지연 로딩은 두 엔티티를 대상으로 특정 엔티티를 조회함에 있어서 연관된 엔티티는 바로 가져오지 않고, 해당 연관된 엔티티를 필요로 할 때(사용될 때) 로딩하는 기법을 의미한다.

(2) 지연 로딩의 핵심 매커니즘은 프록시 기술과 연관되어 있다.

7-9. 연관관계를 사용해서 도메인 중심으로 설계 시 얻는 장점?

(1) 각자의 역할에 집중하게 된다 :

  • 계층별로 응집성이 강해진다. 도메인끼리의 협업 관계를 통해 서비스 계층에서는 외부 트랜잭션 관리, 의존성 관리 등의 역할만 맡게됨으로써 각자의 역할에 더 집중하는 코드가 완성된다.

(2) 협업 시 코드 리딩이 쉬워진다.

  • 서비스 계층에 너무 많은 로직이 몰려있다 보면 코드를 리딩하는 입장에서 번거로울 수 있다. 하지만 도메인 중심으로 코드를 설계하면 계층이 어느 정도 분리됨을 가져가면서 도메인 계층이 각각 어떤 역할을 수행하는지 파악하기 수월해진다.

(3) 테스트 코드 작성 시 용이하다.

(4) 결론

  • 원론적인 이야기이지만 항상 연관관계를 사용하는 것이 정답은 아니기에 요구사항, 도메인 아키텍처 등 여러 가지 사항을 충분히 고민해서 연관관계 사용을 선택해야 한다.

7-10. 학습 내용 개인 회고

(1) 개인적으로는 이번 섹션이 가장 중요한 내용인 것 같다.

(2) 영속성 컨텍스트(JPA 내부 동작 방식 이해)를 이해하는 것과 객체와 테이블을 매핑시킬 때 가장 중요한 연관관계에 대한 내용이라 더욱 그렇게 느꼈던 것 같다.

(3) 연관관계를 조금 더 정확히 이해하고 항상 주의깊게 코드를 설계하도록 노력해야 할 것 같다.


8. 2주 차 회고

(1) 벌써 스터디에 참여한지 2주차가 마무리되는 시점이다.

(2) 스터디 완주까지 최선을 다하고 싶고, 기회가 된다면 우수 러너도 도전해 보고 싶다. 많은 것을 경험하고 얻어갈 수 있는 시간이 되었으면 좋겠다.

(3) 0기 스터디원분들 모두 화이팅입니다 :)

댓글을 작성해보세요.

채널톡 아이콘