如何基于readOnlyAndLog选项复用服务,实现CRUD执行与仅日志的逻辑分离?
资源同步服务的复用方案与逻辑分离实践
核心思路
要复用同一服务适配「实际执行CRUD」和「只读日志模拟」两种场景,核心是把「业务决策逻辑」和「具体执行逻辑」完全拆分:
- 业务决策逻辑:负责对比JSON资源与数据库数据,计算出需要创建、保留、删除的资源集合,这部分逻辑完全通用,不需要改动。
- 执行逻辑:分两种实现——一种对接数据库执行真实CRUD,另一种仅输出日志模拟操作,通过切换执行器来适配不同场景。
具体实现步骤
1. 定义执行器接口
先抽象出执行操作的统一接口,包含业务需要的所有操作类型:
public interface SubjectSyncExecutor { // 创建资源 void create(Subject subject); // 删除资源 void delete(Subject subject); // 记录已存在的资源 void logExisting(Subject subject); }
2. 实现两种执行器
- 数据库执行器(真实CRUD场景):
public class DbSubjectSyncExecutor implements SubjectSyncExecutor { private final SubjectRepository subjectRepo; // 通过构造注入数据库仓库 public DbSubjectSyncExecutor(SubjectRepository subjectRepo) { this.subjectRepo = subjectRepo; } @Override public void create(Subject subject) { subjectRepo.save(subject); // 可选:同时记录操作日志 System.out.println("已创建资源:" + subject.getName()); } @Override public void delete(Subject subject) { subjectRepo.delete(subject); System.out.println("已删除资源:" + subject.getName()); } @Override public void logExisting(Subject subject) { System.out.println(subject.getName() + " 已存在于数据库"); } }
- 日志模拟执行器(只读日志场景):
public class LogOnlySubjectSyncExecutor implements SubjectSyncExecutor { @Override public void create(Subject subject) { System.out.println(subject.getName() + " will be created"); } @Override public void delete(Subject subject) { System.out.println(subject.getName() + " will be deleted"); } @Override public void logExisting(Subject subject) { System.out.println(subject.getName() + " is already defined in the DB"); } }
3. 编写核心同步服务(复用决策逻辑)
核心服务只负责业务决策,具体执行交给注入的执行器:
public class SubjectSyncService { private final SubjectJsonLoader jsonLoader; private final SubjectRepository subjectRepo; private final SubjectSyncExecutor executor; // 构造注入所有依赖,包括执行器 public SubjectSyncService(SubjectJsonLoader jsonLoader, SubjectRepository subjectRepo, SubjectSyncExecutor executor) { this.jsonLoader = jsonLoader; this.subjectRepo = subjectRepo; this.executor = executor; } public void sync() { // 1. 加载JSON中的资源并转成内部模型 List<Subject> jsonSubjects = jsonLoader.loadFromRootJson(); Set<String> jsonSubjectNames = jsonSubjects.stream() .map(Subject::getName) .collect(Collectors.toSet()); // 2. 查询数据库中已有的资源 List<Subject> dbSubjects = subjectRepo.findAll(); Set<String> dbSubjectNames = dbSubjects.stream() .map(Subject::getName) .collect(Collectors.toSet()); // 3. 计算需要创建的资源(JSON有,数据库无) List<Subject> toCreate = jsonSubjects.stream() .filter(subject -> !dbSubjectNames.contains(subject.getName())) .collect(Collectors.toList()); // 4. 计算需要删除的资源(数据库有,JSON无) List<Subject> toDelete = dbSubjects.stream() .filter(subject -> !jsonSubjectNames.contains(subject.getName())) .collect(Collectors.toList()); // 5. 计算已存在的资源(两边都有) List<Subject> existing = jsonSubjects.stream() .filter(subject -> dbSubjectNames.contains(subject.getName())) .collect(Collectors.toList()); // 6. 交给执行器处理(核心:无论哪种场景,决策逻辑完全复用) toCreate.forEach(executor::create); toDelete.forEach(executor::delete); existing.forEach(executor::logExisting); } }
4. 场景切换
通过注入不同的执行器来切换场景:
// 实际同步场景:注入数据库执行器 SubjectSyncService realSyncService = new SubjectSyncService( new SubjectJsonLoader(), new SubjectDbRepository(), new DbSubjectSyncExecutor(new SubjectDbRepository()) ); realSyncService.sync(); // 只读日志场景:注入日志模拟执行器 SubjectSyncService logOnlySyncService = new SubjectSyncService( new SubjectJsonLoader(), new SubjectDbRepository(), new LogOnlySubjectSyncExecutor() ); logOnlySyncService.sync();
此类服务逻辑分离的最佳实践
- 接口抽象与依赖注入:通过抽象执行器接口,让核心服务不依赖具体实现,而是通过构造注入执行器,实现解耦,方便灵活切换场景。
- 单一职责原则:每个模块只负责一件事:
SubjectJsonLoader:仅负责加载JSON并转成内部模型;SubjectSyncService:仅负责业务决策(计算操作集合);- 执行器:仅负责具体的操作(CRUD或日志)。
- 配置驱动切换:可以通过配置文件(如
application.yml中的sync.mode=read-only)配合框架自动配置,实现无需修改代码即可切换场景。 - 可测试性优化:因为执行器是接口,单元测试时可以用Mock执行器,无需操作真实数据库,就能单独测试核心决策逻辑的正确性。
- 避免逻辑冗余:所有业务决策逻辑只写一次,两种场景共享,避免重复代码导致的维护成本。
内容的提问来源于stack exchange,提问作者webdudewithnoname
相关产品推荐
相关产品推荐

