I am having a headache trying to put together a simple functionality.
Consider the following definitions:
type Entity = {Id:int;Data:string}
type IRepository =
abstract member SaveAsync: array<Entity> -> Task<bool>
abstract member RollBackAsync: array<Entity> -> Task<bool>
type INotification =
abstract member SaveAsync: array<Entity> -> Task<bool>
The use Task<T>
because they are libraries developed in other .NET languages.
(I have created this code for the sake of the example)
Basically, I want to save data in the repository service, and then save the data in the notification service. But if this second operation fails, and that includes exceptions, I want to rollback the operation in the repository. Then there are two situations where I would want to call the rollback operation, the first if notification.SaveAsync
returns false, and the second if it throws an exception. And of course, I would like to code that call to rollback once, but I cannot find the way.
This is what I have tried:
type Controller(repository:IRepository, notification:INotification) =
let saveEntities entities:Async<bool> = async{
let! repoResult = Async.AwaitTask <| repository.SaveAsync(entities)
if(not repoResult) then
return false
else
let notifResult =
try
let! nr = Async.AwaitTask <| notification.SaveAsync(entities)
nr
with
| _-> false
if(not notifResult) then
let forget = Async.AwaitTask <| repository.RollBackAsync(entities)
return false
else
return true
}
member self.SaveEntitiesAsync(entities:array<Entity>) =
Async.StartAsTask <| saveEntities entities
But unfortunately I get a compiler error on the let! nr = ...
saying: This construct may only be used within computation expressions
Which would be the right way of doing this?
The problem is that when you use let v = e
in computation expressions, the expression e
is an ordinary expression that cannot contain further asynchronous constructs. That's exactly what happens here:
let notifResult =
try
let! nr = Async.AwaitTask <| notification.SaveAsync(entities)
nr
with _-> false
You can turn this into a nested async
block:
let! notifResult = async {
try
let! nr = Async.AwaitTask <| notification.SaveAsync(entities)
return nr
with _-> return false }