반응형
  1. 스프링 배치 심화학습
  2. 멀티 스레드로 여러 개의 Step 실행하기

1. 스프링 배치 심화학습

  1. 다양한 ItemReader 구현 클래스
  2. 다양한 ItemWriter 구현 클래스
  3. JobParameter 사용하기
  4. 테스트 시에만 H2 DB를 사용하도록 설정하기
  5. 청크 지향 프로세싱
  6. 배치의 인터셉터 Listener 설정하기
  7. 어노테이션 기반으로 Listener 설정하기
  8.  Step의 흐름을 제어하는 Flow

1.1 다양한 ItemReader 구현 클래스

리스트 타입으로 Reader를 구현한 ListItemReader 객체가 있습니다.

전에 기본편에서 만들었던 QueueItemReader 객체와 동일한 역할을 수행합니다.

ListItemReader 객체를 사용하면 모든 데이터를 한번에 가져와 메모리에 올려놓고 read() 메서드로 하나씩 배치 처리 작업을 수행할 수 있습니다.

그런데 수백, 수천을 넘어 수십만 개 이상의 데이터를 한번에 가져와 메모리에 올려놓아야 할 때는 어떻게 해야 할까요?

이때는 배치 프로젝트에서 제공하는 PagingItemReader 구현체를 사용할 수 있습니다.

구현체는 크게 JdbcPagingItemReader, JpaPagingItemReader, HibernatePagingItemReader가 있습니다. 

JpaPagingItemReader를 사용해보겠습니다.

JpaPagingItemReader에는 지정한 데이터 크기만큼 DB에서 읽어오는 setPageSize() 메서드 기능이 있습니다.

데이터를 지정한 단위로 가져와 배치 처리를 수행할 수 있습니다.

1. @Bean(destroyMethod="") -> 스프링에서 destroyMethod를 사용해 삭제할 빈을 자동으로 추적합니다.

destoryMethod=""와 같이 하여 기능을 사용하지 않도록 설정하면 실행 시 출력되는 warning 메시지를 삭제할 수 있습니다.

2. jpaPagingItemReader.setQueryString() -> jpaPagingItemReader를 사용하려면 쿼리를 직접 짜서 실행하는 방법밖에 없습니다. 

3. jpaPagingItemReader.setParameterValues(map) -> updatedDate, status 파라미터를 Map에 추가해 사용할 파라미터를 설정합니다.

 

JpaPagingItemReader 주의사항

inacticeJobStep()에서 설정한 청크 단위(커밋 단위)가 5라고 가정하면 Item5개를 writer까지 배치 처리를 진행하고 저장한다고 해봅시다. 저장된 데이터를 바탕으로 다음에 다시 지정한 크기로 새 인덱스를 할당해 읽어 와야 하는데 이전에 진행한 5라는 인덱스값을 그대로 사용해 데이터를 불러오도록 로직이 짜여 있어서 문제가 됩니다.

예를 들어 청크 단위로 Item 5개를 커밋하고 다음 청크 단위로 넘어가야 하는 경우를 가정하겠습니다.

하지만 entityManager에서 앞서 처리된 Item 5개 때문에 새로 불러올 Item의 인덱스 시작점이 5로 설정되어 있게 됩니다. 그러면 쿼리 요청 시 offset 5(인덱스값), limit 5(지정한 크기 단위)이므로 개념상 바로 다음 청크 단위(Item 5개)인 Item을 건너뛰는 상황이 발생합니다.

이러한 상황에서 가장 간단한 해결 방법은 조회용 인덱스값을 항상 0으로 반환하는 겁니다.

0으로 반환하면 Item 5개를 수정하고 다음 5개를 건너뛰지 않고 원하는 순서/청크 단위로 처리가 가능해집니다.

 

1.2 다양한 ItemWriter 구현 클래스

JpaItemWriter는 별도로 저장 설정을 할 필요 없이 제네릭에서 저장할 타입을 명시하고 EntityManagerFactory만 설정하면 Processor에서 넘어온 데이터를 청크 단위로 저장합니다.

1.3 JobParameter 사용하기

테스트 코드에 JobParameter를 생성해 JobLauncher에 전달하게끔 수정합니다.

1. new Date(); -> Date 타입은 JobParameter에서 허용하는 파라미터 중 하나입니다.

2. jobLauncherTestUtils.launchJob(new JobParametersBuilder().addDate("nowDate", nowDate).toJobParameters()) ->

JobParametersBuilder를 사용하면 간편하게 JobParameters를 생성할 수 있습니다. JobParameters는 여러 JobParameter를 받는 객체입니다. JobLauncher를 사용하려면 JobParameters가 필요합니다.

 

1.4 테스트 시에만 H2 DB를 사용하도록 설정하기

@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2) 어노테이션을 사용하면 테스트 시에는 H2를 할당하게 처리할 수 있습니다.

1.5 청크 지향 프로세싱

청크 지향 프로세싱은 트랜잭션 경계 내에서 청크 단위로 데이터를 읽고 생성하는 프로그래밍 기법입니다. 

청크란 아이템이 트랜잭션에서 커밋되는 수를 말합니다.

read한 데이터 수가 지정한 청크 단위와 일치하면 wirte를 수행하고 트랜잭션을 커밋합니다.

청크 지향 프로세싱의 이점은 1000여 개의 데이터에 대해 배치 로직을 실행한다고 가정합시다. 청크로 나누지 않았을 때는 하나만 실패해도 다른 성공한 999개의 데이터가 롤백됩니다. 그런데 청크 단위를 10으로 해서 배치 처리를 하면 도중에 배치 처리에 실패하더라도 다른 청크는 영향을 받지 않습니다.

 

1.6 배치의 인터셉터 Listener 설정하기

배치 흐름에서 전후 처리를 하는 Listener를 설정할 수 있습니다.

구체적으로 Job의 전후 처리, Step의 전후 처리, 각 청크 단위에서의 전후 처리 등 세세한 과정 실행 시 특정 로직을 할당해 제어할 수 있습니다.

스프링 배치에서는 Job의 Listener로 JobExecutionListener 인터페이스를 제공합니다.

1. @Slf4j -> 필드에 로그 객체를 따로 생성할 필요 없이 로그 객체를 사용할 수 있도록 설정하는 롬복 어노테이션 

 

- Job 설정에 Listener 등록하기

InactiveUserJobConfig.java

1.7 어노테이션 기반으로 Listener 설정하기

InacticeStepListener.java

- Step 설정에 Listener 등록하기

 

1.8 Step의 흐름을 제어하는 Flow

스프링 배치는 세부적인 조건에 대한 흐름을 제어하는 Flow를 제공합니다.

흐름에서 조건에 해당하는 부분을 JobExecutionDecider 인터페이스를 사용해 구현할 수 있습니다.

JobExecutionDecider 인터페이스는 decide() 메서드 하나만 제공합니다.

1. start(new InactiveJobExecutionDecider()) -> start()로 설정해 맨 처음 시작하도록 지정합니다.

2. on(FlowExecutionStatus.FAILED.getName()).end() -> failed가 반환되면 end()를 사용해 곧바로 끝나도록 설정합니다.

3. on(FlowExecutionStatus.COMPLETED.getName()).to(inactiveJobStep) -> completed가 반환되면 기존에 설정한 inactiveJobStep을 실행하도록 설정합니다.

4. start(inactiveJobFlow) -> inactiveUserJob 시작 시 Flow를 거쳐 Step을 실행하도록 inactiveJobFlow를 start()에 설정합니다.

 

2. 멀티 스레드로 여러 개의 Step 실행하기

1. TaskExecutor를 사용해 여러 Step 동작시키기

 

2.1 TaskExecutor를 사용해 여러 Step 동작시키기

TaskExcutor 인터페이스는 멀티 스레드로 Step을 실행하는 가장 기본적인 방법입니다.

Task는 Runnable 인터페이스를 구현해 각각의 스레드가 독립적으로 실행되도록 작업을 할당하는 객체입니다.

 

 

 

 

 

