简体   繁体   English

执行多个操作和提交

[英]Perform multiple operations and commit

A system handles two types of resources. 系统处理两种类型的资源。 There are write and delete APIs for managing the resources. 有用于管理资源的写入和删除API。 A client (user) will use a library API to manage these resources. 客户端(用户)将使用库API来管理这些资源。 Each resource write (or create) will result in updating a store or a database. 每个资源写入(或创建)将导致更新商店或数据库。

The API would look like: API看起来像:

1) Create Library client. 1)创建库客户端。 The user will use the returned client to operate on the resources. 用户将使用返回的客户端来操作资源。

MyClient createClient(); //to create the client

2) MyClient interface. 2)MyClient接口。 Providing operations on a resource 提供资源操作

writeResourceType1(id);
deleteResourceType1(id);

writeResourceType2(id);
deleteResourceType2(id);

Some resources are dependent on the other. 一些资源依赖于另一个。 The user may write them out-of-order (might write a resource before writing its dependent). 用户可以无序地写入它们(可能在写入其依赖之前写入资源)。 In order to prevent the system from having an inconsistent state, all changes (resource updates) will be written to a staging location. 为了防止系统处于不一致状态,所有更改(资源更新)都将写入暂存位置。 The changes will be written to the actual store only when the user indicates he/she has written everything. 当用户表明他/她已写入所有内容时,更改才会写入实际商店。

This means I would need a commit kind of method in the above MyClient interface. 这意味着我需要在上面的MyClient接口中提交一种方法。 So, access pattern will look like 所以,访问模式看起来像

Client client = provider.createClient();
..
client.writeResourceType1(..)

client.writeResourceType1(..)

client.deleteResourceType2(..)

client.commit(); //<----

I'm not comfortable having the commit API in the MyClient interface. MyClientMyClient界面中使用提交API。 I feel it is polluting it and a wrong it is a wrong level of abstraction. 我觉得它污染了它,错误的是它是一个错误的抽象层次。

Is there a better way to handle this? 有没有更好的方法来处理这个?


Another option I thought of is getting all the updates as part of a single call. 我想到的另一个选择是将所有更新作为单个调用的一部分。 This API would act as a Batch API 此API将充当批处理API

writeOrDelete(List<Operations> writeAndDeleteOpsForAllResources)

The downside of this is this the user has to combine all the operations on their end to call this. 这样做的缺点是用户必须将所有操作结合起来调用它。 This is also stuffing too much into a single call. 这也是一次调用过多的问题。 So, I'm not inclined to this approach. 所以,我不倾向于这种方法。

While both ways that you've presented can be viable options, the thing is that at some point in time, the user must somehow say: "Ok, these are are my changes, take them all or leave them". 虽然你提出的两种方式都是可行的选择,但事情是,在某个时间点,用户必须以某种方式说:“好吧,这些是我的改变,把它们全部拿走或留下它们”。 This is exactly what commit is IMO. 这正是IMO提交的内容。 And this alone makes necessary some kind of call that must present in the API. 而这一点只需要在API中必须进行某种调用。

In the first approach that you've presented its obviously explicit, and is done with commit method. 在第一种方法中,您已经明确地表达了它,并且使用了commit方法。 In the second approach its rather implicit and is determined by the content of the list that you pass into writeOrDelete method. 在第二种方法中,它相当隐含并且由您传递给writeOrDelete方法的列表的内容决定。

So in my understanding, commit must exist somehow, but the question is how do you make it less "annoying" :) 所以在我的理解中,提交必须以某种方式存在,但问题是如何使它不那么“烦人”:)

Here are couple of tricks: 这里有几个技巧:

Trick 1: Builder / DSL 技巧1:Builder / DSL

interface MyBuilder {
   MyBuilder addResourceType1(id);
   MyBuilder addResourceType2(id);
   MyBuilder deleteResourceType1/2...();
   BatchRequest build();
}

interface MyClient {
   BatchExecutionResult executeBatchRequest(BatchRequest req);
}

This method is more or less like the second method, however it has a clear way of "adding resources". 这种方法或多或少类似于第二种方法,但它有一种明确的“添加资源”的方法。 A single point of creation (pretty much like MyClient not, just I believe that eventually it will have more methods, so maybe its a good idea to separate. As you stated: "I'm not comfortable having the commit API in the MyClient interface. I feel it is polluting it and a wrong it is a wrong level of abstraction") Additional argument for this approach is that now you know that there is a builder and its an "abstraction to go" in your code that uses this, you don't have to think about passing a reference to the list, think about what happens if someone calls stuff like clear() on this list, and so on and so forth. 单点创建(非常类似于MyClient ,我相信它最终会有更多方法,所以也许它是一个好主意分离。正如你所说:“我不习惯在MyClient接口中使用提交API我认为这是在污染它而错误的是它是一个错误的抽象层次“)这种方法的另一个论点是,现在你知道在你的代码中有一个构建器及其”抽象“,使用它,你不必考虑将引用传递给列表,考虑如果有人在此列表中调用clear()类的内容会发生什么,等等。 The builder has a precisely defined API of what can be done. 构建器具有精确定义的API,可以执行的操作。

