Provided by: librinci-perl_1.1.93-1_all bug

NAME

       Rinci::Undo - (DEPRECATED) Protocol for undo operations in functions

SPECIFICATION VERSION

        1.1

VERSION

       This document describes version 1.1.93 of Rinci::Undo (from Perl distribution Rinci), released on
       2019-07-19.

STATUS

       This protocol (riundo for short) is now deprecated in favor of Rinci::Transaction (ritx for short) for
       several reasons:

       •   riundo is inherently unreliable

           Undo  information  is  returned  by function after the function has performed the action. If function
           dies in the middle of action, client does not have the information to undo the (partially  completed)
           action.  That  is  why in ritx, the TM asks the function first for undo information before asking the
           function to perform its action.

       •   ritx does not limit using the same function for undo

           In riundo, we must call the same function (passing the previously obtained undo data  from  the  that
           function)  to  undo  the information. This is sometimes slightly cumbersome. The undo action might be
           provided by other functions, but we still have to go through the same function first.

       •   ritx can also implement undo/redo

           So there is no need for maintaining two specifications.

SPECIFICATION

       This document describes the Rinci undo protocol. This protocol must be followed by functions  that  claim
       that  they support undo (have their "undo" "feature" set to true). Such functions are from here on called
       undoable function (or just function, unless when ambiguous).

       The protocol is basically the non-OO version of the command pattern, a design pattern most commonly  used
       to implement undo/redo functionality. In this case, each function behaves like a command object. You pass
       a  special  argument  "-undo_action"  with  the  value  of  "do" and "undo" to execute or undo a command,
       respectively. For "do" and "undo", the same set of arguments are passed.

   Requirements
       Function MUST check special argument "-undo_action" before it checks other arguments.  Function  MUST  at
       least  support the following undo action: "do", "undo". On unsupported/unknown undo action, function MUST
       return status 400, with message like "Unsupported undo action".

       If "-undo_action" is not set, it means caller does not care about undo.  Undoable function should execute
       as any normal function.

   Performing 'do'
       To indicate that we need undo, we call function by passing special argument "-undo_action" with the value
       of "do". Function should perform its operation and save undo data along the way. If "-undo_action" is not
       passed or false/undef, function should assume that caller does not need undo later, so function need  not
       save  any  undo  data.  After  completing  operation successfully, function should return status 200, the
       result, and undo data. Undo data is returned in  the  result  metadata  (the  fourth  element  of  result
       envelope), example:

        [200, "OK", $result, {undo_data=>$undo_data}]

       Undo  data  should  be  serializable  so  it  is  easy  to  be made persistent if necessary (e.g. by some
       undo/transaction manager).

   Performing 'undo'
       To perform an undo, caller must call  the  function  again  with  the  same  previous  arguments,  except
       "-undo_action"  should  be  set  to  "undo"  and  "-undo_data"  set  to undo data previously given by the
       function. Function should perform the undo operation using the undo data. Upon success,  it  must  return
       status  200,  the  result,  and an undo data (in other words, redo data, since it can be used to undo the
       undo operation).

   Performing 'redo'
       To perform redo, caller can call the function again with <-undo_action> set to  "undo"  and  "-undo_data"
       set  to the redo data given in the undo step. Or, alternatively, caller can just perform a normal do (see
       above).

       An example:

        $SPEC{setenv} = {
            v => 1.1,
            summary  => 'Set environment variable',
            args     => {
                name  => {req=>1, schema=>'str*'},
                value => {req=>1, schema=>'str*'},
            },
            features => {undo=>1},
        };
        sub setenv {
            my %args        = @_;
            my $name        = $args{name};
            my $value       = $args{value};
            my $undo_action = $args{-undo_action} // '';
            my $undo_data   = $args{-undo_data};

            my $old;
            if ($undo_action) {
                # save original value and existence state
                $old = [exists($ENV{$name}), $ENV{$name}];
            }

            if ($undo_action eq 'undo') {
                if ($undo_data->[0]) {
                    $ENV{$name} = $undo_data->[1];
                } else {
                    delete $ENV{$name};
                }
            } else {
                $ENV{$name} = $value;
            }

            [200, "OK", undef, $undo_action ? {undo_data=>$old} : {}];
        }

       The above example declares an undoable command "setenv" to set an environment variable (%ENV).

       To perform command:

        my $res = setenv(name=>"DEBUG", value=>1, -undo_action=>"do");
        die "Failed: $res->[0] - $res->[1]" unless $res->[0] == 200;
        my $undo_data = $res->[3]{undo_data};

       To perform undo:

        $res = setenv(name=>"DEBUG", value=>1,
                      -undo_action="undo", -undo_data=>$undo_data);
        die "Can't undo: $res->[0] - $res->[1]" unless $res->[0] == 200;

       After this undo, DEBUG environment variable  will  be  set  to  original  value.  If  it  did  not  exist
       previously, it will be deleted.

       To perform redo:

        my $redo_data = $res->[3]{undo_data};
        $res = setenv(name=>"DEBUG", value=>1,
                      -undo_action="undo", -undo_data=>$redo_data);

       or you can just do:

        $res = setenv(name=>"DEBUG", value=>1, -undo_action="do");

   Saving undo data in external storage
       Although  the  complete  undo  data  can  be  returned by the function in the "undo_data" result metadata
       property, sometimes it is more efficient to just return a pointer to said undo  data,  while  saving  the
       actual undo data in some external storage.

       For  example,  if a function deletes a big file and wants to save undo data, it is more efficient to move
       the file to trash directory and return its path as the undo data,  instead  of  reading  the  whole  file
       content and its metadata to memory and return it in "undo_data" result metadata.

       Functions  which  require  undo  trash  directory  should  specify  this  in  its  metadata,  through the
       "undo_trash_dir" dependency clause. For example:

        deps => {
            ...
            trash_dir => 1,
        }

       When calling function, caller needs to  provide  path  to  undo  trash  directory  via  special  argument
       "-trash_dir", for example:

        -trash_dir => "/home/.trash/2fe2f4ad-a494-0044-b2e0-94b2b338056e"

   What about non-undoable actions?
       Like in real life, not all actions are undoable. Examples of undoable/irreversible actions include wiping
       a  file/directory  (more  generally speaking, any action to permanently delete/destroy something, without
       backing up the data first), sending an email (more generally speaking, any action  that  is  sent  to  an
       external entity beyond our control, unless that external entity provides a way to undo the action).

       An undoable function MUST NOT mix undoable and non-undoable actions. For example:

        safe_delete(file=>'/path/to/file'); # puts file into Trash, undoable action
        safe_delete(file=>'/path/to/file', permanent=>1); # deletes file, non-undoable

       The  "safe_delete"  function  above  mixes undoable action (putting a file into Trash directory) and non-
       undoable action (permanently deleting a file without putting it in Trash). Without  domain  knowledge  of
       the  function,  a  caller  cannot know whether a call will be undoable or not. This will also prevent the
       function from participating in a transaction, because transaction requires function  call  to  always  be
       undoable, for rollback purpose.

       The solution is to separate non-undoable action in another function, for example:

        trash(file=>'/path/to/file');  # undoable, can execute inside transaction
        delete(file=>'/path/to/file'); # non-undoable, executes outside transaction
        empty_trash();                 # non-undoable, executes outside transaction

       The non-undoable function is also non-transactional (it operates outside the scope of a transaction). But
       it  can  still be idempotent. And it can manipulate the transactions if it needs too. In the example, the
       empty_trash() function instructs the transaction manager to discard the trash() transactions, since after
       the trash is emptied, the trash() transactions cannot be undone anyway.

HOMEPAGE

       Please visit the project's homepage at <https://metacpan.org/release/Rinci>.

SOURCE

       Source repository is at <https://github.com/perlancar/perl-Rinci>.

BUGS

       Please    report    any    bugs     or     feature     requests     on     the     bugtracker     website
       <https://rt.cpan.org/Public/Dist/Display.html?Name=Rinci>

       When  submitting  a  bug  or request, please include a test-file or a patch to an existing test-file that
       illustrates the bug or desired feature.

SEE ALSO

       Related specifications: Rinci::Transaction

AUTHOR

       perlancar <perlancar@cpan.org>

COPYRIGHT AND LICENSE

       This software is copyright (c) 2019, 2018, 2017, 2016, 2015, 2014, 2013, 2012 by perlancar@cpan.org.

       This is free software; you can redistribute it and/or modify it under  the  same  terms  as  the  Perl  5
       programming language system itself.

perl v5.28.1                                       2019-08-17                                   Rinci::Undo(3pm)