728x90
반응형
반응형
  1. 배경지식
  2. 스프링 부트 배치 이해하기
  3. 스프링 부트 휴면회원 배치 설계하기
  4. 스프링 부트 배치 설정하기
  5. 스프링 부트 휴먼회원 배치 구현하기

1. 배경지식

배치 처리에 스프링 부트 배치를 써야 하는 이유

  • 대용량 데이터 처리에 최적화되어 고성능을 발휘합니다.
  • 효과적인 로깅, 통계 처리, 트랜잭션 관리 등 재사용 가능한 필수 기능을 지원합니다.
  • 수동으로 처리하지 않도록 자동화되어 있습니다.
  • 예외사항과 비정상 동작에 대한 방어 기능이 있습니다.
  • 스프링 부트 배치의 반복되는 작업 프로세스를 이해하면 비즈니스 로직에 집중할 수 있습니다.

스프링 부트 배치 주의사항

1. 가능하면 단순화해서 복잡한 구조와 로직을 피해야 합니다.

2. 데이터를 직접 사용하는 작업이 빈번하게 일어나므로 데이터 무결성을 유지하는 유효성 검사 등의 방어책이 있어야 합니다.

3. 배치 처리 시 시스템 I/O 사용을 최소화해야 합니다. 잦은 I/O로 데이터베이스 커넥션과 네트워크 비용이 커지면 성능에 영향을 줄 수 있기 때문입니다. 따라서 가능하면 한번에 데이터를 조회하여 메모리에 저장해두고 처리를 한 다음, 그 결과를 한번에 데이터베이스에 저장하는 것이 좋습니다.

4. 일반적으로 같은 서비스에 사용되는 웹, API, 배치, 기타 프로젝트들은 서로 영향을 줍니다. 따라서 배치 처리가 진행되는 동안 다른 프로젝트 요소에 영향을 주는 경우가 없는지 주의를 기울여야 합니다.

5. 스프링 부트 배치는 스케줄러를 제공하지 않습니다. 배치 처리 기능만 제공하며 스케줄링 기능은 스프링에서 제공하는 쿼츠 프레임워크(Quartz Framework), IBM 티볼리(Tivoli) 스케줄러, BMC 컨트롤-M(Control-M) 등을 이용해야 합니다. 리눅스 crontab 명령은 가장 간단히 사용할 수 있지만 이는 추천하지 않습니다. crontab의 경우 각 서버마다 따로 스케줄링을 관리해야 하며 무엇보다 클러스터링 기능이 제공되지 않습니다. 반면에 쿼츠와 같으 ㄴ스케줄링 프레임워크를 사용한다면 클러스터링뿐만 아니라 다양한 스케줄링 기능, 실행 이력 관리 등 여러 이점을 얻을 수 있습니다.

 

2. 스프링 부트 배치 이해하기

1. 읽기(read) - 데이터 저장소(알반적으로 데이터베이스)에서 특징 데이터 레코드를 읽습니다.

2. 처리(processing) - 원하는 방식으로 데이터를 가공/처리합니다.

3. 쓰기(write) - 수정된 데이터를 다시 저장소 (데이터베이스)에 저장합니다.

Job과 Step은 1:M, Step과 ItemReader, ItemProcessor, ItemWriter는 1:1의 관계를 가집니다. 즉, Job이라는 하나의 큰 일감(Job)에 여러 단계(Step)를 두고, 각 단계를 배치으 기본 흐름대로 구현합니다.

 

Job

Job은 배치 처리 과정을 하나의 단위로 만들어 표현한 객체입니다. 또한 전체 배치 처리에 있어 항상 최상단 계층에 있습니다. 위에서 하나의 Job(일감) 안에는 여러 Step(단계)이 있다고 설명했던 바와 같이 스프링 배치에서 Job 객체는 여러 Step 인스턴스를 포함하는 컨테이너입니다.

Job 객체를 만드는 빌더는 여러 개 있습니다. 여러 빌더를 통합 처리하는 공정인 JobBuilderFactory로 원하는 Job을 손쉽게 만들 수 있습니다. JobBuilderFactory의 get() 메소드로 JobBuilder를 생성하고 이를 이용합니다.

 

Step

Step은 실질적인 배치 처리를 정의하고 제어하는 데 필요한 모든 정보가 들어 있는 도메인 객체입니다.

Job을 처리하는 실질적인 단위로 쓰입니다. 모든 Job에는 1개 이상의 Step이 있어야 합니다.

 

JobRepository

JobRepository는 배치 처리 정보를 담고 있는 매커니즘(어떠한 사물의 구조, 또는 그것이 작동하는 원리)입니다. 어떤 Job이 실행되었으며 몇 번 실행되었고 언제 끝났는지 등 배치 처리에 대한 메타데이터를 저장합니다. 예를 들어 Job 하나가 실행되면 JobRepository에서는 배치 실행에 관련된 정보를 담고 있는 도메인인 JobExecution을 생성합니다.

JobRepository는 Step의 실행 정보를 담고 있는 StepExcution도 저장소에 저장하며 전체 메타데이터를 저장/관리하는 역할을 수행합니다.

 

JobLauncher

JobLauncher는 Job, JobParameters와 함께 배치를 실행하는 인터페이스입니다. 인터페이스의 메서드는 run() 하나입니다.

 

ItemReader

ItemReader는 Step의 대상이 되는 배치 데이터를 읽어오는 인터페이스입니다. FILE, XML, DB 등 여러 타입의 데이터를 읽어올 수 있습니다.

 

ItemProcessor

ItemProcessor는 ItemReader로 읽어온 배치 데이터를 변환하는 역할을 수행합니다.

굳이 ItemWriter에 변환하는 로직을 넣을 수도 있는데 왜 ItemProcessor를 따로 제공할까요? 그 이유는 두 가지입니다.

첫 번째 이유는 비즈니스 로직을 분리하기 위해서입니다. ItemWriter는 저장만 수행하고, ItemProcessor는 로직 처리만 수행해 역할을 명확하게 분리합니다.

두 번째 이유는 읽어온 배치 데이터와 쓰여질 데이터의 타입이 다를 경우에 대응하기 위해서입니다.

 

ItemWriter

ItemWriter는 배치 데이터를 저장합니다. 일반적으로 DB나 파일에 저장합니다.

 

3. 스프링 부트 휴먼회원 배치 설계하기

전체 배치 프로세스

1. H2 DB에 저장된 데이터 중 1년간 업데이트되지 않은 사용자를 찾는 로직을 ItemReader로 구현합니다.

2. 대상 사용자 데이터의 상탯값을 휴먼회원으로 전환하는 프로세스를 ItemProcessor에 구현합니다.

3. 상탯값이 변한 휴먼회원을 실제로 DB에 저장하는 ItemWriter를 구현합니다.

 

4. 스프링 부트 배치 설정하기

스프링 부트 배치 스타터를 사용하면 배치 생성에 필요한 많은 설정을 자동으로 적용할 수 있습니다.

 

5. 스프링 부트 휴먼회원 배치 구현하기

1. 휴먼회원 배치 테스트 코드 생성

2. 휴먼회원 배치 정보 설정

3. SQL로 테스트 데이터 주입하기

 

5.1 휴먼회원 배치 테스트 코드 생성

JobLauncherTestUtils를 빈으로 등록해 테스트 설정 클래스를 작성합니다.

JobLauncherTestUtils는 배치의 Job을 실행해 테스트하는 유틸리티 클래스입니다.

 

1. @EnableBatchProcessing은 스프링 부트 배치 스타터에 미리 정의된 설정들을 실행시키는 마법의 어노테이션입니다.

배치에 필요한 JobBuilder, StepBuilder, JobRepository, JobLauncher 등 다양한 설정이 자동으로 주입됩니다.

2. Job 실행에 필요한 JobLauncher를 필드값으로 갖는 JobLauncherTestUtils를 빈으로 등록합니다.

 

