系统处理两种类型的资源。有用于管理资源的写入和删除API。客户端(用户)将使用库API来管理这些资源。每次写入(或创建)资源都会导致更新商店或数据库。
API如下所示:
1)创建库客户端。用户将使用返回的客户端来操作资源。
MyClient createClient(); //to create the client
2)MyClient界面。提供对资源的操作
writeResourceType1(id);
deleteResourceType1(id);
writeResourceType2(id);
deleteResourceType2(id);
某些资源依赖于另一资源。用户可能会无序地写入它们(可能在写入依赖项之前先写入资源)。为了防止系统状态不一致,所有更改(资源更新)都将写入到暂存位置。当用户指示他/她已经写完所有内容时,所做的更改将仅写到实际的商店中。
这意味着我需要在上述MyClient
界面中使用 commit 这种方法。因此,访问模式看起来像
Client client = provider.createClient();
..
client.writeResourceType1(..)
client.writeResourceType1(..)
client.deleteResourceType2(..)
client.commit(); //<----
我不愿意在MyClient
界面中使用提交API。我觉得它在污染它,错误的是错误的抽象级别。
是否有更好的方法来处理?
我想到的另一种选择是作为单个呼叫的一部分获得全部更新。该API将充当批处理API
writeOrDelete(List<Operations> writeAndDeleteOpsForAllResources)
这的缺点是用户必须结合其末端的所有操作才能调用此操作。单个调用中也塞满了太多内容。因此,我不倾向于这种方法。
答案 0 :(得分:10)
尽管您介绍的两种方式都是可行的选择,但事实是,在某个时间点,用户必须以某种方式说:“好吧,这些是我的更改,全部接受或放弃。”这正是IMO的提交。 而且仅此一项就必须进行某种必须在API中进行的调用。
在第一种方法中,您已经展示了其明显的显式方法,并且是通过commit
方法完成的。
在第二种方法中,它相当隐式,并且由您传递到writeOrDelete
方法中的列表的内容确定。
因此,据我所知,提交必须以某种方式存在,但问题是如何使它减少“烦人”:)
以下是一些技巧:
技巧1:Builder / DSL
interface MyBuilder {
MyBuilder addResourceType1(id);
MyBuilder addResourceType2(id);
MyBuilder deleteResourceType1/2...();
BatchRequest build();
}
interface MyClient {
BatchExecutionResult executeBatchRequest(BatchRequest req);
}
此方法或多或少类似于第二种方法,但是它具有“添加资源”的明确方法。单一的创建点(几乎与MyClient
类似,只是我相信最终它将有更多的方法,所以也许将其分离是一个好主意。正如您所说:“我不愿意拥有commit API在MyClient界面中。我感觉它正在污染它,而错误则是错误的抽象级别”)
这种方法的另一个论点是,现在您知道在使用此方法的代码中存在一个生成器及其“抽象”,您无需考虑将引用传递给列表,无需考虑如果有人在此列表中称呼clear()
之类的东西,依此类推。构建器具有精确定义的API,可以完成哪些工作。
关于创建构建器:
您可以使用类似Static Utility类的东西,甚至可以向MyClient
添加方法:
// option1
public class MyClientDSL {
private MyClientDSL {}
public static MyBuilder createBuilder();
}
// option 2
public interface MyClient {
MyBuilder newBuilder();
}
此方法的参考:JOOQ(它们具有DSL这样的功能),OkHttp,它们具有Http请求的构建器,实体等等(与OkHttpClient本身分离)。
技巧2:提供执行代码块
根据您所运行的环境,现在实施起来可能很棘手,
但基本上从Spring借来了一个主意:
为了在使用数据库时保证事务,他们提供了一个特殊的注释@Transactional
,该注释放在方法上时基本上说:“方法中的所有内容都在事务中运行,我将自己提交它,以便用户完全不会处理事务/提交。我还会在出现异常时回滚“
所以在代码中看起来像:
class MyBusinessService {
private MyClient myClient; // injected
@Transactional
public void doSomething() {
myClient.addResourceType1();
...
myClient.addResourceType2();
...
}
}
在后台,他们应该维护ThreadLocals才能在多线程环境中实现这一点,但重点是该API很干净。方法commit
可能存在,但在大多数情况下可能不会使用,更不用说用户可能真的需要“细粒度”控件的复杂场景了。
如果您使用spring /来管理代码的任何其他容器,则可以将其与spring集成(执行此操作的技术方法不在此问题的范围内,但您可以理解)。
如果没有,您可以提供最简单的方法:
public class MyClientCommitableBlock {
public static <T> T executeInTransaction(CodeBlock<T> someBlock)
builder) {
MyBuilder builder = create...;
T result = codeBlock(builder);
// build the request, execute and commit
return result;
}
}
这是它的外观:
static import MyClientCommitableBlock.*;
public static void main() {
Integer result = executeInTransaction(builder -> {
builder.addResourceType1();
...
return 42;
});
}
// or using method reference:
class Bar {
Integer foo() {
return executeInTransaction(this::bar);
}
private Integer bar(MyBuilder builder) {
....
}
}
在这种方法中,构建器在仍然精确定义一组API的情况下,可能不会向最终用户公开“显式”提交方法。相反,它可以在MyClientCommitableBlock
类中使用某些“打包私有”方法
答案 1 :(得分:0)
尝试一下是否适合您
让我们在登台表中有一个标记为status
的列
状态列值
New : Record inserted by user
ReadyForProcessing : Records ready for processing
Completed : Records processed and updated in Actual Store
添加以下方法而不是commit()
,并且一旦用户调用此方法/服务,请提取status: New
中该用户的记录,并将其从以下位置发布到实际存储中暂存位置
client.userUpdateCompleted();
还有另一种选择,我们可以通过给client.commit(); or client.userUpdateCompleted();
来消除客户的干预,相反,我们可以有一个batch process using Scheduler
,它以特定的间隔运行,扫描暂存表并填充有意义的用户将完成的记录更新到实际存储中