spebbe / dartz

Functional programming in Dart
MIT License
748 stars 59 forks source link

Cancelable task #77

Open dan-leech opened 3 years ago

dan-leech commented 3 years ago

I'm a newbie in FP. FP seems very powerful but I cannot catch the idea yet...

I need to tie two repositories transactions which don't know nothing about each other. I've found probably a strange way to do this:

  Function(Completer<int> result, Completer<bool> rollBack) getData() {
    return (Completer<int> result, Completer<bool> rollBack) => db.transaction(() async {
      result.complete(45);

      final res  = await rollBack.future;
      if(!res) {
        // ex will be rethrown and the transaction will be rolled back
        throw Exception('rollback');
      }
    });
  }

I want to return a cancelable kind of task. That carries a possible result and can be rolled back from another task that this serves for. Do you know any way how to do this in FP with dartz?

spebbe commented 2 years ago

Hi @dan-leech! Sorry for the late reply.

From your example, it looks like you do want result to complete successfully even in a rollback scenario, which makes me a bit unsure of what semantics you need. Either way, below are some pretty general pointers that might be useful to you:

One thing to try is to model your domain objects using immutable data structures. IMap, IList and the other immutable collections in dartz provide a kind of in-memory transactionality by simply never overwriting/mutating anything. This means that you can safely and incrementally create new versions of domain objects in your application logic and later choose to either commit them atomically to some backing store or "roll back" by simply discarding the references to the new object graphs.

Another thing you might want to try is Evaluation, which provides ways of both sequencing the results of asynchronous/parallel tasks and propagating errors through the use of Either values, which can be used to decide whether to commit or roll back at the end of a composed operation. The resulting code would be still be impure, assuming the db.transaction operations in your example are side effecting, but it would likely improve composability and testability.

A somewhat more involved approach is to capture all database operations in a Free algebra. This is what doobie does for the JDBC API and a similar approach is likely possible for your choice of database API. This would allow your application logic to be fully pure and would push away all database interaction into Free interpreters, which in turn can be side effecting (i.e., actually talk to the database) for production runs and integration tests, but can remain pure for unit and functional tests. This would also allow you to use Conveyor to construct streaming programs with semantics determined by your Free algebra. See https://github.com/spebbe/dartz/blob/master/lib/src/io.dart, https://github.com/spebbe/dartz/blob/master/lib/src/unsafe/io.dart and https://github.com/spebbe/dartz/blob/master/example/free_io/mock_io.dart for an example on how to capture and interpret impure APIs using Free.

I hope that helped, but I understand if the tips above are a bit too general to be immediately useful to you. Please reply with more detailed information about your use case if you want and maybe we can uncover something more concrete then!