휴먼 전환이 올바르게 되었는지 확인하는 테스트 코드를 먼저 작성합니다.

1. jobLauncherTestUtils.launchJob() -> launchJob() 메서드로 Job을 실행시켰습니다. launchJob() 메서드의 반환값으로 실행 결과에 대한 정보를 담고 있는 JobExecution이 반환됩니다.

2. jobExecution.getStatus() -> getStatus() 값이 COMPLETED로 출력되면 Job의 실행 여부 테스트는 성공입니다.

3. findByUpdatedDateBeforeAndStatusEquals -> 업데이트된 날짜가 1년 전이며 상태값이 ACTIVE인 사용자들이 없어야 휴먼회원 배치 테스트가 성공입니다.

 

- 휴먼회원 검색 쿼리 생성

5.2 휴먼회원 배치 정보 설정

1. 휴먼회원 Job 설정

2. 휴먼회원 Step 설정

3. 휴먼회원 Reader, Processor, Writer 설정

 

배치 정보는 @Configuration 어노테이션을 사용하는 설정 클래스에 빈으로 등록합니다. jobs 패키지를 새로 만들어 InactiveUserJobConfig 클래스를 생성합니다.

 

inacticeUserJobConfig.java

1. JobBuilderFactory jobBuilderFactory -> Job 생성을 직관적이고 편리하게 도와주는 빌더인 JobBuilderFactory를 주입했습니다. 빈에 주입할 객체를 파라미터로 명시하면 @Autowired 어노테이션을 쓰는 것과 같은 효과가 있습니다.

2. get("inactiveUserJob") -> inactiveUserJob 이라는 이름의 JobBuilder를 생성하며, preventRestart()는 Job의 재실행을 막습니다.

3. start(inactiveJobStep)은 파라미터에서 주입받은 휴먼회원 관련 Step인 inactiveJobStep을 제일 먼저 실행하도록 설정하는 부분입니다. inactiveJobStep은 앞선 inactiveUserJob과 같이 InactiveUserJobConfig 클래스에 빈으로 등록할 겁니다.

 

inacticeUserJobConfig.java

1. <User, User> chunk(10) -> 제네릭을 사용해 chunk()의 입력 타입과 출력 타입을 User 타입으로 설정했습니다. chunk의 인잣값은 10으로 설정했는데 쓰기 시에 청크 단위로 묶어서 writer() 메서드를 실행시킬 단위를 지정한 겁니다. 즉, 커밋의 단위가 10개입니다.

2. Step의 reader, processor, writer를 각각 설정했습니다.

 

inacticeUserReader()

inacticeUserJobConfig.java

1. @StepScope -> 기본 빈 생성은 싱글턴이지만 @StepScope를 사용하면 해당 메서드는 Step의 주기에 따라 새로운 빈을 생성합니다. 즉, 각 Step의 실행마다 새로 빈을 만들기 때문에 지연 생성이 가능합니다. 주의할 사항은 @StepScope는 기본 프록시 모드가 반환되는 클래스 타입을 참조하기 때문에 @StepScope를 사용하면 반드시 구현된 반환 타입을 명시해 반환해야 한다는 겁니다.

 

QueueItemReader

QueueItemReader.java

ItemReader의 기본 반환 타입은 단수형인데 그에 따라 구현하면 User 객체 1개씩 DB에 select 쿼리를 요청하므로 매우 비효율적인 방식이 될 수 있습니다.

1. QueueItemReader를 사용해 휴먼회원으로 지정될 타깃 데이터를 한번에 불러와 큐에 담아놓습니다.

2. read() 메서드를 사용할 때 큐의 poll() 메서드를 사용하여 큐에서 데이터를 하나씩 반환합니다.

 

inacticeUserProcessor()

데이터를 DB에서 읽어와 QueueItemReader에 저장하였으므로 이제 읽어온 타깃 데이터를 휴면회원으로 전환시키는 processor를 만들어보겠습니다.

inacticeUserJobConfig.java

inacticeUserWriter()

휴먼회원을 DB에 저장하는 inacticeUserWriter

inacticeUserJobConfig.java

BatchApplication.java

JobBuilderFactory, StepBuilderFactory를 자동으로 주입받기 위해서는 애플리케이션ㄴ 구동하는 BatchApplication 클래스에 @EnableBatchProcessing 어노테이션을 빈으로 등록하여야 한다.

@EnableBatchProcessing  - 배치 작업에 필요한 빈을 미리 등록하여 사용할 수 있도록 해준다.

BatchApplication.java

5.3 SQL로 테스트 데이터 주입하기

SQL 파일을 이용해 테스트 데이터를 생성하여 저장이 가능합니다.

/resources 하위 경로에 import.sql 파일을 생성해놓으면 스프링 부트가 실행될 때 자동으로 해당 파일의 쿼리를 실행합니다. 더 정확히 구분해 말하자면 import.sql은 하이버네이트가, data.sql은 스프링 JDBC가 실행합니다.

import.sql

import.sql

 

728x90
반응형
반응형

목차

1. 스프링 부트 데이터 레스트로 REST API 구현하기

2. @RepositoryRestController를 사용하여 REST API 구현하기

3. 프로젝션, 롤, 이벤트 등 세부적인 설정 처리

4. HAL 브라우저 적용하기


시작하기 전 yml 설정

  - DB는 AWS RDS를 사용하였습니다.

 - spring.data.rest.base-path : API의 모든 요청의 기본 경로를 지정합니다.

 - spring.data.rest.default-page-size : 클라이언트가 따로 페이지 크기를 요청하지 않았을 때 적용할 기본 페이지 크기를 설정합니다.

 - spring.data.rest.max-page-size : 최대 페이지 수를 설정합니다.

 

그 외 아래와 같은 프로퍼티 설정도 가능합니다.

이름  설명
page-param-name 페이지를 선택하는 쿼리 파라미터명을 변경합니다.
limit-param-name 페이지 아이템 수를 나타내는 쿼리 파리미터형을 변경합니다.
sort-param-name 페이지 정렬값을 나타내는 쿼리 파라미터형을 변경합니다.
default-media-type 미디어 타입을 지정하지 않았을 때 사용할 기본 미디어 타입을 설정합니다.
return-body-on-create 새로운 엔티티를 생성한 이후에 응답 바디(Response Body) 반환 여부를 설정합니다.
return-body-on-update 엔티티를 수정한 이후에 응답 바디 반환 여부를 설정합니다.
enable-enum-translation 'rest-messages'라는 프로퍼티 파일을 만들어서 지정한 enum 값을 사용하게 해줍니다. 적합한 enum 값(DEFAULT, ALL, VISIBILITY, ANNOTATED)을 키로 사용합니다.
detection-strategy 리포지토리 노출 전략을 설정하는 프로퍼티값입니다. RepositoryDetectionStrategy 인터페이스 내부에 구현된 enum 값으로 설정합니다.

 detection-strategy에 RepositoryDetectionStrategy에 주어진 enum 값은 아래와 같습니다.

  - ALL : 모든 유형의 리포지토리를 노출합니다.

  - DEFAULT : public으로 설정된 모든 리포지토리를 노출합니다.

  - ANNOTAION : @(Repository) RestResource가 설정된 리포지토리만 노출합니다. 여기서 @(Repository) RestResource가 'exported'로 설정된 flag 값이 false가 아니어야 합니다.

  - VISIBILITY : public으로 설정된 인터페이스만 노출합니다.

 

 1. 스프링 부트 데이터 레스트로 REST API 구현하기

 

BoardRepository.java
UserRepository.java

http://localhost:8081/api/boards 결과

 

호출해보니 HATEOAS를 지키며 MVC 패턴을 활용한 방법보다 더 많은 링크 정보를 제공하고 있습니다.

boards 키의 내부 _links는 해당 Board와 관련된 링크 정보를 포함하고 외부 _links는 Board의 페이징 처리와 관련된 링크 정보를 포함합니다. 링크를 살펴보면 관련된 데이터를 어떻게 호출해야 할지 힌트를 얻을 수 있습니다.

이러한 정보는 키값(key : value) 형식으로 구성되어 있어서 클라이언트가 키를 참조하도록 코드를 설정한다면 서버에서 요청된 데이터의 정보가 바뀌더라도 클라이언트 입장에서는 코드를 수정할 필요가 없습니다. 

 

2. @RepositoryRestController를 사용하여 REST API 구현하기

스프링 부트 데이터 레스트의 기본 설정을 사용해서 편리하게 정보를 구성했지만 모든 사람이 위와 같은 데이터형을 원하지는 않을 겁니다. 또느 좀 더 최적화하고 싶은 사람도 있을 겁니다. 이럴 때는 @RepositoryRestController 어노테이션을 사용하면 좋습니다. 이는 @RestController를 대체하는 요도로 사용하며 두가지 주의사항이 있습니다.

첫째, 매핑하는 URL 형식이 스프링 부트 데이터 레스트에서 정의하는 REST API 형식에 맞아야 합니다.

둘째, 기존에 기본으로 제공하는 URL 형식과 같게 제공해야 해당 컨트롤러의 메서드가 기존의 기본 API를 오버라이드합니다.

 

1. @GetMapping("/boards") - /api/boards로 스프링 부트 데이터 레스트에서 기본적으로 제공해주는 URL 형식을 오버라이드합니다.

2. PageMetadata - 전체 페이지 수, 현재 페이지 번호, 총 게시판 수등의 페이지 정보를 담는 PageMetadata 객체를 생성합니다.

3. PagedResources - 컬렉션의 페이지 리소스 정보를 추가적으로 제공해주는 PagedResources 객체를 만들어 반환값으로 사용합니다.

4. resources.add() - 필요한 링크를 추가합니다.

 

3. 프로젝션, 롤, 이벤트 등 세부적인 설정 처리

프로젝션으로 노출 필드 제한하기

특정 사용자 개인정보처럼 민감한 데이터를 다룰 때는 한정된 값만 사용하도록 제한을 걸어야 됩니다.

http://localhost:8081/api/users/1 결과

스프링 부트 데이터 레스트는 반환값을 제어하는 3가지 방법을 제공합니다.

1. 도메인 필드에 @JsonIngnore 추가

2. @Projection을 사용

3. 프로젝션을 수동으로 등록 - RepositoryRestConfigurerAdapter 클래스를 상속받아 configureRepositoryRestConfiguration 메소드를 오버라이드하여 프로젝션을 등록할 수 있습니다. 따로 예제로 살펴보지 않겠습니다.

 

1. 도메인 필드에 @JsonIngnore 추가

  - 가장 간단합니다. 

  -  User 도메인의 경우 다음과 같이 password 필드에 사용하면 적절합니다.

2. @Projection을 사용

  - 프로젝션을 설정하여 원하는 필드만 제한할 수 있습니다. 프로젝션은 사용자에게 제공되는 정보를 줄일 수도 있고 반대로 제공하지 않던 데이터를 가져올 수도 있습니다. 다음과 같은 사항에 유의해서 사용해야 합니다.

  • 프로젝션 인터페이스 생성 시 반드시 해당 도메인 클래스와 같은 패키지 경로 또는 하위 패키지 경로에 생성해야 합니다.
  • @RespositoryRestResource 어노테이션의 excerptProjection 프로퍼티로 관리되는 리소스 참조는 단일 참조 시 적용되지 않습니다.

 

도메인 하위 경로에 프로젝션 패키지를 생성하고 원하는 노출 필드가 지정된 인터페이스를 하나 생성합니다.

 User의 이름만 제공하는 인터페이스를 생성하겠습니다.

UserOnlyContainName.java

생성한 프로젝션을 UserRepository에 적용합니다. excerptProjection 파라미터값에 생성한 프로젝션 클래스를 넣어주면 됩니다.

 

UserRepository.java

http://localhost:8081/api/users 결과

하지만 http://localhost:8081/api/users/1은 어젼히 모든 정보가 노출됩니다.

위에서 언급한 @Projection 주의사항인 @RespositoryRestResource 어노테이션의 excerptProjection 프로퍼티로 관리되는 리소스 참조는 단일 참조 시 적용되지 않습니다.

위 경우에 대한 대책으로 http://localhost:8081/api/users/1?projection=getOnlyName 적용하면 됩니다. 또는 특정 경로에 따라 반환할 데이터가 명확한 방법인 @RepositoryRestController를 사용하는 것을 추천합니다.

각 메서드 권한 제한

가장 기본적인 방법은 @Secured 어노테이션입니다. @Secured는 순수하게 롤 기반으로 접근을 제한합니다. 대신 @Secured는 권한 지정에 있어서 유연성이 떨어집니다.

@PreAuthorize 어노테이션을 사용하면 @Secured보다 효율적으로 권한 지정을 할 수 있습니다. 이 방식은 권한 지정 외에도 EL 태그를 사용하여 유연한 관리가 가능합니다. @PreAauthorize는 메서드 레벨과 리포지토리 레벨 모두 사용할 수 있지만 두 가지를 혼합해 사용하는 방법은 추천하지 않습니다.

 

@PreAuthorize를 사용하여 ROLE_ADMIN 권한을 갖고 있어야한 Board를 저장할 수 있게끔 예제를 구현하겠습니다.

 

BoardOnlyContationTitle.java

JpaRepository 인터페이스에서 상속받는 메서드를 오버라이드하여 권한을 지정했습니다.

BoardRepository.java

이벤트 바인딩

스프링 부트 데이터 레스트에서는 여러 메소드의 이벤트 발생 시점을 가로채서 원하는 데이터를 추가하거나 검사하는 이벤트 어노테이션을 제공합니다.

BeforeCreateEvent 생성하기 전의 이벤트
AfterCreateEvent 생성한 후의 이벤트
BeforeSaveEvent 수정하기 전의 이벤트
AfterSaveEvent 수정한 후의 이벤트
BeforeDeleteEvent 삭제하기 전의 이벤트
AfterDeleteEvent 삭제한 후의 이벤트
BeforeLinkSaveEvent 관계를 가진(1:1, M:M) 링크를 수정하기 전의 이벤트
AfterLinkSaveEvent 관계를 가진(1:1, M:M) 링크를 수정한 후의 이벤트
BeforeLinkDeleteEvent 관계를 가진(1:1, M:M) 링크를 삭제하기 전의 이벤트
AfterLinkDeleteEvent 관계를 가진(1:1, M:M) 링크를 삭제한 후의 이벤트

게시글 생성 시 생성한 날짜와 수정한 날짜를 서버에서 설정하도록 하는 BoardEventHandler라는 클래스를 생성하겠습니다.

 

BoardEventHandler.java

선언한 이벤트를 적용하는 두 가지 방법을 제공합니다.

첫 번째는 수동으로 이벤트를 적용하는 ApplicationListener를 사용하는 방법 AbstractRepositoryEventListener를 상속받고 관련 메소드를 오버라이드하여 원하는 이벤트만 등록할 수 있습니다.

두 번째는 사용한 어노테이션을 기반으로 이벤트 처리하는 방법입니다. 생성한 이벤트 핸들러 클래스에는 @RepositoryEventHandler 어노테이션이 선언되어 있어야 합니다. 이 어노테이션은 BeanPostProcessor에 클래스가 검사될 필요가 있음을 알려줍니다. 그리고 이벤트 핸들러를 등록하려면 @Component를 사용하거나 직접 ApplicationContext에 빈으로 등록해야 합니다.

 

ApplicationContext 클래스에 직접 빈으로 등록하는 방법을 사용하겠습니다.

ApplicationContext.java

테스트 코드를 통해서 확인해보겠습니다.

BoardEventTest.java

- @AutoConfigureTestDatabase : H2가 build.gradle의 클래스 경로에 포함되어 있으면 자동으로 H2를 테스트 데이터베이스로 지정합니다. 만약 이 어노테이션을 사용하지 않는다면 테스트에서 Board를 저장할 때마다 실제 데이터베이스에 반영될 겁니다.

