Doubts about Design Patterns - Idempotency Messages

3

I'm trying to create an application sample that can call a function / method and after a certain time it checks whether the process has already been executed, if not, call the same function / method again and check the previous progress if it is running or if it is finished and return the process.

I've tried a lot, but I have not found an example that can be followed. The links below describe what Design Patterns - Idempotency Messages actually does.

link

link

I'm trying this way;

using System;

namespace Idempotent_Messages
{
    public class Program
    {
        static IdePotencyHelper idePotencyHelper = new IdePotencyHelper();
        public static int passo = 1;

        static void Main(string[] args)
        {          
            var func = new Funcionario();
            var command = new CqrsBase<Int32, Funcionario>();
            command.Execute(func);

            command.IdPotency((f) =>
            {
                var funcBanco = new Funcionario();
                funcBanco.Id = 1;

                return f.Id == funcBanco.Id;
            },
            (x)=> { return x.Id; }
            , func);
        }
    }

    public class Funcionario
    {
        public int Id { get; set; }
    }

    public class CqrsBase<T, TArgs> 
    {
        public T Execute(TArgs args)
        {
            return default(T);
        }
    }

    public static class CqrsBase
    {
        public static TReturn IdPotency<TReturn, TArgs>(this CqrsBase<TReturn, TArgs> cqrs, Func<TArgs,bool> verify, Func<TArgs, TReturn> existsFunc, TArgs args)
        {
            var existe = verify(args);
            if (existe)
            {
                return existsFunc(args);
            }
            else
            {
                return cqrs.Execute(args);
            }
        }
    }
}

Design Patterns - Idempotency

An operation is idempotent if it does not have additional effects if it is called more than once with the same parameters. You can not control how your public API is called by your clients, so you should make sure that it does not lead to any unwanted effects if they repeat your calls over and over again.

A common solution is to provide a correlation ID for any operation potentially changed by the state. The service checks in some repository if the request has been processed. If yes, the previously stored response must be provided.

Here is a flowchart that shows communication with a correlation ID:

Image copied from site

The problem is that I do not know how to create an identifier for each process to store its status and after the determiner time check the process by the identifier.

    
asked by anonymous 14.07.2017 / 21:31

1 answer

6

This function does not do what you specified, but is closer to reaching your goal. It tries to execute the action function a certain number of times. If the action completes within the specified time then the value is returned. Adapt the feature to meet your additional requirement to complete a previous call.

static async Task<T> Retry<T>(Func<T> action, TimeSpan timeout, int? attempts = 3){
    for(int i = 0; i < attempts; ++i){
        var task = Task.Factory
            .FromAsync(action.BeginInvoke(null, null), r => action.EndInvoke(r));
        var timeoutTask = Task.Delay(timeout);
        var completedTask = await Task.WhenAny(task, timeoutTask);
        if(completedTask == task){
            return task.Result; 
        }
    }
    throw new InvalidOperationException(
        $"Was not able to execute the task within {attempts} attempts");
}

To use:

Retry(() => 4, TimeSpan.FromSeconds(1)).Result.Dump();
Retry(() => 4, TimeSpan.FromMilliseconds(1)).Result.Dump();
Retry(() => {Thread.Sleep(500); return 4;}, TimeSpan.FromMilliseconds(500)).Result.Dump();
Retry(() => {Thread.Sleep(500); return 4;}, TimeSpan.FromMilliseconds(1)).Result.Dump();

Requests for clarification of comments:

  

How can I identify which action is being performed

A task is running if its status is equal to Running

var task = Task.Delay(500);
task.Status == TaskStatus.Running // está a ser executada

Another less correct way is to assume that your Task is running until it completes await whether it exits by exception, or by complete process

var task = Task.Delay(500);
await task; //a task está a ser executada até á próxima instrucao
Debug.Assert(task.Status == TaskStatus.RanToCompletion) // a task completou

So checking to see if a process has finished in 10 minutes can be done as follows:

async Task<bool> Timeout(Action action, TimeSpan timeout){
    var task = Task.Factory
        .FromAsync(action.BeginInvoke(null, null), r => action.EndInvoke(r));
    var timeoutTask = Task.Delay(timeout);
    var completedTask = await Task.WhenAny(task, timeoutTask);
    return completedTask == task;
}

Timeout(()=> Thread.Sleep(TimeSpan.FromSeconds(5)), TimeSpan.FromMinutes(10)).Result.Dump();

If you watched, you saw that the code is similar to the code I showed earlier for Retry .

  

I just want to run again if I did not have anything I was doing   of that same action

I do not know what that means. If you had a task executed then, from the moment it enters the TaskStatus.Running state, it has already executed something.

In other words, you will never have to execute anything again because something has already been executed.

It seems that the question has changed a lot, but criticism aside ...

  

The problem is that I do not know how to create an identifier for   each process store its status and after the determiner time   verify the process by the identifier.

The only way I know of solving this problem is to create an identifier when creating the process and provide this identifier to the API consumer.

Minimalist example:

/*********** API ***********/
private Dictionary<string, Task<int>> tarefas = new Dictionary<string, Task<int>>();
private Random r = new Random();

private async Task<int> Tarefa(){
    await Task.Delay(TimeSpan.FromSeconds(5));
    return r.Next();
}

public string CriaTarefa(){
    var id = Guid.NewGuid().ToString();
    tarefas.Add(id, Tarefa());
    return id;
}

public int? VerificaTarefa(string id){
    Task<int> tarefa;
    if(!tarefas.TryGetValue(id, out tarefa)){
        return null;
    }
    if(!tarefa.IsCompleted){
        return null;
    }
    return tarefa.Result;
}

/*********** Cliente ***********/
var id = CriaTarefa();

while(VerificaTarefa(id) == null){
    Thread.Sleep(500);
}

VerificaTarefa(id).Dump();

There are better techniques that prevent polling from the operation

Instead of checking the process, what you should do is create an event that allows the client to be notified.

/*********** API ***********/   
public class Tarefa{
    public event EventHandler<int> OperationCompleted;
    private Random r = new Random();

    public async void CriaTarefa(){
        await Task.Delay(TimeSpan.FromSeconds(5));
        OperationCompleted?.Invoke(this, r.Next());
    }

}

/*********** Cliente ***********/
var tarefa = new Tarefa();
tarefa.OperationCompleted += (ctx, r) => r.Dump();
tarefa.CriaTarefa();
Thread.Sleep(TimeSpan.FromSeconds(6));
    
15.07.2017 / 12:30