본문 바로가기

Spring Boot/Cache

Spring Cache 추상화 : 추상화의 필요성

캐시를 추상화..?

 

- Spring Framework의 Cache 추상화 계층 구조 이해

- 다양한 캐시 구현체(Caffeine, Redis, EnCache 등)와의 독립성을 보장하는 이유

- 캐시를 일관된 방식으로 제어할 수 있는 Spring Cache API 구조

- 선언적(Declarative) 캐시 관리 방식

- 캐시 추상화를 적용함으로써 얻을 수 있는 코드 유지보수성 향상 효과

 


Spring Cache 추상화

1. 캐시 추상화

Spring Cache 추상화는 여러 종류의 캐시 구현체를 일관된 방식으로 사용할 수 있게 해주는 통합 계층임

개발자는 캐시 내부 동작(저장방식, 네트워크 구조 등등..)을 몰라도, 하나의 통합 인터페이스(API)를 통해 다양한 캐시를 다룰 수 있게됨

캐시 추상화

 

Caffeine -> EnCache -> Redis로 교체하더라도 비즈니스 코드 수정 없이 설정만 변경해주면 됨

* MongoDB를 쓸 수 있지 않을까?

 

2. Spring Cache 추상화의 주요 목적

목적 설명
구현체 독립성 Cache API를 표준화하여 Caffeine, Redis, Guava 등 구현체를 쉽게 교체 가능함
일관된 캐시 접근 방식 동일한 애너테이션(@Cacheable, @CacheEvict 등)으로 다양한 캐시 제어
선언적 캐시 관리 코드 내부에서 캐시 로직을 직접 작성하지 않아도 애너테이션으로 제어 가능
코드 유지보수성 향상 비즈니스 로직과 캐시 로직을 분리하여 코드 가독성과 관리성 향상

 

 


캐시 추상화의 필요성

1. 문제상황

Spring 이전 전통적 방식에서는, 개발자가 직접 캐시 로직을 작성했음

// 전통적 캐시 관리 방식
public String getUserName(Long id) {
	String key = "user:" + id; // key
    
   // 캐시 조회 로직 직접 작성
   if(localCache.containsKey(key)) {
   		return localCache.get(key);
   }
   
   String name = userRepository.findNameById(id);
   localCache.put(key, name);
   return name;
}

 

이 방식의 단점

* 캐시 로직이 비즈니스 로직(서비스)에 침투함 -> 코드 복잡도가 증가함

* 캐시 변경(Redis로 교체하면..)시 전체 코드 수정 필요 -> 유지보수가 어려움

 

 

2. Spring Cache 추상화 적용 후

@Cacheable(value = "userCache", key = "#id")
public String getUserName(Long id) {
	return userRepository.findNameById(id);
}

 

Spring Cache를 사용해서 간결하게 표현 가능

 

내부 동작

1) 메서드 실행 전 캐시(userCache)를 확인함

2) 캐시에 존재하면 메서드 실행 없이 캐시 데이터를 반환함

3) 캐시에 없으면 실제 메서드를 실행 후 결과를 캐시에 저장함

 

결과적으로, 개발자는 비즈니스 로직에 집중할 수 있고 캐시 관리 코드는 스프링이 담당하게 됨

 


Spring Cache 추상화 구조

세 가지 주요 계층으로 구성됨

 

Spring Cache Abstraction

- > @Cacheable / @CacheEvict / @CachePut (Annotation Layer)

- > CacheManager(관리자)

- > - > Cache(실제 캐시 저장소)

- > - > CacheResolver(동적 캐시 결정)

- > 캐시 구현(Caffeine, Redis, EnCache 등)

 

구성 요소 개요

구성 요소 역할
CacheManager 캐시를 생성하고, 각 캐시 영역(Cache)을 관리함
Cache 캐시 데이터의 CRUD를 수행하는 인터페이스
CacheResolver 여러 캐시중 어떤 캐시를 사용할지 결정함

 

 


선언적 캐시 관리

Spring은 캐시 로직을 AOP 기반으로 자동 적용함

개발자는 복잡한 캐시 제어 코드를 작성하지 않고, 단지 애너테이션을 붙이는 것만으로 캐시를 적용할 수 있다

애너테이션 역할
@Cacheable 메서드 실행 결과를 캐시에 저장하고, 캐시 존재시 결과를 재사용함
@CacheEvict 특정 캐시 데이터를 삭제(주로 업데이트 후 사용)
@CachePut 메서드 실행 결과를 항상 캐시에 저장함(강제 갱신 시 사용함)
@Caching 여러 캐시 애너테이션을 결합해서 사용함

 


예제

@Service
public class UserService {
	@Cacheable(value = "userCache", key = "#id")
    public User getUserById(Long id) {
    	System.out.println("[DB 조회] 캐시에 데이터가 없어서 DB 접근 수행");
        return userRepository.findById(id).orElseThrow();
    }
    
    @CacheEvict(value = "userCache", key = "#id")
    public void deleteUser(Long id) {
    	System.out.println("[Cache Evict] 사용자 정보 삭제");
        userRepository.deleteById(id);
    }
}

 

예시 결과(예상)

[DB 조회] 캐시에 데이터가 없어 DB 접근 수행
// 이후 두번째 호출부터는 캐시 Hit 수행

 

 


캐시 추상화 적용의 장점

 - 코드 단순화 : 캐시 로직을 직접 작성하지 않아도 됨

 - 유지보수성 향상 : 캐시 교체시 코드 수정 없이 설정만 변경 가능

 - 테스트 용이성 : 캐시와 비즈니스 로직 분리로 단위 테스트가 용이함

 - 유연한 확장성 : 다양한 캐시 구현체(Caffeine, Redis 등등..) 통합 가능함

 


* 캐시 이름(value)은 도메인 단위로 명명하면 관리가 편함(예 : userCache, contentCache, productCache 등)

* key는 #id, #root.methodName 등 SpEL을 적극 활용하면 유연한 캐시 키 구성이 가능함

* 캐시와 DB의 일관성을 위해 @CacheEvict와 @CachePut을 적절히 조합해야함

* @EnableCaching 애너테이션을 @Configuration 클래스에 반드시 추가해줘야 캐시 기능이 활성화

 


정리

구분 주요 개념 요약
Spring Cache 추상화 캐시 접근 통합 계층 캐시 구현체 교체 가능
주요 구성 요소 CacheManager, Cache, CacheResolver 캐시 관리 및 실행 담당
선언적 캐시 관리 @Cacheable, @CacheEvict 등 코드 간결화 및 유지보수 향상
적용 효과 캐시 독립성, 테스트 용이성, 확장성 확보 다양한 환경에 적용 가능