- TestRestTemplatedms RestTemplate을 래핑한 객체로서 GET, POST, PUT, DELETE와 같은 HttpRequest를 편하게 테스트하도록 도와줍니다.

 

HAL 브라우저 적용하기

build.gradle 아래 의존성을 추가합니다. 

compile('org.springframework.data:spring-data-rest-hal-browser')

서버를 구동하고 루트 경로로 들어가면 HAL 브라우저 UI 창으로 리다이렉트 됩니다.

 

 - Explorer : 검색할 URI를 지정합니다.

 - Custom Request Headers : 검색을 요청할 때 헤더를 설정할 수 있습니다.

 - Properties : 페이징 처리와 같은 부가적인 프로퍼티 정보를 표현합니다.

 - Links : 응답 데이터에서 제공하는 링크(_links)값의 데이터를 표현합니다.

 - Response Headers : 응답 헤더를 표현합니다.

 - Response Body : 응답 바디를 JSON형으로 표현합니다.

 

요청 정보와 설정한 정보 그리고 어떻게 응답이 왔는지 한눈에 확인할 수 있습니다. 요청마다 웹 서버를 구동시킬 필요도 별도로 REST 테스트 도구를 사용할 필요도 없습니다.

 

마치며

MVC 패턴은 일반적인 스프링 구성을 사용하여 개발합니다. 그러므로 디테일한 구성을 할 수 있습니다. 하지만 모든 API를 일일이 신경 써서 개발해야 하며 반복되는 작업을 피할 수 없습니다. 반면 스프링 부트 데이터 레스트에서 제공하는 방식은 반복되는 작업을 피할 수 있습니다. 불필요한 컨트롤러와 서비스 영역을 제거하여 코드를 단축시킬 수 있습니다. 하지만 상세한 구성에 제약이 있으며 어느 정도 정해진 틀에 맞춰 개발을 진행해야 합니다.

 

두 방식 모두 장단점이 명확히 존재하며, 상황에 맞게 선택하여 사용하는 것이 중요할 것 같습니다.

728x90
반응형

'Spring' 카테고리의 다른 글

스프링 부트 배치 - 심화  (0) 2021.05.23
스프링 부트 배치 - 기본  (2) 2021.05.19
스프링 부트 데이터 레스트  (0) 2021.05.01
스프링 부트 시큐리티 + OAuth2  (0) 2021.04.20
스프링 부트 웹  (0) 2021.04.11
반응형

목차

  1. 배경지식
  2. 설계하기
  3. 스프링 부트 MVC 패턴으로 REST API 만들기
  4. 스프링 부트 데이터 레스트로 REST API 만들기

1. 배경지식

- REST 소개

REST는 웹과 같은 분산 하이퍼미디어 시스템에서 사용하는 통신 네트워크 아키텍처로, 네트워크 아키텍처의 원리 모음입니다.

웹은 전송 방식으로 HTTP를, 식별 방법으로 URI를 사용합니다. HTTP는 웹에서 GET, POST, PUT, DELETE 등의 메서드를 사용하여 정보를 주고 받는 프로토콜입니다. REST는 HTTP와 URI의 단순하고 간결한 장점을 계승한 네트워크 아키텍처입니다. 따라사 다양한 요구사항에 대응하여 때로는 서버와 클라이언트가 서로 통신하는 리소스에 대해 복잡한 방식으로 상호작용할 수 있습니다. REST는 다음과 같은 목적으로 만들어졌습니다.

  • 구성요소 상호작용의 규모 확장성
  • 인터페이스의 범용성
  • 구성요소의 독립적인 배포
  • 중간적 구성요소를 이용한 응답 지연 감소, 보안 강화, 레거시 시스템 인캡슐레이션

REST API 추천 영상 - 그런 REST API로 괜찮은가

- RESTful 제약조건

REST의 구현 원칙을 제대로 지키면서 REST 아키텍처를 만드는 것을 RESTful이라고 합니다.

RESTful 제약조건

 

클라이언트 - 서버(client-server)

 - 이 제약 조건의 기본 원칙은 관심사의 명확한 분리입니다. 관심사의 명확한 분리가 선행되면 서버의 구성요소가 단순화되고 확장성이 향상되어 여러 플랫폼을 지원할 수 있습니다.

 

무상태성(stateless)

- 서버에 클라이언트의 상태 정보를 저장하지 않는 것을 말합니다. 단순히 들어오는 요청만 처리하여 구현을 단순화합니다. 단, 클라이언트의 모든 요청은 서버가 요청을 알아듣는 데 필요한 모든 정보를 담고 있어야 합니다..

 

캐시 가능(cacheable)

- 클라이언트의 응답을 캐시할 수 있어야 합니다. 앞에서 HTTP의 장점을 그대로 계승한 아키텍처가 REST라고 했습니다. 따라서 HTTP의 캐시 기능도 적용할 수 있습니다.

 

계층화 시스템(layered system)

- 서버는 중개 서버(게이트웨이, 프록시)나 로드 밸런싱, 공유 캐시 등의 기능을 사용하여 확장성 있는 시스템을 구성할 수 있습니다.

 

코드 온 디맨드(code on demand)

- 클라이언트는 서버에서 자바 애플릿, 자바스크립트 실행 코드를 전송받아 기능을 일시적으로 확장할 수 있습니다. 이 제약 조건은 선택 가능합니다.

 

인터페이스 일관성(uniform interface)

- URI(통합 자원 식별자)로 지정된 리소스에 균일하게 통일된 인터페이스를 제공합니다. 아키텍처를 단순하게 분리하여 독립적으로 만들 수 있습니다.

 

인터페이스 일관성은 세부 원칙을 갖고 있습니다. 인터페이스 일관성이 잘 지켜졌는지에 따라 REST를 제대로 사용했는지 판단할 수 있습니다. 인터페이스 일관성에는 다음 4가지 프로퍼티가 존재합니다.

 

1. 자원 식별(identification of resources)

2. 메시지를 통한 리소스 조작(manipulation of resources through representations)

3. 자기 서술적 메시지(self-descriptive messages)

4. 애플리케이션 상태에 대한 엔진으로서의 하이퍼미디어(HATEOAS - hypermedia as the engine of application state)

 

ps -인터페이스 일관성(uniform interface)에서 제약조건 3.4번이 지켜지기가 힘들다..

- REST API 설계하기

REST API는 다음과 같이 구성해야 합니다.

자원(resource) : URI

행위(verb) : HTTP 메서드

표현(representations) : 리소스에 대한 표현(HTTP Message Body)

 

URI 설계

- 명사를 사용해야 하며 동사를 피해야 합니다.

예) http://localhost:8080/api/read/books -> 이처럼 동사를 표현할 때는 HTTP 메서드인 GET, POST, PUT, DELETE 등으로 다음과 같이 대체해야 합니다.

- GET http://localhost:8080/api/books

모든 경우에 완벽하게 호환되지는 않습니다. 앞의 URI 설계에 대한 원칙은 어디까지나 불필요한 동사를 URI에 포함하는 것을 지양해야 한다는 것이지 완전히 배제시킨다는 것은 아닙니다.

 

복수형을 사용하라

URI에서는 명사에 단수형보다는 복수형을 사용해야 합니다. /book도 물론 명사고 사용 가능하지만 /books로 리소스를 표현하면 컬렉션으로 명확하게 표현할 수 있어 확장성 측면에서 더 좋습니다.

{

  books : [

         {

              book : ...

         },

         {

              book : ...

         },

         {

              book : ...

         }

  ] 

}

 

 

그리고 컬렉션으로 URI를 사용할 경우 컬렉션을 한번 더 감싼 중첩(nested) 형식으로 사용하는 것이 좋습니다.

만약 중첩하지 않고 바로 컬렉션을 반환하면 추후 수정할 때나 확장할 때 번거롭게 됩니다.

 

{

   -embedded : [

         {

              books : ...

         },

         {

              stores : ...

         },

  ]

}