In terms of creating the builder: 在创建构建器方面:

You can go with something like Static Utility class or even add a method to MyClient : 你可以使用像Static Utility类这样的东西,甚至可以在MyClient添加一个方法:

// option1

public class MyClientDSL {
   private MyClientDSL {}
   public static MyBuilder createBuilder();
}

// option 2
public interface MyClient {
    MyBuilder newBuilder();
}

References to this approach: JOOQ (they have DSL like this), OkHttp that have builders for Http Requests, Bodies and so forth (decoupled from the OkHttpClient itself). 引用这种方法:JOOQ(他们有像这样的DSL),OkHttp有Http请求,Bodies等构建器(与OkHttpClient本身分离)。

Trick 2: Providing an execution code block 技巧2:提供执行代码块

Now this can be tricky to implement depending on what kind of environment do you run in, but basically an idea is borrowed from Spring: In order to guarantee a transaction while working with databases they provide a special annotation @Transactional that while placed on the methods basically says: "everything inside the method is running in transaction, I'll commit it by myself so that the user won't deal with transactions/commits at all. I'll also roll back upon exception" 现在这可能很难实现,具体取决于你运行的环境类型,但基本上是从Spring借用的一个想法:为了在使用数据库时保证事务,它们提供了一个特殊的注释@Transactional ,同时放在方法上基本上说:“方法中的所有内容都在事务中运行,我将自己提交,以便用户根本不会处理事务/提交。我还会回滚异常”

So in code it looks like: 所以在代码中它看起来像:

class MyBusinessService {
       private MyClient myClient; // injected
         @Transactional
         public void doSomething() {
             myClient.addResourceType1();
             ...
             myClient.addResourceType2();
             ...  
         }
}

Under the hood they should maintain ThreadLocals to make this possible in multithreaded environment, but the point is that the API is clean. 在引擎盖下,他们应该维护ThreadLocals以在多线程环境中实现这一点,但关键是API是干净的。 The method commit might exist but probably won't be used at the most of the cases, leaving alone the really sophisticated scenarios where the user might really "need" this fine-grained control. 方法commit可能存在,但可能不会在大多数情况下使用,只留下用户可能真正“需要”这种细粒度控件的真正复杂的场景。

If you use spring/ any other containter that manages your code, you can integrate it with spring (the technical way of doing this is out of scope of this question, but you get the idea). 如果你使用spring /任何其他管理代码的包含器,你可以将它与spring集成(这样做的技术方法超出了这个问题的范围,但你明白了)。

If not, you can provide the most simplistic way of it: 如果没有,您可以提供最简单的方式:

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;
      }
}

Here is how it looks: 以下是它的外观:

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) {
         ....
       }
    }

In this approach a builder while still defining precisely a set of APIs might not have an "explicit" commit method exposed to the end user. 在这种方法中,构建器在仍然精确定义一组API时可能没有向最终用户公开的“显式”提交方法。 Instead it can have some "package private" method to be used from within the MyClientCommitableBlock class 相反,它可以在MyClientCommitableBlock类中使用一些“包私有”方法

Try if this suits you 试试这是否适合你

Let us have a flag in staging table with column named status 让我们在登台表中有一个标志status列的标志

Status Column values 状态列值

New : Record inserted by user
ReadyForProcessing : Records ready for processing
Completed : Records processed and updated in Actual Store

Add this below method instead of commit() , and once user invokes this method/service, pick up the records which are for this user and which are in status: New and post it into the Actual Store from the staging location 添加以下方法而不是commit() ,并且一旦用户调用此方法/服务,请选取适用于此用户的status: Newstatus: New并从暂存位置将其发布到Actual Store中

client.userUpdateCompleted();

There is another option as well let us take out the client intervention by giving client.commit(); or client.userUpdateCompleted(); 还有另一种选择,让我们通过给予client.commit(); or client.userUpdateCompleted();来取消客户干预client.commit(); or client.userUpdateCompleted(); client.commit(); or client.userUpdateCompleted(); and instead we can have a batch process using Scheduler which runs at specific intervals which scans the Staging Table and populates the meaningful and user update completed records into the Actual Store 而我们可以batch process using Scheduler以特定的时间间隔运行,扫描临时表并将有意义的和用户更新完成的记录填充到实际存储中

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM