naver/arcus-spring

KeyGenerator의 새로운 인터페이스 추가 여부 검토

Closed this issue · 5 comments

AS-IS

KeyGenerator

현재 KeyGenerator의 유일한 인터페이스는 다음과 같습니다.

Object generate(Object target, Method method, Object... params);

Implementation of KeyGenerator

단, 현재 구현 상 매개변수 target과 method는 사용하지 않습니다.

public class StringKeyGenerator implements KeyGenerator {
  @Override
  public Object generate(Object target, Method method, Object... params) {
    int hash = 0;
    StringBuilder keyBuilder = new StringBuilder();
    for (int i = 0, n = params.length; i < n; i++) {
      if (i > 0) {
        keyBuilder.append(DEFAULT_SEPARTOR);
      }
      if (params[i] != null) {
        keyBuilder.append(params[i]);
        hash ^= ArcusStringKey.light_hash(params[i].toString());
      }
    }

    return new ArcusStringKey(keyBuilder.toString().replace(' ', '_') + hash);
  }
}

public class SimpleStringKeyGenerator implements KeyGenerator {
  @Override
  public Object generate(Object target, Method method, Object... params) {
    StringBuilder keyBuilder = new StringBuilder();
    for (int i = 0, n = params.length; i < n; i++) {
      if (i > 0) {
        keyBuilder.append(DEFAULT_SEPARTOR);
      }
      if (params[i] != null) {
        keyBuilder.append(params[i]);
      }
    }
    return new ArcusStringKey(keyBuilder.toString());
  }
}

Usage

따라서 CacheManager를 직접 주입 받아 사용할 경우, KeyGenerator를 다음과 같이 사용해야 합니다.

@Service
public class ProductService {
    @Autowired
    private CacheManager cacheManager;
    
    @Autowired
    private KeyGenerator keyGenerator;

    /*
        using the "testCache" cache with 60 expire seconds and "TEST-PRODUCT" prefix.
    */
    public Product getProduct_TestCache(int id) {
      Product product = cacheManager.getCache("testCache")
              .get(keyGenerator.generate(null, null, id), Product.class);
      
      if (product == null) {
        return new Product(id);
      }
      return product;
    }
    
    /*
        using the "devCache" cache with 120 expire seconds and "DEV-PRODUCT" prefix.
    */  
    public Product getProduct_DevCache(int id) {
      Product product = cacheManager.getCache("devCache")
              .get(keyGenerator.generate(null, null, id), Product.class);
      
      if (product == null) {
        return new Product(id);
      }
      return product;
    }

    /*
        using the "missingCache" cache with 60 expire seconds and "DEFAULT" prefix.
    */
    public Product getProduct_DefaultCache(int id) {
      Product product = cacheManager.getCache("missingCache")
              .get(keyGenerator.generate(null, null, id), Product.class);
      
      if (product == null) {
        return new Product(id);
      }
      return product;
    }
    
}

Issue

두 매개변수를 null로 주어도 내부적으로 사용하지 않으니 올바르게 동작하지만, 얼핏 코드를 보면 KeyGenerator를 사용하는 인터페이스가 잘못되었단 느낌을 받기 쉽습니다.

TO-BE

따라서 다음과 같은 클래스를 추가하여 매개변수에 null을 넣을 필요 없는 인터페이스를 오버로딩으로 제공하면 좋을 것 같습니다.

New Class

public abstract class ArcusKeyGenerator implements KeyGenerator {
  public Object generate(Object... params) {
    return generate(null, null, params);
  }
}

Implementation of KeyGenerator

public class StringKeyGenerator extends ArcusKeyGenerator { // 상속 관계 변경
  @Override
  public Object generate(Object target, Method method, Object... params) {
    int hash = 0;
    StringBuilder keyBuilder = new StringBuilder();
    for (int i = 0, n = params.length; i < n; i++) {
      if (i > 0) {
        keyBuilder.append(DEFAULT_SEPARTOR);
      }
      if (params[i] != null) {
        keyBuilder.append(params[i]);
        hash ^= ArcusStringKey.light_hash(params[i].toString());
      }
    }

    return new ArcusStringKey(keyBuilder.toString().replace(' ', '_') + hash);
  }
}

public class SimpleStringKeyGenerator extends ArcusKeyGenerator { // 상속 관계 변경
  @Override
  public Object generate(Object target, Method method, Object... params) {
    StringBuilder keyBuilder = new StringBuilder();
    for (int i = 0, n = params.length; i < n; i++) {
      if (i > 0) {
        keyBuilder.append(DEFAULT_SEPARTOR);
      }
      if (params[i] != null) {
        keyBuilder.append(params[i]);
      }
    }
    return new ArcusStringKey(keyBuilder.toString());
  }
}

Usage

@Service
public class ProductService {
    @Autowired
    private CacheManager cacheManager;
    
    @Autowired
    private ArcusKeyGenerator keyGenerator;

    /*
        using the "testCache" cache with 60 expire seconds and "TEST-PRODUCT" prefix.
    */
    public Product getProduct_TestCache(int id) {
      Product product = cacheManager.getCache("testCache")
              .get(keyGenerator.generate(id), Product.class);
      
      if (product == null) {
        return new Product(id);
      }
      return product;
    }
    
    /*
        using the "devCache" cache with 120 expire seconds and "DEV-PRODUCT" prefix.
    */  
    public Product getProduct_DevCache(int id) {
      Product product = cacheManager.getCache("devCache")
              .get(keyGenerator.generate(id), Product.class);
      
      if (product == null) {
        return new Product(id);
      }
      return product;
    }

    /*
        using the "missingCache" cache with 60 expire seconds and "DEFAULT" prefix.
    */
    public Product getProduct_DefaultCache(int id) {
      Product product = cacheManager.getCache("missingCache")
              .get(keyGenerator.generate(id), Product.class);
      
      if (product == null) {
        return new Product(id);
      }
      return product;
    }
    
}

@jhpark816

#61 PR에 대해 오프라인으로 논의하며 잠시 이 이슈에 대해 이야기 한 적이 있는데요.
정확히 첫번째 코멘트와 동일한 형태의 구현은 아니어도, Object generate(Object... params) 형태의 인터페이스가 필요하다는 것에는 동의를 했던 기억이 납니다.
이 인터페이스의 추가 작업을 진행하도록 할까요?

@oliviarla 이에 대해서는 어떻게 생각하나요?

@uhm0311 @oliviarla

Spring에서 KeyGenerator interface 살펴보니, 아래 method만 정의되어 있습니다.

  • public Object generate(Object target, Method method, Object... params)

KeyGenerator 구현한 SimpleKeyGenerator 확인해 보니 2가지 메소드가 있습니다.

  • public Object generate(Object target, Method method, Object... params)
  • public static Object generateKey(Object... params)

위에 언급한 기능은 generateKey() 메소드로 제공하는 것이 좋겠습니다.

@jhpark816 님이 말씀하신 것 처럼 public static Object generateKey(Object... params) 메서드를 제공하면 될 것 같습니다.

@uhm0311 진행해 주시죠.