행위 설계

Resource GET(read) POST(create) PUT(update) DELETE(delete)
/books book 목록 보기 해당 book 추가 - -
/books/1 ID가 1인 book 보기 - ID가 1인 book 수정 ID가 1인 book 삭제

 

2. 설계하기

2.1 MVC 패턴을 확용하는 방법

클라이언트 > 컨트롤러 > 서비스 > 리포지토리 > DB

 

2.2 스프링 부트 데이터 레스트를 활용하는 방법

클라이언트 > REST 리포지토리 > DB

- 컨트롤러와 서비스 단계가 없습니다. 필요하다면 생성하여 사용할 수도 있습니다. 스프링 부트 데이터 레스트는 REST URL 요청을 리포지토리 내부의 CRUD 메서드와 매핑하여 처리합니다.

 

3. 스프링 부트 MVC 패턴으로 REST API 구현하기

소스코드

- github.com/kgc0120/first_time_springboot2/tree/master/Spring-Boot-Community-Rest-master

 

kgc0120/first_time_springboot2

Contribute to kgc0120/first_time_springboot2 development by creating an account on GitHub.

github.com

 

CORS 허용 및 시큐리티 설정

WEB 프로젝트 localhost:8080과 REST API 프로젝트의 localhost:8081은 호스트는 동일할지라도 포트가 상이하기 때문에 Ajax 요청은 모두 실패하게 됩니다. 즉 출처는 자원 + 도메인 + 포트 번호("http://localhost:8080")로 결합된 문자열입니다. 이 조합에서 한 글자라도 다르면 다른 출처로 판단됩니다. 이러한 교차 출처 http 요청을 가능하게 해주는 매커니즘을 교차 출처 자원 공유(CORS)라고 합니다.  CORS는 서로 다른 도메인의 접근을 허용하는 권한을 부여합니다.

 

스프링 시큐리티를 이용해 쉽게 CORS를 설정할 수 있습니다.

다음은 모든 출처에 대해 허용하도록 설정하는 코드입니다.

 

@EnableWebSecurity - 웹용 시큐리티를 활성화하는 어노테이션입니다.

1. @CorsConfiguration 객체를 생성하여 CORS에서 Origin, Method, Header 별로 허용할 값을 설정할 수 있습니다.

2. 특정 경로에 대해 CorsConfiguration 객체에서 설정한 값을 CorsConfigurationSource 인터페이스를 구현한 UrlBasedCorsConfigurationSource에 적용시킵니다. 

3. .and().cors().configurationSource(source) - CorsConfigurationSource 인터페이스의 구현체를 파라미터로 받는 configurationSource가 있습니다. 여기에 설정한 UrlBasedCorsConfigurationSource 객체를 넣어주면 설정한 내용이 시큐리티 설정에 추가됩니다. 

 

글이 많이 길어질 것 같아서 다음 장에서 4.스프링 부트 데이터 레스트로 REST API 만들기 이어가도록 하겠습니다.

728x90
반응형

'Spring' 카테고리의 다른 글

스프링 부트 배치 - 기본  (2) 2021.05.19
스프링 부트 데이터 레스트로 REST API 만들기  (0) 2021.05.05
스프링 부트 시큐리티 + OAuth2  (0) 2021.04.20
스프링 부트 웹  (0) 2021.04.11
스프링 부트 테스트  (0) 2021.04.09
반응형
  • 배경지식 소개
  • 스프링 부트 시큐리티 + OAuth2 설계하기
  • 스프링 부트 시큐리티 + OAuth2 의존성 설정하기
  • 스프링 부트 시큐리티 + OAuth2 구현하기
  • 스프링 부트 2.0 기반의 OAuth2 설정하기

배경지식 소개

스프링 부트 시큐리티에서 가장 중요한 개념은 인증(Authentication)과 권한 부여(Authorization)입니다.

인증은 사용자(클라이언트)가 애플리케이션의 특정 동작에 관하여 허락된 사용자인지 확인하는 절차를 말합니다.

보통 웹사이트 로그인을 인증이라 생각하면 됩니다.

권한 부여는 데이터나 프로그램 등의 특정 자원이나 서비스에 접근할 수 있는 권한을 허용하는 겁니다.

 

인증 방식은 다양합니다. 전통적인 인증방식으로 사용자명과 비밀번호로 인증하는 '크리덴셜 기반 인증 방식' OTP와 같이 추가적인 인증 방식을 도입해 한번에 2가지 방법으로 인증하는 '이중 인증 방식' 소셜 미디어를 사용해 편리하게 인증하는 'OAuth2 인증 방식' 

 

OAuth2

OAuth 토큰을 사용한 범용적인 방법의 인증을 제공하는 표준 인증 프로토콜입니다.

OAuth2에서 제공하는 승인 타입은 총 4가지입니다.

 

권한 부여 코드 승인 타입

 - 리소스 접근을 위한 사용자명과 비밀번호, 권한 서버에 요청해서 받은 권한 코드를 함께 활용하여 리소스에 대한 액  세스 토큰을 받으면 이를 인증에 이용하는 방식

 

암시적 승인 타입

- 권한 부여 코드 승인 타입과 다르게 권한 코드 교환 단계 없이 액세스 토큰을 즉시 반환받아 이를 인증에 이용하는 방식

 

리소스 소유자 암호 자격 증명 승인 타입

- 클라이언트 암호를 사용하여 액세스 토큰에 대한 사용자의 자격 증명을 교환하는 방식

 

클라이언트 자격 증명 승인 타입

- 클라이언트가 컨텍스트 외부에서 액세스 토큰을 얻어 특정 리소스에 접근을 요청할 때 사용하는 방식

 

여기서는 권한 부여 코드 승인 타입을 눈여겨보겠습니다. 페이스북, 구글, 카카오 등 소셜 미디어들이 웹 서버 형태의 클라이언트를 지원하는 데 이 방식을 사용하기 때문입니다.

 

권한 부여 코드 승인 타입 시퀀스 다이어그램

권한 부여 코드 승인 타입 시퀀스 다이어그램

 

스프링 부트 시큐리티 + OAuth2 설계하기

1. 사용자가 애플리케이션에 접속하면 해당 사용자에 대한 이전 로그인 정보(세션)의 유무를 체크합니다.

2. 세션이 있으면 그대로 세션을 사용하고, 없으면 OAuth2 인증 과정을 거치게 됩니다.

3. 이메일을 키값으로 사용하여 이미 가입된 사용자인지 체크합니다. 이미 가입된 사용자라면 등록된 정보를 반환하여 요청한 URL로 의 접근을 허용하고, 아니라면 새롭게 User 정보를 저장하는 과정을 진행합니다.

4. 각 소셜 미디어에서 제공하는 User 정보가 다르기 때문에 소셜 미디어에 따라 User 객체를 생성한 후 DB에 저장합니다.

 

스프링 부트 시큐리티 + OAuth2 의존성 설정하기

기존 spring 1.5버전에는 spring-security-oauth2만 설정해주어도 OAuth2 관련 모든 설정이 끝났지만 2.0부터는 설정이 세분화되었습니다. 기본적인 OAuth2 인증 관련 객체들이 시큐리티로 이전되었습니다.

 

스프링 부트 시큐리티 + OAuth2 구현하기

소셜 미디어중에서 카카오 OAuth2 로그인을 구현해보겠습니다.

먼저 카카오의 개발자센터에서 '클라이언트 ID'와 Secret(클라이언트 시크릿 키값, 클라이언트 보안 비밀)을 발급받아보겠습니다.

 

Kakao Developers 접속합니다.

- developers.kakao.com/console/app

 

1. 애플리케이션 추가하기

 

2. 플랫폼 선택 후 Web 플랫폼 등록

 

3. Redirect URI 등록하러 가기

  - 인증이 완료된 후 redirect 될 URI를 작성해줍니다. 

이로써 클라이언트 ID 발급은 끝났습니다.

 

