Выполните несколько операций и зафиксируйте - PullRequest
5 голосов
/ 14 июня 2019

Система обрабатывает два типа ресурсов. Существуют API записи и удаления для управления ресурсами. Клиент (пользователь) будет использовать библиотечный API для управления этими ресурсами. Каждая запись (или создание) ресурса приведет к обновлению магазина или базы данных.

API будет выглядеть так:

1) Создать клиент библиотеки. Пользователь будет использовать возвращенный клиент для работы с ресурсами.

MyClient createClient(); //to create the client

2) Интерфейс MyClient. Предоставление операций на ресурсе

writeResourceType1(id);
deleteResourceType1(id);

writeResourceType2(id);
deleteResourceType2(id);

Некоторые ресурсы зависят от других. Пользователь может написать их не по порядку (может написать ресурс, прежде чем писать его зависимый). Чтобы система не имела несовместимого состояния, все изменения (обновления ресурсов) будут записаны в промежуточное местоположение. Изменения будут записаны в фактическое хранилище только , когда пользователь укажет, что он все написал.

Это означает, что мне понадобится метод commit в указанном выше интерфейсе MyClient. Таким образом, шаблон доступа будет выглядеть как

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

client.writeResourceType1(..)

client.deleteResourceType2(..)

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

Мне не комфортно иметь API фиксации в интерфейсе MyClient. Я чувствую, что это загрязняет и неправильно, это неправильный уровень абстракции.

Есть ли лучший способ справиться с этим?


Другой вариант, о котором я подумал, - это все обновлений в рамках одного вызова Этот API будет действовать как Batch API

writeOrDelete(List<Operations> writeAndDeleteOpsForAllResources)

Недостатком этого является то, что пользователь должен объединить все операции на своем конце, чтобы вызвать это. Это также вбивает слишком много в один звонок. Так что я не склонен к такому подходу.

Ответы [ 2 ]

10 голосов
/ 17 июня 2019

Хотя оба способа, которые вы представили, могут быть жизнеспособными, дело в том, что в какой-то момент пользователь должен как-то сказать: «Хорошо, это мои изменения, примите их все или оставьте». Это именно то, что делает IMO. И это само по себе делает необходимым какой-то вызов, который должен присутствовать в API.

В первом подходе, который вы представили, он явно явный и выполняется методом commit. Во втором подходе он довольно неявный и определяется содержимым списка, который вы передаете методу writeOrDelete.

Так что в моем понимании коммит должен как-то существовать, но вопрос в том, как сделать его менее "раздражающим":)

Вот несколько хитростей:

Трюк 1: Строитель / DSL

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

interface MyClient {
   BatchExecutionResult executeBatchRequest(BatchRequest req);
}

Этот метод более или менее похож на второй метод, однако он имеет четкий способ «добавления ресурсов». Единственная точка создания (почти как MyClient нет, просто я верю, что со временем у нее будет больше методов, так что, возможно, это хорошая идея для разделения. Как вы сказали: «Мне неудобно иметь 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 / любой другой контейнер, который управляет вашим кодом, вы можете интегрировать его с пружиной (технический способ сделать это выходит за рамки этого вопроса, но вы поняли идею).

Если нет, вы можете предоставить наиболее простой способ:

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, может не быть «явного» метода фиксации, доступного для конечного пользователя. Вместо этого он может иметь некоторый метод "package private", который будет использоваться из MyClientCommitableBlock class

0 голосов
/ 14 июня 2019

Попробуйте, если это вам подходит

Пусть в промежуточной таблице есть флаг со столбцом с именем status

Значения столбца состояния

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

Добавьте этот метод ниже вместо commit(), и, как только пользователь вызовет этот метод / службу, выберите записи, предназначенные для этого пользователя и находящиеся в status: New, и отправьте их в хранилище Actual из места размещения * 1013.*

client.userUpdateCompleted();

Существует также другая опция, позволяющая нам исключить вмешательство клиента, дав client.commit(); or client.userUpdateCompleted();, и вместо этого мы можем получить batch process using Scheduler, который выполняется через определенные интервалы, который сканирует промежуточную таблицу и заполняет значимое и пользовательское значение.обновить заполненные записи в Actual Store

...