항해99 개발일지

[항해99] Spring 숙련 (AOP, Transaction)

펭귄코기 2022. 10. 8. 21:09

AOP란?

'핵심기능': 각 API 별 수행해야 할 비즈니스 로직

ex) 상품 키워드 검색, 관심상품 등록, 회원 가입, 관심상품에 폴더 추가...

 

'부가기능': 핵심기능을 보조하는 기능

ex) 회원 패턴 분석을 위한 로그 기록, API 수행시간 저장

 

// 측정 시작 시간
long startTime = System.currentTimeMillis();

try {
	// 핵심기능 수행
	// 로그인 되어 있는 회원 테이블의 ID
	Long userId = userDetails.getUser().getId();
	
	Product product = productService.createProduct(requestDto, userId);
	
	// 응답 보내기
	return product;
} finally {
	// 측정 종료 시간
	long endTime = System.currentTimeMillis();
	// 수행시간 = 종료 시간 - 시작 시간
	long runTime = endTime - startTime;
	// 수행시간을 DB 에 기록
	...
}

 

문제점

모든 '핵심기능'의 Controller 에 '부가기능' 코드를 추가했을 때..

- '핵심기능' 이 100개라면??

- 100개의 '핵심기능' 모두에 동일한 내용의 코드 추가 필요

 

'핵심기능' 이 나중에 추가된다면?

- 항상 '부가기능' 추가를 신경써야 함

 

부가기능' 추가를 깜박한다면?

- 일부 API 수행시간이 추가되지 않음 → Top5 회원의 신뢰성 이슈

 

'핵심기능' 수정 시

- 같은 함수 내에 '핵심기능'과 '부가기능'이 섞여 있음

- '핵심기능' 이해를 위해 '부가기능'까지 이해 필요

 

'부가기능'의 변경이 필요하다면??

- '핵심기능'의 개수만큼 '부가기능'도 수정해 줘야 함

- '부가기능' 삭제

 

AOP (Aspect Oriented Programming) 를 통해 부가기능을 모듈화

- **'부가기능'**은 '핵심기능'과는 관점(Aspect), 관심이 다름

- 따라서 '핵심기능'과 또옥~!! 분리해서 '부가기능' 중심으로 설계, 구현 가능

 

스프링 AOP 어노테이션

@Aspect

스프링 빈 (Bean) 클래스에만 적용 가능

 

어드바이스 종류

@Around: '핵심기능' 수행 전과 후 (@Before + @After)

@Before: '핵심기능' 호출 전 (ex. Client 의 입력값 Validation 수행)

@After: '핵심기능' 수행 성공/실패 여부와 상관없이 언제나 동작 (try, catch 의 finally() 처럼 동작)

@AfterReturning: '핵심기능' 호출 성공 시 (함수의 Return 값 사용 가능)

@AfterThrowing: '핵심기능' 호출 실패 시.

즉, 예외 (Exception) 가 발생한 경우만 동작 (ex. 예외가 발생했을 때 개발자에게 email 이나 SMS 보냄)

 

포인트컷 Expression Language

포인트컷 Expression 형태

execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)

? 는 생략 가능

 

포인트컷 Expression 예제

@Around("execution(public * com.sparta.springcore.controller..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { ... }

 

 

modifiers-pattern, 제어자

public, private, *

 

return-type-pattern, 반환타입

void, String, List<String>, *****

 

declaring-type-pattern, 클래스

클래스명 (패키지명 필요)

com.sparta.springcore.controller.* - controller 패키지의 모든 클래스에 적용

com.sparta.springcore.controller.. - controller 패키지 및 하위 패키지의 모든 클래스

 

method-name-pattern(param-pattern)

함수명

addFolders : addFolders() 함수에만 적용

add* : add 로 시작하는 모든 함수에 적용

 

파라미터 패턴 (param-pattern)

(com.sparta.springcore.dto.FolderRequestDto) - FolderRequestDto 인수 (arguments) 만 적용

() - 인수 없음

(*) - 인수 1개 (타입 상관없음)

(..) - 인수 0~N개 (타입 상관없음)

 

@Pointcut

포인트컷 재사용 가능

포인트컷 결합 (combine) 가능

@Component
@Aspect
public class Aspect {
	@Pointcut("execution(* com.sparta.springcore.controller.*.*(..))")
	private void forAllController() {}

	@Pointcut("execution(String com.sparta.springcore.controller.*.*())")
	private void forAllViewController() {}

	@Around("forAllContorller() && !forAllViewController")
	public void saveRestApiLog() {
		...
	}

	@Around("forAllContorller()")
	public void saveAllApiLog() {
		...
	}	
}

 

Transaction

트랜잭션: 데이터베이스에서 데이터에 대한 하나의 논리적 실행단계

 

ACID (원자성, 일관성, 고립성, 지속성)는 데이터베이스 트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질을 가리키는 약어

 

트랜잭션의 특징

더 이상 쪼갤 수 없는 최소단위의 작업

모두 저장되거나, 아무 것도 저장되지 않거나를 보장!!

모! 아니면 도!!

 

모두 성공 시 ⇒트랜잭션 Commit

중간에 하나라도 실패 시 ⇒ 트랜잭션 Rollback

 

DB 운영 방식 (Primary, Replica)

쓰기 전용 DB (Primary) 와 읽기 전용 DB (Replica) 를 구분

 

Primary: 쓰기 전용

@Transactional 의 readOnly 속성

@Transactional(readOnly = false)

 

readOnly 를 코드에 적지 않으면, 기본값은 false

import org.springframework.transaction.annotation.Transactional;

@Transactional
public List<Folder> createFolders(List<String> folderNameList, User user) {

Write 된 Data (Create, Update, Delete) 가 Replica 로 Sync 됨 (Replication)

 

Replica (Secondary): 읽기 전용

@Transactional(readOnly = true)

하지만, 위 개념은 스프링에 Primary DB endpoint, Replica DB endpoint 를 설정해야지만 가능

 

수십년 동안 통용되던 용어가 '노예제'와 관련된다는 이유로 대체됨

마스터 (Master) → Primary

슬레이브 (Slave) → Replica, Secondary

 

Primary 에 문제가 생겼을 때

Replica 중 1개가 Primary 가 됨

다시 Primary - Replica 로 정상 운영