SNS 프로퍼티 설정 및 바인딩

application.yml 파일에 아래와 같이 작성합니다.

구글, 페이스북에 대한 기본 정보는 스프링 부트 시큐리티 OAuth2 API에서 제공합니다.

그러므로 SNS 개발자센터에서 받은 ID와 Secret만 프로퍼티로 등록해주면 됩니다.

 

국내에서만 사용되는 소셜은 OAuth2 API에서 제공하는 방법과 동일하게 소셜 정보를 담은 객체를 생성합니다.

 

스프링 부트 시큐리티에서 제공되는 CommonOAuth2Provider.java 방법과 동일하게

CustomOAuth2Porvider.java에 KAKAO OAuth2 로그인 정보를 빌더로 생성

SecurityConfig.java

ouath2Login()만 추가로 설정하면 기본적으로 제공되는 구글과 페이스북에 대한 OAuth2 인증 방식이 적용됩니다.

추가적으로 @EnableWebSecurity 어노테이션은 웹에서 시큐리티 기능을 사용하겠다는 어노테이션입니다.

자동 설정 그대로 사용할 수도 있지만 요청, 권한, 기타 설정에 대해서는 필수적으로 최적화한 설정이 들어가야 합니다.

최적화 설정을 위해 WebSecurityConfigurerAdapter를 상속받고 configure(HttpSecurity http) 메서드를 오버라이드하여 원하는 형식의 시큐리티 설정을 합니다.

다음은 오버라이드한 configure() 메서드의 설정 프로퍼티에 대한 설명입니다. 더 많은 설정 항목이 있지만 해당하는 프로퍼티만 나열하겠습니다.

authorizeRequests() - 인증 매커니즘을 요청한 HttpServletRequest 기반으로 설정합니다.

- antMatchers() - 요청 패턴을 리스트 형식으로 설정합니다.

- permitAll() - 설정한 리퀘스트 패턴을 누구나 접근할 수 있도록 허용합니다.

- anyRequest() - 설정한 요청 이외에 리퀘스트 요청을 표현합니다.

- authenticated() - 해당 요청은 인증된 사용자만 할 수 있습니다.

 

headers() - 응답에 해당하는 header를 설정합니다. 설정하지 않으면 디폴트값으로 설정됩니다.

- frameOptions().disable() - XFrameOptionsHeaderWriter의 최적화 설정을 허용하지 않습니다.

 

authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")) - 인증의 진입 지점입니다. 인증되지 않은 사용자가 허용되지 않은 경로로 리퀘스트를 요청할 경우 '/login'으로 이동됩니다.

 

formLogin().successForwardUrl("/board/list") - 로그인에 성공하면 설정된 경로로 포워딩됩니다.

 

logout() - 로그아웃에 대한 설정을 할 수 있습니다. 코드에서는 로그아웃이 수행될 URL(logoutUrl), 로그아웃이 성공했을 때 포워딩될 URL(logoutSuccessUrl), 로그아웃을 성공했을 때 삭제될 쿠키값(deleteCookies), 설정된 세션의 무효화(invalidateHttpSession)를 수행하게끔 설정되어 있습니다.

 

addFilterBefore(filter, beforeFilter) - 첫 번째 인자보다 먼저 시작될 필터를 등록합니다. 

   - addFilterBefore(filter, CsrFilter.class) - 문자 인코딩 필터(filter)보다 CsrfFilter를 먼저 실행하도록 설정합니다.

 

SecurityConfig.java

OAuth2ClientProperties와 yml에서 설정했던 카카오 클라이언트 ID를 불러옵니다. @Configuration으로 등록되어 있는 클래스에서 @Bean으로 등록된 메서드의 파라미터로 지정된 객체들은 오토와이어링(autowiring)할 수 있습니다.

OAuth2ClientProperties에는 구글과 페이스북의 정보가 들어 있고 카카오는 따로 등록했기 때문에 @Value 어노테이션을 사용하여 수동으로 불러옵니다.

getRegistration() 메서드를 사용해 구글과 페이스북의 인증 정보를 빌드시켜줍니다.

registrations.add를 통해서 리스트에 카카오 정보를 추가합니다. 

 

UserArgumentResolver.java

HadlerMethodArgumentResolver는 전략 패턴의 일종으로 컨트롤러 메서드에서 특정 조건에 해당하는 파라미터가 있으면 생성한 로직을 처리한 후 해당 파라미터에 바인딩해주는 전략 인터페이스입니다. 따라서 AOP로 모든 메서드를 일일이 찾아보면서 파라미터에 바인딩하는 방법보다 훨씬 빠르고 만들기도 편리합니다.

 

소스코드

github - github.com/kgc0120/first_time_springboot2/tree/master/community_web

728x90
반응형
반응형

목표 

스프링 부트 웹 프로젝트 에노테이션 정리 


들어가기 전에..

주제가 web인 만큼 web 스타터를 한번 살펴보겠다. 

스프링 부트 웹 스타터에 내부에는 어떤 의존성을 갖고 있는지 확인해보겠다.

 

먼저 자신의 스타터 버전을 확인하고 싶으면 Intellij 기준으로 왼쪽 project 탭 아래에 External Libraies에서 starter 라이브러리를 찾으면 된다.(현재 2.4.4 버전 starter를 사용 중인 것을 확인할 수 있다.)

 

 

아래 url에서 자신의 스프링 부트 버전 브랜치를 선택하면 스타터에 명시된 버전의 의존성을 확인할 수 있다.

- github.com/spring-projects/spring-boot/tree/2.4.x/spring-boot-project/spring-boot-dependencies

 

스프링 부트 버전에 따라 달라진 점 확인은 아래 링크에서 확인 가능하다.

- github.com/spring-projects/spring-boot/wiki

 

에노테이션 정리

Lombok을 사용하면 생성자도 자동으로 생성할 수 있습니다. 

@NoArgsConstructor - 어노테이션은 파라미터가 없는 기본 생성자를 생성 

@AllArgsConstructor - 어노테이션은 모든 필드 값을 파라미터로 받는 생성자를 생성

@RequiredArgsConstructor - 어노테이션은 final이나 @NonNull인 필드 값만 파라미터로 받는 생성자를 생성

 

@GeneratedValue - 기본 키가 자동으로 할당되도록 설정하는 어노테이션

@Enumerated - Enum 타입 매핑용 에노테이션 (EnumType.STRING으로 사용을 권장, ORDINAL 사용 시 나중에 칼럼이 추가되거나 순서 변경될 때 심각한 오류 발생 우려가 있다.)

 

@Autowired - 의존성 주입하는 어노테이션 아래와 같이 권장하지 않는다.(생성자 주입 방식 권장)

 

친절한 intellij 해당 에노테이션을 추천하지 않는다고 해서 궁금해서 찾아보았다.

의존성을 주입하는 방식은 3가지가 있다.

1. 생성자 주입(Constructor Injection) - 권장

생성자 주입

2. 필드 주입(Field Injection)

필드 주입

3. 수정자 주입(Setter Injection)

수정자 주입

생성자 주입 방식과 필드, 수정자 주입 방식의 차이점

여러 가지 차이점이 있지만 대표적으로 객체 생성 시점이 아닌가 싶다.

객체 생성시점에서 순환 참조가 일어나는 것(생성자 주입) 객체생성 후 비즈니스 로직상에서 순환참조가 일어나는 것(필드, 수정자 주입)

필드 주입이나, 수정자 주입은 객체 생성 시점에는 순환참조가 일어나는지 아닌지 발견할 수 있는 방법이 없다.

생성자 주입은 컨테이너가 빈을 생성하는 시점에서 순환 참조가 있다면 객체 생성에 사이클관계가 생기기 때문에 앱구동 자체가 실패한다.

 

생성자 주입 방식 권장하는 이유

  • 의존관계 설정이 되지 않으면 객체생성 불가
  • 의존성 주입이 필요한 필드를 final로 선언 가능 - 객체 불변성 보장
  • 순환 참조 - 순환 참조 시 앱 구동 실패
  • 테스트 코드 작성 용이 - 필드 주입을 하면 단위 테스트 시 의존관계를 가지는 객체를 생성해서 주입할 수가 없다.
