Refactor repository structure for Couchbase collections

Introduced AbstractCollectionRepository and AbstractSingletonRepository to clarify repository types and encapsulate logic for single-key and collection-based repositories. Updated AdminRepository, AppleAppRepository, GoogleAppRepository, and GroupRepository to extend the appropriate abstract classes. Removed COMMON_COLLECTION_NAME from Constant.java and enforced collection name restrictions in AbstractRepository.
This commit is contained in:
2025-11-08 00:05:09 +07:00
parent 7349501086
commit c0facd5ba2
8 changed files with 104 additions and 59 deletions
@@ -7,7 +7,6 @@ import java.time.temporal.ChronoUnit;
import java.util.Set;
public class Constant {
public static final String COMMON_COLLECTION_NAME = "common";
public static final long APP_CACHE_SECONDS = 600;
public static final long NUM_DAYS_WARNING_NOT_UPDATED = 30;
public static final LocalTime SCHEDULE_CHECK_APP_TIME = LocalTime.of(7, 0);
@@ -0,0 +1,38 @@
package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.model.AbstractModel;
import lombok.extern.log4j.Log4j2;
/**
* Các repository tương ứng với 1 Couchbase collection, public các method protected ở class
* AbstractRepository để các nơi khác gọi
*/
@Log4j2
public abstract class AbstractCollectionRepository<K, V extends AbstractModel<K>>
extends AbstractRepository<K, V> {
@Override
public void init(K key) {
super.init(key);
}
@Override
public void save(K key, V data) {
super.save(key, data);
}
@Override
public boolean exist(K key) {
return super.exist(key);
}
@Override
public V load(K key) {
return super.load(key);
}
@Override
public void delete(K key) {
super.delete(key);
}
}
@@ -1,5 +1,7 @@
package com.miti99.storescraperbot.repository;
import static com.miti99.storescraperbot.repository.AbstractSingletonRepository.COMMON_COLLECTION_NAME;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.kv.UpsertOptions;
import com.google.common.base.CaseFormat;
@@ -10,9 +12,13 @@ import java.lang.reflect.ParameterizedType;
import java.time.Duration;
import lombok.extern.log4j.Log4j2;
/**
* @param <K> class Key
* @param <V> class Value
*/
@Log4j2
public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
public static final String SEPARATOR = "_";
protected static final String SEPARATOR = "_";
// protected final Class<K> classK = getKeyClass();
protected final Class<V> classV = getDataClass();
protected final String scopeName = Environment.ENV.name().toLowerCase();
@@ -25,6 +31,10 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
protected AbstractRepository() {
collectionName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, classV.getSimpleName());
if (COMMON_COLLECTION_NAME.equals(collectionName)) {
throw new RuntimeException(
"Collection named '%s' is reserved".formatted(COMMON_COLLECTION_NAME));
}
CouchbaseUtil.createCollection(scopeName, collectionName);
}
@@ -35,8 +45,7 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
/**
* Lấy ra class của V. Khi tạo 1 abstract class extends AbstractRepository mà không phải final thì
* sẽ cần override hàm này phù hợp<br>
* Chi tiết có thể tìm hiểu thêm về getGenericSuperclass và getParameterizedClass<br>
* sẽ cần override hàm này phù hợp
*
* @return Class&lt;V&gt;
*/
@@ -56,7 +65,7 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
return 0;
}
public void init(K key) {
protected void init(K key) {
try {
if (exist(key)) {
return;
@@ -73,7 +82,7 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
return String.valueOf(key);
}
public void save(K key, V data) {
protected void save(K key, V data) {
var databaseKey = getDatabaseKey(key);
try {
if (getExpireSeconds() == 0) {
@@ -88,7 +97,7 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
}
}
public boolean exist(K key) {
protected boolean exist(K key) {
var databaseKey = getDatabaseKey(key);
try {
return collection().exists(databaseKey).exists();
@@ -98,7 +107,7 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
}
}
public V load(K key) {
protected V load(K key) {
var databaseKey = getDatabaseKey(key);
try {
var getResult = collection().get(databaseKey);
@@ -112,7 +121,7 @@ public abstract class AbstractRepository<K, V extends AbstractModel<K>> {
}
}
public void delete(K key) {
protected void delete(K key) {
var databaseKey = getDatabaseKey(key);
try {
collection().remove(databaseKey);
@@ -0,0 +1,43 @@
package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.model.AbstractModel;
import lombok.extern.log4j.Log4j2;
/**
* Repository chỉ chứa 1 key duy nhất, public các method liên quan nhưng không cho truyền params
* vào. Các repository loại này được lưu trong 1 collection duy nhất là "common" (do Couchbase có
* giới hạn số lượng collection mỗi cluster nên gom nhóm các repository loại này lại)
*/
@Log4j2
public abstract class AbstractSingletonRepository<K, V extends AbstractModel<K>>
extends AbstractRepository<K, V> {
public static final String COMMON_COLLECTION_NAME = "common";
protected final K key = getKey();
protected AbstractSingletonRepository() {
super(COMMON_COLLECTION_NAME);
}
protected abstract K getKey();
public void init() {
init(key);
}
public void save(V data) {
save(key, data);
}
public boolean exist() {
return exist(key);
}
public V load() {
return load(key);
}
public void delete() {
delete(key);
}
}
@@ -1,57 +1,13 @@
package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.constant.Constant;
import com.miti99.storescraperbot.model.Admin;
/**
* Đây là repository chỉ chứa 1 key duy nhất. Nên lưu trong default collection của Couchbase
* ("_default")
*
* <p>TODO: Refactor các logic của một single key repository sang abstract class để dễ hiểu hơn.
* Code hiện tại này chỉ là trick tạm thời nên chưa tốt lắm. Cần thiết kế lại tốt hơn
*/
public class AdminRepository extends AbstractRepository<String, Admin> {
public class AdminRepository extends AbstractSingletonRepository<String, Admin> {
public static final AdminRepository INSTANCE = new AdminRepository();
public static final String KEY = "admin";
public AdminRepository() {
super(Constant.COMMON_COLLECTION_NAME);
}
@Override
public void init(String key) {
super.init(KEY);
}
@Override
public void save(String key, Admin data) {
super.save(KEY, data);
}
@Override
public boolean exist(String key) {
return super.exist(KEY);
}
@Override
public Admin load(String key) {
return super.load(KEY);
}
@Override
public void delete(String key) {
super.delete(KEY);
}
public void init() {
init(KEY);
}
public Admin load() {
return load(KEY);
}
public void save(Admin data) {
save(KEY, data);
protected String getKey() {
return KEY;
}
}
@@ -3,7 +3,7 @@ package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.constant.Constant;
import com.miti99.storescraperbot.model.AppleApp;
public class AppleAppRepository extends AbstractRepository<String, AppleApp> {
public class AppleAppRepository extends AbstractCollectionRepository<String, AppleApp> {
public static final AppleAppRepository INSTANCE = new AppleAppRepository();
@Override
@@ -3,7 +3,7 @@ package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.constant.Constant;
import com.miti99.storescraperbot.model.GoogleApp;
public class GoogleAppRepository extends AbstractRepository<String, GoogleApp> {
public class GoogleAppRepository extends AbstractCollectionRepository<String, GoogleApp> {
public static final GoogleAppRepository INSTANCE = new GoogleAppRepository();
@Override
@@ -2,6 +2,6 @@ package com.miti99.storescraperbot.repository;
import com.miti99.storescraperbot.model.Group;
public class GroupRepository extends AbstractRepository<Long, Group> {
public class GroupRepository extends AbstractCollectionRepository<Long, Group> {
public static final GroupRepository INSTANCE = new GroupRepository();
}