[인프런 워밍업 클럽 BE 2기] 백엔드 프로젝트 - 2주차 발자국
1주차에는 간단한 엔티티와 프로젝트 구조 작성을 완료하고, 이번 주에는 리포지토리, DTO, 서비스 계층을 전반적으로 개발했으며, 그 과정에서 테스트 코드도 함께 작성했다. 이를 통해 프로젝트의 핵심 기능들을 구조화하고, 안정성을 높이는 데 중점을 두었다.
데이터를 다루는 리포지토리 개발하기
리포지토리 개발을 하였으며 역시 1주차와 달리 혼동스러운 어노테이션 및 개념과 처음 접하는 어노테이션 및 개념들이 나왔었다. 하나하나 상세히 어떤 역할을 하는지 강의에서 알려주셔서 따라가는데 문제 없었으나, 중간에 오류가 한번 발생하여 찾아보니 JPA영속성 관련 부분이였다.
Projectskill에 데이터가 들어가지 않아 확인을 해보니 project 엔티티에서 skill 부분에 cascade 부분을 빼먹어서 함께 저장이 되지 않았다.
1. Spring Component와 Bean 등록
Component Bean 등록:
스프링에서는
@Component
,@Controller
,@Service
,@Repository
어노테이션을 통해 클래스를 Bean으로 등록@Component
: 기본적인 스프링 컴포넌트 등록 어노테이션.@Controller
: 웹 컨트롤러 계층에 사용, 요청을 처리하는 클래스.@Service
: 서비스 계층에 사용, 비즈니스 로직을 처리하는 클래스.@Repository
: 데이터 접근 계층에 사용, 데이터베이스와 상호작용하는 클래스.이들은 모두
@Component
를 상속한 어노테이션으로, 역할에 따라 더 구체적으로 분류
@ComponentScan
:스프링이 애플리케이션을 시작할 때, 이 어노테이션을 사용하여 해당 패키지나 하위 패키지에서 컴포넌트를 스캔하고, DI(의존성 주입)를 위해 Bean으로 등록
2. 스프링 프로파일과 @Profile
어노테이션
@Profile
:스프링에서 다양한 환경 설정을 관리할 때, 특정 프로파일이 활성화된 경우에만 빈(Bean)을 활성화할 수 있도록 하는 어노테이션
예를 들어,
@Profile("dev")
는 "dev" 프로파일이 활성화될 때만 해당 빈이 등록
3. 생성자 주입과 Bean 관리
생성자 주입 방식으로 의존성을 주입받아 사용하는 것이 권장. 생성자 내부에서 미리 정의된 Bean을 주입받아 사용하고, 이 과정에서 스프링이 의존성 관리를 담당.
@PostConstruct
:Bean이 스프링 컨테이너에 등록된 후 실행되는 메서드에 붙일 수 있는 어노테이션. Bean의 초기화를 마친 후 로직을 처리할 때 사용.
4. 로그 사용 권장
System.out.println()
은 성능상 좋지 않으며, 멀티 쓰레드 환경에서 비효율적이기 때문에 로그 라이브러리를 사용하여 로그를 남기는 것이 좋다.
5. JPA와 영속성 관리 (Cascade)
Cascade: JPA에서 엔티티의 상태 변화에 따라 연관된 엔티티도 함께 변경되도록 하는 옵션.
CascadeType.PERSIST: 엔티티를 저장할 때 연관된 엔티티도 함께 저장.
CascadeType.MERGE: 엔티티를 병합할 때 연관된 엔티티도 함께 병합.
CascadeType.REMOVE: 엔티티를 삭제할 때 연관된 엔티티도 함께 삭제.
CascadeType.REFRESH: 엔티티를 새로고침할 때 연관된 엔티티도 함께 새로고침.
CascadeType.DETACH: 엔티티가 영속성 컨텍스트에서 분리될 때 연관된 엔티티도 분리.
CascadeType.ALL: 모든 작업(PERSIST, MERGE, REMOVE, REFRESH, DETACH)에 대해 적용.
예를 들어,
Project
클래스에서ProjectSkill
엔티티와의 연관관계에CascadeType.PERSIST
를 설정하지 않으면,Project
가 저장될 때 연관된ProjectSkill
이 함께 저장되지 않을 수 있다.
6. JPA 성능 개선 (findById 오버라이딩)
JpaRepository
의findById
메서드를 오버라이드하여 성능을 최적화할 수 있습니다. 예를 들어, 특정 조건에 맞는 쿼리를 커스터마이징하여 데이터베이스 성능을 개선할 수 있습니다.
이러한 요소들은 스프링 애플리케이션 개발에서 빈 관리, 의존성 주입, JPA를 통한 데이터베이스 영속성 관리를 포함한 다양한 개발 과정에서 중요한 역할을 합니다.
리포지토리 테스트 하고 성능 개선하기
개발한 리포지토리가 제대로 작성 되는지 테스트코드를 작성하였으며, 또한 리포지토리의 성능을 개선하기 위해 JPQL을 따로 작성하여 fetch 전략으로 LAZY를 사용하여도 깔끔하게 출력되게 개선하였다.
리포지토리 테스트 코드 작성
@TestInstance.Lifecycle.PER_CLASS
: 메서드 간 독립적인 실행이 가능하며, 메서드 간 의존성이 제거됨.의존성 주입: 테스트 시 필요한 리포지토리나 서비스 등을 주입받아 테스트를 진행.
테스트 데이터 초기화:
@BeforeAll
을 사용하여 테스트에 필요한 데이터를 사전 생성.
프록시 객체와 Fetch 전략
프록시 객체: JPA에서 연관된 엔티티를 가짜 객체로 생성하여 필요할 때만 데이터베이스에서 불러옴.
LAZY 로딩: 필요할 때마다 쿼리를 날려 데이터를 가져옴. 하지만, 반복문을 돌 때마다
SELECT
문이 나가 성능 저하가 발생할 수 있음.EAGER 로딩: 한 번에 모든 연관된 엔티티를 조회하지만, 불필요한 데이터를 미리 가져올 경우 성능에 영향을 미칠 수 있음.
3. Fetch Join과 N+1 문제 해결
Fetch Join: JPQL에서 사용하여 연관된 엔티티를 한 번의 쿼리로 모두 조회하는 방법.
장점: 추가적인 쿼리가 발생하지 않아 성능 최적화가 가능.
단점: 필요 없는 데이터를 미리 가져올 경우 메모리 낭비 가능.
N+1 문제: Lazy 로딩 시 발생하는 성능 문제로, 하나의 엔티티를 조회할 때 연관된 엔티티를 각각 추가 쿼리로 조회하여 비효율이 발생. Fetch Join으로 이 문제를 해결 가능.
4.Assertions의
assertThat
assertThat
: 테스트 코드에서 검증을 위한 메서드로, 다양한 조건에 맞는 검증을 수행할 수 있음.isEqualTo(): 두 값이 같은지 비교.
isTrue() / isFalse(): 조건이 참인지, 거짓인지 확인.
hasSize(): 리스트나 배열의 크기를 검증.
contains(): 리스트가 특정 값을 포함하는지 확인.
데이터를 조회하고 변환하는 서비스 개발하기
도메인 패키지와 연결된 presentation 패키지를 생성하였고, 해당 패키지 안에 클래스, DTO, 리포지토리, 서비스 개발을 완료하였다. 또한, 서비스에 대한 테스트 코드도 작성했다.
이번에는 도메인에서 개발했던 리포지토리 테스트와 달리 단위 테스트가 아닌 방법으로 개발하였다. 기존까지 나는 개발 환경에서 페이지를 띄운 후 하나하나 수동으로 테스트를 진행했지만, 이러한 방식은 운영 서버에서 테스트하기 어렵다는 단점이 있었다.
따라서 테스트 코드를 작성하여 자동화된 방식으로 테스트하는 것이 실무적으로 더 안정적이라고 한다.
1. @RestController
로 REST API 구현
@RestController
: 스프링에서 RESTful 웹 서비스를 만들 때 사용하는 어노테이션으로, 컨트롤러에서 반환하는 데이터를 자동으로 JSON 형식으로 변환해 클라이언트에 전달한다. 주로 API 응답에서 사용되며, HTML이 아닌 데이터를 반환한다.
2. @GetMapping
과 @RequestMapping
@GetMapping
: HTTP GET 요청을 처리하는 어노테이션으로,@RequestMapping(method = RequestMethod.GET)
의 간편한 대체 방식이다. 이를 통해 서버는 클라이언트의 GET 요청에 대해 데이터를 조회하고 응답할 수 있다.
3. DTO (Data Transfer Object)
DTO: 데이터 전송 객체로, 서버와 클라이언트 간 데이터를 주고받기 위한 객체다. 비즈니스 로직을 포함하지 않고 데이터의 전송에만 집중되어 있으며, 데이터를 안전하고 일관되게 전달하는 데 사용된다.
4. Mockito와 @InjectMocks
로 의존성 주입
Mockito: 단위 테스트에서 실제 객체 대신 Mock 객체를 사용하여 독립적인 테스트를 가능하게 해주는 테스트 라이브러리이다. 이를 통해 외부 의존성을 최소화한 테스트를 할 수 있다.
@ExtendWith(MockitoExtension::class)
: JUnit5와 Mockito를 통합해, 테스트 환경에서 Mock 객체의 자동 주입을 제공하는 어노테이션이다. 테스트 클래스에서 의존성 주입을 자동으로 처리하며, 의존성을 가진 객체들을 쉽게 Mocking할 수 있다.@InjectMocks
: Mock 객체를 주입받아 의존성을 가진 객체를 독립적으로 테스트할 수 있도록 하는 어노테이션이다. Mock 객체를 통해 실제 객체 없이도 테스트할 수 있어, 테스트의 독립성과 효율성을 높여준다.
미션 3
REST API 설계하기
이번 미션에서는 설계한 테이블을 기반으로 REST API를 설계하는 작업을 진행했다. 상품 API, 분류 API, 브랜드 API, 재고 API를 설계했으며, 각 API의 기능을 세부적으로 구현하였다.
분류와 브랜드 API에서는 삭제 대신 useYn 필드를 변경하여 비활성화하는 API를 설계했다. 즉, 데이터를 완전히 삭제하지 않고 비활성화 상태로 변경하는 방식이다.
재고 API에서는 재고 삭제 API를 설계하지 않았고, 재고를 수정할 때 기존 데이터를 수정하는 대신 새로운 데이터를 생성하여 수정 이력을 로그처럼 남길 수 있도록 설계했다.
또한, HTTP 메서드 중 PUT과 PATCH 메서드의 차이점에 대해 고민했는데, 두 메서드의 차이는 다음과 같다:
PUT: 전체 리소스를 대체하거나 새로 생성하는 데 사용된다.
PATCH: 리소스의 일부만 수정하는 데 사용되며, 부분적인 변경에 적합하다.
https://github.com/Malvin222/mission-backoffice
댓글을 작성해보세요.