728x90
반응형
반응형

목차

  • @SpringBootTest
  • @WebMvcTest
  • @DataJpaTest
  • @RestClientTest
  • @JsonTest

들어가기 앞서..

Springboot 2.2.0 이후 버전부터는 junit5가 기본으로 되었다.

 

Junit 5 특징

Junit5는 Junit Platform, Junit Jupiter, Junit Vintage 모듈 세 가지로 구성되어 있다.

  • Junit Platform - JVM에서 동작하는 테스트 프레임워크이다. 테스트를 발견하고 계획을 생성하고 결과를 보고하는 TestEngine 인터페이스를 정의한다.
  • Junit Jupiter - Junit5 TestEngine의 실제 구현체입니다. Junit5 기반의 테스트를 실행시키기 위한 TestEngine을 Platform에 제공합니다.
  • Junit Vintage - TestEngine에서 Junit3 및 Junit4 기반 테스트를 실행하기 위한 기능을 제공한다.

@SpringBootTest

- 스프링 부트의 테스트 어노테이션에서 @SpringBootTest는 만능이다.

- 실제 구동되는 애플리케이션과 똑같이 애플리케이션 컨텍스트를 로드하여 테스트하기 때문에 하고 싶은 모든 테스트를 수행 할 수 있다.

- 규모가 클수록 속도가 느려진다.

 

@SpirngBootTest 파라미터

 

1. 테스트가 실행되기 전에 적용할 프로퍼티를 주입시킬 수 있다.

2. 테스트가 실행되기 전에 {key=value} 형식으로 프로퍼티를 추가할 수 있다.

3. 애플리케이션 컨텍스트에 로드할 클래스를 지정할 수 있다. 따로 지정하지 않으면 @SpringBootConfiguration을 찾아서 로드한다.

4. 애플리케이션이 실행될 때의 웹 환경을 설정할 수 있다. 기본값은 Mock 서블릿을 로드하여 구동된다.

 

**Tip

- 프로파일 환경(개발, QA, 운영)마다 다른 데이터소스(DataSource)를 갖는다면 @ActiveProfiles("local")과 같은 방식으로 원하는 프로파일 환경값을 부여한다.

- 테스트에서 @Transactional을 사용하면 테스트를 마치고 나서 수정된 데이터가 롤백됩니다.

하지만 webEnvironment의 RANDOM_PORT나 DEFINED_PORT로 테스트를 설정하면 테스트가 별도의 스레드에서 수행되기 때문에 rollback이 수행되지 않음

- @SpringBootTest는 기본적으로 검색 알고리즘을 사용하여 @SpringBootApplication이나 @SpringBootConfiguration 어노테이션을 찾습니다. 스피링 부트 테스트이기 때문에 해당 어노테이션중 하나는 필수이다.

 

@WebMvcTest

- MVC를 위한 테스트이다.

- 웹에서 테스트하기 힘든 컨트롤러를 테스트하는 데 적합하다.

- 시큐리티 혹은 필터까지 자동으로 테스트하며 수동으로 추가/삭제까지 가능하다.

- @WebMvcTest 어노테이션을 사용하면 MVC관련 설정인 @Controller, @ControllerAdvice, @JsonComponent와 Filter, WebMvcConfigurer, HandlerMethodArgumentResolver만 로드된다.

 

@DataJpaTest

- JPA 관련 테스틈 설정만 로드한다.

- 데이터소스 설정이 정상적인지, JPA를 사용하여 데이터를 제대로 생성, 수정, 삭제하는지 등의 테스타가 가능하다.

- 기본적으로 인메모리 임베디드 데이터베이스를 사용하며, @Entity 클래스를 스캔하여 스프링 데이터 JPA 저장소를 구성한다. 

- JPA 테스트가 끝날 때마다 자동으로 테스트에 사용한 데이터를 롤백한다. @Transactional 어노테이션 내장되어 있다.

 

@RestClientTest

- REST 관련 테스트를 도와주는 어노테이션이다.

- REST 통신의 데이터형으로 사용되는 JSON 형식이 예상대로 응답을 반환하는지 등을 테스트할 수 있다.

 

@JsonTest

- JSON 테스트를 지원하는 어노테이션이다.

- JSON의 직렬화 역 직렬화를 수행하는 라이브러리인 Gson과 Jackson API의 테스트를 제공한다.

- 각각 GsonTester와 JacksonTester를 사용하여 테스트를 수행한다.

 

마무리

각 어노테이션의 용도를 정확히 이해하여 적합한 상황에서 사용하는 것이 중요하다.

스프링의 모든 빈을 올리는 대신, 각 테스트에 필요한 가짜 객체를 만들어 테스트하는 방법을 사용해 상황에 맞는 테스트를 하는 게 중요하다.

 

 

 

** Intellij에서 테스트코드 참고사항

1. 아래와 같이 에러가 출력되면 Setting에 Run tests using을 inteillij IDEA로 수정 후 확인해보자.

 

FAILURE: Build failed with an exception.

* What went wrong:

Execution failed for task ':test'.

 

Junit5을 사용하면서 문제가 발생하는 걸로 보인다.

 

 

2. 테스트 코드 한글 이름이 깨지면 탭에서 Help > Edit custom VM Options..설정에 아래 encoding 라인을 추가하자

-Dfile.encoding=UTF-8

 

 

 

 

728x90
반응형
반응형

취준생일 때 우연치 않게 구글링하다가 '기억보단 기억을' 블로그 운영자이신 이동욱 님의 3번째 직장에 오기까지 시리즈를 읽게 되었다.

정말 그 당시 너무 감명 깊게 읽었다. 이동욱 님의 간절했고, 피나는 노력이 글 한 문장 한 문장에서 느껴졌다. 글이 살아있다는 느낌을 받았다.

지금까지 블로그 글을 읽고 나서 그런 감정을 느껴본 적은 없었다. 지금까지도... 내가 가고자 하는 길인데 의지박약인 내가 이 정도의 피나는 노력을 할 수 있을까..  무섭고 이동욱 님이 경의로웠다. 그다음부터 자연스럽게 나의 롤 모델이 되었고, 블로그를 자주 들여다보게 되었다. 그 당시 이동욱 님의 블로그 글을 이해하기에는 부족한 실력을 가진 나였지만 한 번씩 들여다보았다.

 

그러던 중 '스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 책을 쓰셨다는 글을 읽었다.

고민도 하지 않고 바로 주문했다. 책 구성부터가 당시 나에게 너무 필요한 부분이었다. 혼자서 개발 환경 구성부터 배포까지 경험해본 적이 없었기 때문이다. 책을 따라서 해볼 생각에 엄청 설렜던 기억이 난다. 하지만.. 부족한 실력에도 불구하고 당시 취업을 해서 많은 시간이 지난 지금 다시 책을 폈다... 

 

그래도 그 당시에는 목차에 있는 제목들이 많이 낯설었는데 시간이 지난 지금은 많이 익숙한 제목들이 보인다. 조금이나마 성장을 하고 있다는 뜻일까 ㅎㅎ,,  

 

시간이 지난 지금도 사실 개발 환경 구성과 배포의 경험을 제대로 해본 적이 없다. 책을 조금 읽었는데 정말 이해하기 쉽고 따라 하기 쉽게 잘 구성되어져있다. 이 책을 통해서 차근차근 성장하는 개발자가 되고싶다.

 

 

 

728x90
반응형

'Spring' 카테고리의 다른 글

스프링 부트 데이터 레스트로 REST API 만들기  (0) 2021.05.05
스프링 부트 데이터 레스트  (0) 2021.05.01
스프링 부트 시큐리티 + OAuth2  (0) 2021.04.20
스프링 부트 웹  (0) 2021.04.11
스프링 부트 테스트  (0) 2021.04.09

+ Recent posts