In this series I've been showing how you can tidy up code that uses the result pattern by taking advantage of LINQ and related functional concepts. In this post we'll continue to iterate on the example from the last post, using Result<T>
in more places, making some functions async
and seeing how to handle that without the result code becoming a mess.
This post builds directly on the previous posts, so I strongly recommend you read them first before digging in here.
- Background: where we left off—LINQ and
Result<T>
- 1. Handling cases where not every method returns
Result<T>
- 2. Converting
List<Result<T>>
intoResult<List<T>>
usingSequence()
- 3. Using
async
/await
with LINQ query syntax - Summary
Background: where we left off—LINQ and Result<T>
By the end of the last post, we had managed to refactor the Result<T>
-based code used in UserProvisioningService.ProvisionUser()
to the following, which uses LINQ query syntax to handle errors automatically very tersely:
public class UserProvisioningService(CreateUserService createUserService)
{
public Result<UserAccount> ProvisionUser(ExternalLoginInfo info)
{
// Each call in the chain will "shot circuit" if an error occurs,
// returning an error to the caller without executing the rest of the method
return
from claims in GetClaimValues(info)
from validatedClaims in ValidateClaims(claims)
from tenantId in GetTenantId(validatedClaims)
from createRequest in CreateProvisionUserRequest(tenantId, validatedClaims)
from userAccount in createUserService.GetOrCreateAccount2(createRequest)
select userAccount;
}
// Stub methods called by ProvisionUser
private Result<Claim[]> GetClaimValues(ExternalLoginInfo loginInfo) => Array.Empty<Claim>();
private Result<Claim[]> ValidateClaims(Claim[] claims) => claims;
private Result<Guid> GetTenantId(Claim[] claims) => Guid.NewGuid();
private Result<ProvisionUserRequest> CreateProvisionUserRequest(Guid employerId, Claim[] claims) => new ProvisionUserRequest();
}
// External service
public class CreateUserService
{
public Result<UserAccount> GetOrCreateAccount(ProvisionUserRequest request) => new();
}
To achieve that terseness we simply implemented two extension methods on Result<T>
—Select()
and SelectMany()
—and the LINQ syntax support lit up.
I think it's pretty hard to argue with the terseness of this, but things are rarely as simple as the example I've shown above. In the previous example, each of the "stub" methods is a synchronous method (there's no async
/await
here), and they all return Result<T>
values. That's "easy mode"; for the rest of the post we'll add some complications and look at how to deal with them.
1. Handling cases where not every method returns Result<T>
For our first example, let's imagine that returning a Result<T>
doesn't make sense for all methods. For example, perhaps there's no "failure" path for CreateProvisionUserRequest
; that might well be the case if this method is just performing a transform creating a DTO:
// 👇 Returns ProvisionUserRequest, not Result<ProvisionUserRequest>
private ProvisionUserRequest CreateProvisionUserRequest(Guid employerId, Claim[] claims) => new ProvisionUserRequest();
Unfortunately, if you make this change, suddenly your LINQ code won't compile:
As is often the case with failures of this kind, the error is disappointingly opaque. Unless you're used to seeing errors like these, it's unlikely you'll spot the issue. The problem is that the SelectMany()
extension method we wrote operates on Result<T>
, that's why we can use this from <VAL> in <SOURCE>
construct. If <source>
is not a Result<T>
, the compiler doesn't know what to do!
One solution is to make sure that <SOURCE>
is a Result<T>
type. That doesn't mean the method we invoke has to return a Result<T>
, it just means we need to create one. For example:
public class UserProvisioningService(CreateUserService createUserService)
{
public Result<UserAccount> ProvisionUser(ExternalLoginInfo info)
{
return
from claims in GetClaimValues(info)
from validatedClaims in ValidateClaims(claims)
from tenantId in GetTenantId(validatedClaims)
// Explicitly wrap return value from CreateProvisionUserRequest()
// in a Result<T> object 👇
from createRequest in Result<ProvisionUserRequest>.Success(CreateProvisionUserRequest(tenantId, validatedClaims))
from userAccount in createUserService.GetOrCreateAccount2(createRequest)
select userAccount;
}
}
That's obviously somewhat ugly, so it's quite common to have an extension method to tidy things up
public static class ResultExtensions
{
public static Result<T> ToResult<T>(this T source)
=> Result<T>.Success(source);
}
With this simple extension, the code to convert any type to a "success" result is simpler:
public class UserProvisioningService(CreateUserService createUserService)
{
public Result<UserAccount> ProvisionUser(ExternalLoginInfo info)
{
return
from claims in GetClaimValues(info)
from validatedClaims in ValidateClaims(claims)
from tenantId in GetTenantId(validatedClaims)
// Calling ToResult() on the result is shorter and cleaner👇
from createRequest in CreateProvisionUserRequest(tenantId, validatedClaims).ToResult()
from userAccount in createUserService.GetOrCreateAccount2(createRequest)
select userAccount;
}
}
However, in this case, we don't actually need the ToResult()
. Instead, you can use a construct built into the LINQ query syntax: let
. You can use a let
clause to store the result of a method call into a new variable which is exactly what we want!
public class UserProvisioningService(CreateUserService createUserService)
{
public Result<UserAccount> ProvisionUser(ExternalLoginInfo info)
{
return
from claims in GetClaimValues(info)
from validatedClaims in ValidateClaims(claims)
from tenantId in GetTenantId(validatedClaims)
// 👇 Use `let x = y` instead of `from x in y` when the return value is not a Result<T>
let createRequest = CreateProvisionUserRequest(tenantId, validatedClaims)
from userAccount in createUserService.GetOrCreateAccount2(createRequest)
select userAccount;
}
}
The lesson here is that not everything needs to be a Result<T>
😃
2. Converting List<Result<T>>
into Result<List<T>>
using Sequence()
For our next challenge, we're going to focus on the ValidateClaims()
method. Our stub version currently looks like this, for simplicity:
private Result<Claim[]> ValidateClaims(Claim[] claims) => claims;
Lets imagine it's slightly more complicated than that. We'll imagine that this method operates by calling a helper method ValidateClaim()
, which returns a Result<ValidatedClaim>
object, something like this:
// Stub helper method that operates on a single claim
private Result<ValidatedClaim> ValidateClaim(Claim claim)
=> new ValidatedClaim(claim);
// The method we need to implement
private Result<IEnumerable<ValidatedClaim>> ValidateClaims(Claim[] claims)
{
// ... TODO
}
I've left the method implementation blank for now. It's clear we need to call ValidateClaim
with each of the Claim
objects in claims
, but then what? If we leverage LINQ again (method syntax this time), then we get:
private Result<IEnumerable<ValidatedClaim>> ValidateClaims(Claim[] claims)
{
IEnumerable<Result<ValidatedClaim>> validationResults =
claims.Select(c => ValidateClaim(c));
// ...
}
That gets us something that looks close: it gives us an IEnumerable<Result<T>>
, but what we want is a Result<IEnumerable<T>>
. If we think about how to make that translation, we need to:
- Initialize an empty collection of
ValidatedClaim
to hold the results. - Iterate through all of the
Result<T>
invalidationResults
.- If the
Result<T>
is aFail
result, create aResult<IEnumerable<T>>
from the failure, and return it. - If the
Result<T>
is a success, append it to the collection of results
- If the
- After iterating through everything, you will either have a failed
Result<IEnumerable<T>>
containing the first failure, or aResult<IEnumerable<T>>
containing all the valid results.
As mentioned previously, I don't want to get into the weeds of functional programming or the differences between monadic and applicative styles, so I'm using the same monadic approach we have previously here!
At a high level, that functionality is encapsulated by the Linq
method Aggregate()
(which is an the implementation of the functional concept left fold). We can use Aggregate()
(combined with a bit of LINQ query syntax for good measure) to implement our final ValidateClaims()
method:
private Result<IEnumerable<ValidatedClaim>> ValidateClaims(Claim[] claims)
{
IEnumerable<Result<ValidatedClaim>> validationResults = claims.Select(ValidateClaim);
var zero = Result<IEnumerable<ValidatedClaim>>.Success([]);
return validationResults.Aggregate(zero, (accumulated, current) =>
from previouslyValidated in accumulated // These ensure we return a failure
from claim in current // if there are any failures so far.
select previouslyValidated.Append(claim)); // If no failures, append to the list
}
Personally I find anything that uses Aggregate()
is complex to both read and write, so you definitely don't want to be writing this every time you need it. The good news is that we can easily create a generic extension method (called Sequence()
for historic reasons) to help with this scenario:
public static class ResultExtensions
{
public static Result<IEnumerable<T>> Sequence<T>(this IEnumerable<Result<T>> results)
{
var zero = Result<IEnumerable<T>>.Success([]);
return results.Aggregate(zero, (accumulated, result) =>
from previous in accumulated
from next in result
select previous.Append(next));
}
}
You can hopefully recognise that this is essentially the same implementation, just updated to work with T
instead of Claim
. We can now rewrite the ValidateClaims()
method to use this extension directly, so that the implementation becomes:
private Result<IEnumerable<ValidatedClaim>> ValidateClaims(Claim[] claims)
=> claims.Select(ValidateClaim).Sequence();
That's much better!
3. Using async
/await
with LINQ query syntax
In this section we'll look at a different common scenario: where some or all of the methods are asynchronous and return Task<Result<T>>
. For our purposes we'll assume that several of the methods in our LINQ statement are async
, for example:
private Task<Result<IEnumerable<Claim>>> ValidateClaims(Claim[] claims);
private Task<Result<Guid>> GetTenantId(IEnumerable<Claim> claims);
Your first thought may be that you can simply call await
inline for the async
methods, but unfortunately, that gets you a compiler error:
Instead, we must provide a way for the LINQ statements to work with Task<Result<T>>
values, and for that we're back to our old friend SelectMany
. In the previous post I showed how defining a SelectMany()
extension method for Result<T>
unlocked the LINQ query syntax we've been relying on. The following SelectMany()
definition does the same thing for Task<Result<T>>
:
public static class TaskResultExtensions
{
public static async Task<Result<TResult>> SelectMany<TSource, TMiddle, TResult>(
this Task<Result<TSource>> source,
Func<TSource, Task<Result<TMiddle>>> collectionSelector,
Func<TSource, TMiddle, TResult> resultSelector)
{
// await the Task<Result<T>> to get the result
Result<TSource> result = await source;
return
await result.Switch(
onSuccess: async r =>
{
// we need a second await here
Result<TMiddle> result = await collectionSelector(r);
return result.Select(v => resultSelector(r, v));
},
// The failure path is synchronous, so wrap the failure in a `Task<>`
onFailure: e => Task.FromResult(Result<TResult>.Fail(e)));
}
With that addition, we can now handle Task<Result<T>>
in LINQ statements. One caveat is that we need to ensure every from
statement returns a Task<Result<T>>
:
// Returns a `Task<Result<T>>`
public Task<Result<UserAccount>> ProvisionUser(ExternalLoginInfo info)
{
return
// Every statment needs to return a Task, so you need to wrap non-async results with Task.FromResult()
from claims in Task.FromResult(GetClaimValues(info)) // 👈 Wrap the return value in Task<>
from validatedClaims in ValidateClaims(claims) // Returns Task<Result<T>>
from tenantId in GetTenantId(validatedClaims) // Returns Task<Result<T>>
from createRequest in Task.FromResult(CreateProvisionUserRequest(tenantId, validatedClaims)) // Wrap the return value in Task<>
from userAccount in createUserService.GetOrCreateAccount(createRequest) // Returns Task<Result<T>>
select userAccount;
}
The requirement that every method returns the same "type" of value (whether that's a Result<T>
or Task<Result<T>>
) is just the beginning of where things can start to get mucky. We already have a couple of Task.FromResult()
in the code above, and that's because those methods return Result<T>
. If they returned Task<T>
, things would be much more complicated, and that's where you have to start wondering: is this the right approach?
In the next post I'll describe where I've ended up in my attitude to this style of result pattern, and discuss some of the comments and criticisms this series has received from various people.
Summary
This post expands on the previous posts in the series in which I discuss using the result pattern in practice. In the previous post I showed that using LINQ features can dramatically simplify the amount of boilerplate code the result pattern requires by using LINQ. In this post I expanded the LINQ examples to demonstrate how to support async code, different return types, and collections of results.
Each of these additional scenarios can be supported by leveraging LINQ features or providing additional extension methods, however it also raises the question as to whether this is worth the effort given the extra complexity this adds. In the next post in the series I'll discuss some of the pros and cons of this style of result pattern usage.