[Refactor] ํจํค์ง ๊ตฌ์กฐ์ ์์กด์ฑ
๋ ๋ฒ์งธ ํ๋ก์ ํธ์ ์ฝ๋ ์์ฑ์ด ๊ฑฐ์ ๋๋ฌ๊ณ , ํ ์คํธ ์ฝ๋ ์์ฑ์ ์๋๋ฉฐ ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ์๋ค.
๊ฐ์ฅ ํฐ ๊ณจ์๋ ์๋ฌด๋๋ ์ฐธ์กฐ ๊ด๊ณ์ด๋ค.
ํจํค์ง ๊ตฌ์กฐ๋ฅผ Layered์์ ์ฝ๊ฐ์ DDD(์ ๋งคํ์ง๋ง ใ ใ ) ๋ฅผ ๊ณ๋ค์ธ ๊ตฌ์กฐ๋ก ๋ณ๊ฒฝํ๋ค.
๊ทธ ๊ณผ์ ์์ ํจํค์ง ๊ฐ ์์กด์ฑ์ ๋ํด์ ๊ณ ๋ฏผํด๋ณด๊ณ ์๋ช ํ๋ ๊ฒ๊ณผ ์ค๊ณํ๋ ์๊ฐ์ด ์ ๋ง ์ค๋ ๊ฑธ๋ ธ๋ค.
์ฝ๋ฉ์ ๊ณต๋ถํ๋ฉด ํ ์๋ก ์์ ๊ฒ์ ์๊ฐ์ ์ค๋ ๋ค์ด๊ฒ ๋๋ ๊ฑธ ๋๋๋ค.
์ด์ ๋ ํจํค์ง ์ด๋ฆ์ ์ง๋๋ฐ ๋ฐ๋์ ์ด ๊ฑธ๋ ธ๋ค.
ํ์ฌ์์๋ ๋ณ์๋ช ์ง๋ ๊ฑธ๋ก๋ ํ์๋ฅผ ํ๋ค๊ณ ํ๋ ์ฝ๊ฐ ์ค๊ฐ์ด ๋๊ธฐ๋ ํ๋ค.
์ด๋ ๊ฒ ๋ํ ์ผํ๊ฒ ์ฑ์๋๊ฐ๋ฉด ๊ทธ ๋งํผ ๋ด ์ค๋ ฅ์ด ๋๋ค๊ณ ๋ฏฟ์ต๋๋ค.
์ต์์ ๊ตฌ์กฐ
- auth : ์ธ์ฆ, ์ธ๊ฐ ์ฒ๋ฆฌ
- ์คํ๋ง ์ํ๋ฆฌํฐ ์ด์ฉ
- ์คํ๋ง ์ปจํ ์ด๋๊น์ง ๋๋ฌํ์ง ์๋ ํํฐ ์์ฃผ์ด๊ธฐ ๋๋ฌธ์ ํจํค์ง๋ฅผ ๋ฐ๋ก ๊ตฌ์ฑํ๋ค.
- domain
- ๋๋ฉ์ธ ๊ด๋ จ ์๋น์ค๋ก ๊ตฌ์ฑ๋ ํจํค์ง
- global
- ๋ชจ๋ ํจํค์ง์์ ์ฌ์ฉ ๊ฐ๋ฅ
- ์ ์ญ์ ์ผ๋ก ์ ์ฉ
- usecase
- ๋๋ฉ์ธ์ ๋น์ฆ๋์ค๋ก์ง์์ ํจํค์ง ๊ฐ ์์กด์ฑ์ ๋ฐ๋ก ๋ถ๋ฆฌํ๊ธฐ ์ํด ๊ตฌ์ฑํ ํจํค์ง
- ์ฃผ๋ฌธ, ๊ฒฐ์ ์์ ์ ๋ง์ ์ฐธ์กฐ๋ฅผ ๊ฐ์ง๊ธฐ ๋๋ฌธ์ ๋ถ๋ฆฌํด์ usecase๋ก ์ถ๊ฐํ๋ค.
์ฐจ์์ ๊ตฌ์กฐ
๋ํ ์ผํ๊ฒ ์ค๋ช ํ์ง ์๊ฒ ์ง๋ง, ์ถ์์ ์ธ ๋ค์ด๋ฐ๋ณด๋ค๋ ์๋ฏธ๋ฅผ ๊ฐ์ง ์ด๋ฆ์ผ๋ก ์ต๋ํ ์ง์ด๋ณด๋ ค๊ณ ํ๋ค.
Redis
Redis ๊ด๋ จ๋ ์ค์ ๊ณผ ํด๋์ค๋ค์ ๊ตฌ์กฐ๋ฅผ ๊ณ ๋ฏผํ๊ณ ๊ฐํธํ๋ค.
RedisFunction
์์๋ ํธ๋์ญ์ ์ ์ฑ ์๊น์ง ๊ฐ๊ณ ์๋ RedisFunctionProvider ๊ฐ์ฒด์๋ค.
ํธ๋์ญ์ ์ฒ๋ฆฌ๋ ์ต๋ํ ์คํ๋ง์ด ์ ๊ณตํด์ฃผ๋ @Transactional์ ์ด์ฉํ๋ ๊ฒ์ด ๋ฐ๋์งํ๋ค๊ณ ์๊ฒฌ์ ๋ค์๊ธฐ ๋๋ฌธ์, ๋ฐ๋ก ํธ๋์ญ์ ์ฒ๋ฆฌ Aspect๋ฅผ ๊ณผ๊ฐํ ์ญ์ ํ๋ค.
public interface RedissonFunctionProvider {
RLock lock(String lockKey);
RLock lock(String lockKey, long timeout);
RLock lock(String lockKey, TimeUnit unit, long timeout);
boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime);
void unlock(String lockKey);
void unlock(RLock lock);
Object getValue(String key);
void setValue(String key, Object value);
boolean canUnlock(String lockKey);
}
- ํธ๋์ญ์ ๋ฉ์๋๋ฅผ ์ญ์
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLocked {
/**
* ์ฌ์ฉ์ ์ ์ redisson lock Key
*/
String key() default "";
/**
* redisson lock ์ ์ง ์๊ฐ <br>
* ๋จ์ : milliseconds
*/
long leaseTime() default 3000;
/**
* redisson lock ํ๋ ๋๊ธฐ ์๊ฐ <br>
* ๋จ์ : milliseconds
*/
long waitTime() default 3000;
}
- ํ๋ผ๋ฏธํฐ์ ์ข ์์ ์ด์ง ์๊ธฐ ์ํด ๋ฝ ํค๋ฅผ ์์ ๋ฐ์์ค๋ ์ค์ ์ ์ถ๊ฐ
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
@Order(value = Integer.MIN_VALUE)
public class RedisLockAspect {
- @Transactional๋ก ๋จผ์ ๊ฐ์ธ์ง๊ณ ์ตํ์ ์ด Aspect๊ฐ ๊ฐ์ธ์ง๋ ๊ฒ์ ์ํ๋ฏ๋ก @Order๋ฅผ ์กฐ์ ํ๋ค.
Redis Store
RedisStore ๊ฐ์ฒด๋ ์ถ์ํ๋ RedisTemplate์ ์ด์ฉํ์ฌ Redis์ ์ ๊ทผํด ๋ฐ์ดํฐ๋ฅผ ๋ฃ๊ณ ๋นผ๊ณ ํ๋ ์ญํ ์ ํ๋ค.
ํ์ง๋ง ํ๋๋ StringRedisTemplate๋ง์ ๋ฐ์๋์์๊ณ , Global ํจํค์ง์ ๊ตฌ์ฑํ๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ RedisTemplate์ ์ฌ์ฉํ๋๋ฐ ํ๊ณ๊ฐ ์๊ณ , ์ ์ญ์ ์ฌ์ฉ์ ์ด๋ ค์๊ณผ SOLID 2์์น์ ์๋ฐฐ๋์๋ค๋ ์ฌ์ค์ ์๊ฒ ๋์๋ค.
๊ทธ๋์ ์ถ์ํด๋์ค๋ก ํ๋ฒ ์ ์ ํด๋ดค๋ค.
@RequiredArgsConstructor
public abstract class RedisStore {
private final RedisTemplate<String, Object> redisTemplate;
private final ObjectMapper objectMapper;
public <T> T getData(String key, Class<T> valueType) {
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
String value = (String) valueOperations.get(key);
if (StringUtils.isBlank(value)) {
throw new BadRequestException("ํด๋น ํค์ ๋ง๋ ๊ฐ์ด ์กด์ฌํ์ง ์์ต๋๋ค.");
}
try {
return objectMapper.readValue(value, valueType);
} catch (JsonProcessingException e) {
throw new InternalErrorException(e);
}
}
public void setData(String key, Object value) {
try {
String jsonValue = objectMapper.writeValueAsString(value);
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
valueOperations.set(key, jsonValue);
} catch (JsonProcessingException e) {
throw new InternalErrorException(e);
}
}
public void setDataExpire(String key, Object value, long duration) {
try {
String jsonValue = objectMapper.writeValueAsString(value);
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
Duration expireDuration = Duration.ofSeconds(duration);
valueOperations.set(key, jsonValue, expireDuration);
} catch (JsonProcessingException e) {
throw new InternalErrorException(e);
}
}
public Boolean deleteData(String key) {
return redisTemplate.delete(key);
}
}
- ์ผ๋จ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ String, Object๋ก ๊ตฌ์ฑ์ ํ๋ค.
- ์ ๋ค๋ฆญ๊ณผ Object mapper๋ฅผ ์ฌ์ฉํด์ ๊ฐ์ ๊ฐ์ ธ์ฌ ๋, ์ค๋ธ์ ํธ๋ก ๊ฐ์ ธ์ฌ ์ ์๊ฒ ์งฐ๋ค.
@Component
public class UserRedisStore extends RedisStore {
public UserRedisStore(RedisTemplate<String, Object> redisTemplate, ObjectMapper objectMapper) {
super(redisTemplate, objectMapper);
}
}
- ์ฌ์ฉ ํด๋์ค๋ @Component๋ก Bean ๋ฑ๋ก์ ํ๊ณ ํ์ํ ํ ํ๋ฆฟ๊ณผ ๋งคํผ๋ฅผ ์ฃผ์ ๋ฐ์ ์์ ํด๋์ค์ ์ ๊ณตํ๋ค.
- ๋ค๋ฅธ ๋ ๋์ค ํ ํ๋ฆฟ์ ์ฌ์ฉํ ๊ฒฝ์ฐ ์ง์ ์ฃผ์ ๋ฐ์ ์ฌ์ ์ ํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
์ด๊ฒ ์ต์ ์ ๊ตฌ์ฑ์ธ์ง๋ ์ ๋ชจ๋ฅด๊ฒ ๋ค.
์ดํํฐ๋ธ ์๋ฐ๋ฅผ ๋ณด๋ฉด์ ์์๋ณด๋ค๋ ์ปดํฌ์ง์ ์ ์ฌ์ฉํ๋ผ๋ ์์ดํ ์ฑํฐ๋ฅผ ์ฝ์๋๋ฐ,
์์์ ๊ตฌํํ ๋๋ ๋ฉ์๋์ ์ฌ์ฌ์ฉ๋ณด๋ค๋, ํ์ ์ ์ํด ์ฌ์ฉํ๋ผ๋ ๋ง์ด ์์๋ค.
์ง์ ํ Is - A ๊ด๊ณ์ผ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด๋ณด๋ผ๋ ๊ฒ์ด๋ค.
๊ทผ๋ฐ ๋๋ ์ฌ์ ์๋ฅผ ์ ํ ๊ฒฝ์ฐ ์ฌ์ฌ์ฉ์ ํ๋ ๊ฒ ๊ฐ๊ธฐ๋ ํ๋ค. ์์ง๊น์ง๋ ์ด๊ฒ ์ต์ ์ธ ๋ฏ ์ถ๋ค.
๊ฐ์
์์ง๊น์ง ๋ถ์กฑํ ๊ฒ ๋ง์ ๊ฒ ๊ฐ๋ค.
์๋ฒฝํ๊ฒ ์ฑ์ธ ์ ์๋ค๋ ์๊ฐ์ด ๋ค์๋ค. ์ฃผ์ด์ง ์ํฉ์์ ์ต์ ์ ๋คํ ๋ฟ
์๋๊ฐ ๋๋ฆฌ๋ค๊ณ ์๊ฐํ์ง ๋ง๊ณ , ๊ผผ๊ผผํ ๊ตฌ์ฑ์๋ง ์ง์คํ์.
์ด์ ํ ์คํธ ์ฝ๋๋ฅผ ์ง๊ณ ์๋ค.
ํ ์คํธ ์ฝ๋๋ฅผ ์ง๋ค๋ณด๋๊น ํด๋์ค ๊ฐ ๊ฐ๊ฒฐํฉ์ ์ง์ํ๋ผ๋ ์๋ฏธ๋ฅผ ๋น๋ก์ ์๊ฒ ๋์๋ค.
๋ ์์ ๊ฐ์ฒด, ์์ ํ ์คํธ๋ฅผ ์ํ.
๋ฐ์ดํฐ ํ ์คํธ๋ฅผ ํ ๋์๋ H2 db๋ฅผ ์ฌ์ฉํด์ ๊ตฌํํ๋ ๊ฒ๋ ์์ง ๋ง์