C#向けResultパターンライブラリ「FluentResult」Readme抄訳

# この記事について
C#でResultパターンを実現するためのライブラリ「FluentResults」の[README](https://github.com/altmann/FluentResults/blob/master/README.md)を抄訳したものです。

https://github.com/altmann/FluentResults

パフォーマンス上の問題がある例外を避けつつ、表現力の貧弱なリターンコードに代わる手法を模索する中でResultパターンとこのライブラリを見つけ、導入を検討するにあたり公式ドキュメントを訳してみました。

原文確認日：2024/10/9

------------------------------------------------------

# はじめに
FluentResultsは、例外をスロー・使用する代わりに、処理の成功・失敗を示すオブジェクトを返す機能を提供する軽量なNETライブラリです。

[FluentResultsはNuGetでインストール](https://www.nuget.org/packages/FluentResults/)できます。
```powershell
Install-Package FluentReuslts
```

> 最も必要とされているコミュニティ機能は [FluentResults.Extensions.AspNetCore](https://www.nuget.org/packages/FluentResults.Extensions.AspNetCore/) パッケージとして公開されています。ドキュメントは[こちら](https://github.com/altmann/FluentResults/wiki/Returning-Result-Objects-from-ASP.NET-Core-Controller)。試して[フィードバックを送って](https://github.com/altmann/FluentResults/issues/149)ください。

# 主な機能 ([Key Features](https://github.com/altmann/FluentResults#key-features))
- **一般化されたコンテナ**になっており、ASP.NET MVC/WebApi、WPF、DDD ドメインモデルなどすべてのコンテキストで使用可能
- 1つのResultオブジェクトに**複数のエラー情報を格納可能**
- 単純なエラーメッセージ文字列の代わりに、**強力で精巧なError・Successオブジェクトを格納可能**
- Error・Successオブジェクトをオブジェクト指向に基づいて設計
- 根本的なエラー原因とそれに連なるエラーを階層的な構造で格納可能
- .NET Standard、.NET Core、.NET 5以上、.NET Frameworkをサポート（[詳細](#対象netバージョン-net-targeting)）
- SourceLinkをサポート
- 有名・一般的なフレームワーク・ライブラリとの統合方法を示す[コードサンプル](#サンプルとベストプラクティス-samplesbest-practices)を提供
- **NEW** [FluentAssertions 拡張](https://github.com/altmann/FluentResults/wiki/Asserting-Result-Objects)により、FluentResultのオブジェクトに対するAssertを簡潔に実現可能
- **プレビュー中** [ASP.NETコントローラーからResultオブジェクトを返す](https://github.com/altmann/FluentResults/wiki/Returning-Result-Objects-from-ASP.NET-Core-Controller)

# 例外ではなくResultオブジェクトを使う理由([Why Results insted of exceptions](https://github.com/altmann/FluentResults/wiki/Returning-Result-Objects-from-ASP.NET-Core-Controller))
実のところ、成功・失敗を示すオブジェクトを返すというパターンは新しいアイデアというわけではなく、関数型プログラミング言語に由来しています。FluentResultによりこのパターンを.NET/C#でも使用できます。
[Vladimir Khorikov氏による記事「Exceptions for Flow Control」](https://enterprisecraftsmanship.com/posts/exceptions-for-flow-control/)では、Resultパターンが適しているシナリオ・適していないシナリオが非常にわかりやすく説明されています。[ベストプラクティスとResultパターンに関する資料の一覧](#サンプルとベストプラクティス-samplesbest-practices)もご覧ください。

# Resultの生成方法 ([Creating a Result](https://github.com/altmann/FluentResults?tab=readme-ov-file#creating-a-result))
1つのResultオブジェクトは複数のエラー・成功メッセージを格納できます。

```csharp
// 成功を示すResultを生成
Result successResult1 = Result.Ok();

// 失敗を示すResultを生成
Result errorResult1 = Result.Fail("My error message");
Result errorResult2 = Result.Fail(new Error("My error message"));
Result errorResult3 = Result.Fail(new StartDateIsAfterEndDateError(startDate, endDate));
Result errorResult4 = Result.Fail(new List<string> { "Error 1", "Error 2" });
Result errorResult5 = Result.Fail(new List<IError> { new Error("Error 1"), new Error("Error 2") });
```

`Result`クラスは、通常戻り値を持たないvoidメソッドで使用されます。

```csharp
public Result DoTask()
{
    if (this.State == TaskState.Done)
        return Result.Fail("Task is in the wrong state.");

    // 処理

    return Result.Ok();
}
```

さらに、特定の型の値を持たせることもできます。

```csharp
// 成功を示すResultを生成
Result<int> successResult1 = Result.Ok(42);
Result<MyCustomObject> successResult2 = Result.Ok(new MyCustomObject());

// 失敗を示すResultを生成
Result<int> errorResult = Result.Fail<int>("My error message");
```

`Result<T>`クラスは、戻り値を持つメソッドで使用されます。

```csharp
public Result<Task> GetTask()
{
    if (this.State == TaskState.Deleted)
        return Result.Fail<Task>("Deleted Tasks can not be displayed.");

    // 処理

    return Result.Ok(task);
}
```

# Resultを処理する ([Processing a Result](https://github.com/altmann/FluentResults?tab=readme-ov-file#processing-a-result))
メソッドからResultオブジェクトが返されたら、処理が正常に終了したかそうでないかを確認する必要があります。Resultオブジェクトには、成功を示す `IsSuccess` プロパティ、失敗を示す `IsFailed` プロパティがあります。`Result<T>`の値には、`Value`や`ValueOrDefault`プロパティを使用してアクセスできます。

```csharp
Result<int> result = DoSomething();
     
// 成功・失敗理由のオブジェクトをすべて取得
IEnumerable<IReason> reasons = result.Reasons;

// エラーメッセージをすべて取得
IEnumerable<IError> errors = result.Errors;

// 成功メッセージをすべて取得
IEnumerable<ISuccess> successes = result.Successes;

if (result.IsFailed)
{
    // エラーケースを処理
    var value1 = result.Value; // resultはエラー状態なので例外をスロー
    var value2 = result.ValueOrDefault; // resultはエラー状態なのでデフォルト値（＝0）を返す
    return;
}

// 成功ケースを処理
var value3 = result.Value; // 結果値を返す。例外はスローされない
var value4 = result.ValueOrDefault; // デフォルト値ではなく結果値を返す。
```

# エラー・成功メッセージを設計する ([Designing errors and success messages](https://github.com/altmann/FluentResults?tab=readme-ov-file#designing-errors-and-success-messages))
単純なメッセージ文字列だけを格納するResultパターンのライブラリは多くありますが、FluentResultはその代わりに強力でオブジェクト指向に基づいたError・Successオブジェクトを格納します。これにより、エラーや成功に関係する情報を1つのクラスにまとめて格納することができます。

このライブラリのパブリックAPIでは、`IReason`インターフェースと、そこから派生する`IError`、`ISuccess`インターフェースが使用されています。`Reasons`プロパティに1つでも`IError`オブジェクトが含まれている場合、そのResultオブジェクトは失敗を示し、`IsSuccess`プロパティはfalseとなります。

`IError`、`ISuccess`を実装するか、`Success`、`Error`クラスから派生させることで、独自のエラー・成功クラスを作ることができます。

```csharp
public class StartDateIsAfterEndDateError : Error
{
    public StartDateIsAfterEndDateError(DateTime startDate, DateTime endDate)
        : base($"The start date {startDate} is after the end date {endDate}")
    { 
        Metadata.Add("ErrorCode", "12");
    }
}
```

この仕組みを使って、`Warning`クラスを作ることもできます。これを`Success`と`Error`のどちらのクラスから派生させるかは、警告がある状態をエラーとして扱うか成功として扱うかは、システムの要件により決めることになります。

# その他の機能 ([Further features](https://github.com/altmann/FluentResults?tab=readme-ov-file#further-features))
## エラー・成功メッセージをメソッドチェーンでつなげる ([Chaining error and success messages](https://github.com/altmann/FluentResults#chaining-error-and-success-messages))
```csharp
var result = Result.Fail("error message 1")
                   .WithError("error message 2")
                   .WithError("error message 3")
                   .WithSuccess("success message 1");
```

## 条件式の結果によりResultオブジェクトを生成 ([Create a result depending on success/failure condition](https://github.com/altmann/FluentResults?tab=readme-ov-file#create-a-result-depending-on-successfailure-condition))
```csharp
var result = string.IsNullOrEmpty(firstName) ? Result.Fail("First Name is empty") : Result.Ok();
```

`FailIf()`、`OkIf()`メソッドでより読みやすい書き方もできます。

```csharp
var result = Result.FailIf(string.IsNullOrEmpty(firstName), "First Name is empty");
```

エラーインスタンスの初期化を遅延させたい場合は、`Func<string>`または`Func<Error>`を取るオーバーロードを使うことで実現できます。

```csharp
var list = Enumerable.Range(1, 9).ToList();

var result = Result.FailIf(
    list.Any(IsDivisibleByTen),
    () => new Error($"Item {list.First(IsDivisibleByTen)} should not be on the list"));

bool IsDivisibleByTen(int i) => i % 10 == 0;

// rest of the code
```

## [Try](https://github.com/altmann/FluentResults?tab=readme-ov-file#try) 
ある処理を実行したときに例外が発生したら、キャッチしてResultオブジェクトに変換することができます。
```csharp
var result = Result.Try(() => DoSomethingCritical());
```

独自のResultオブジェクトを返すこともできます。
```csharp
var result = Result.Try(() => {
    if(IsInvalid()) 
    {
        return Result.Fail("Some error");
    }

    int id = DoSomethingCritical();

    return Result.Ok(id);
});
```

上述の例では、デフォルトのcatchHandlerが使用されます。この動作をResultクラスのグローバル設定で変更し、Errorオブジェクトの内容をカスタマイズできます。

```csharp
Result.Setup(cfg =>
{
    cfg.DefaultTryCatchHandler = exception =>
    {
        if (exception is SqlTypeException sqlException)
            return new ExceptionalError("Sql Fehler", sqlException);

        if (exception is DomainException domainException)
            return new Error("Domain Fehler")
                .CausedBy(new ExceptionError(domainException.Message, domainException));

        return new Error(exception.Message);
    };
});

var result = Result.Try(() => DoSomethingCritical());
```

It is also possible to pass a custom catchHandler via the Try(..) method.
カスタムのcatchHandlerを`Try()`メソッドで渡すこともできます。

```csharp
var result = Result.Try(() => DoSomethingCritical(), ex => new MyCustomExceptionError(ex));
```

## エラーの根本原因 ([Root cause of the error](https://github.com/altmann/FluentResults?tab=readme-ov-file#root-cause-of-the-error))
`CausedBy()`には、エラーの根本原因として、Errorオブジェクト、Errorのリスト、文字列、文字列のリストまたは例外オブジェクトを渡すことでき、`Reasons`プロパティを介して参照できます。

例1：例外を渡す場合

```csharp
try
{
    //export csv file
}
catch(CsvExportException ex)
{
    return Result.Fail(new Error("CSV Export not executed successfully").CausedBy(ex));
}
```

例2：Errorオブジェクトを渡す場合

```csharp
Error rootCauseError = new Error("This is the root cause of the error");
Result result = Result.Fail(new Error("Do something failed", rootCauseError));
```

例3：Errorのリストから原因を読み取る

```csharp
Result result = ....;
if (result.IsSuccess)
   return;

foreach(IError error in result.Errors)
{
    foreach(ExceptionalError causedByExceptionalError in error.Reasons.OfType<ExceptionalError>())
    {
        Console.WriteLine(causedByExceptionalError.Exception);
    }
}
```

## メタデータ ([Metadata](https://github.com/altmann/FluentResults?tab=readme-ov-file#metadata))
Error/Successオブジェクトにメタデータを追加することができます。

方法1：Resultオブジェクトを生成する際に`WithMetadata()`メソッドを呼ぶ

```csharp
var result1 = Result.Fail(new Error("Error 1").WithMetadata("metadata name", "metadata value"));

var result2 = Result.Ok()
                    .WithSuccess(new Success("Success 1")
                                 .WithMetadata("metadata name", "metadata value"));
```

方法2：Error/Successクラスのコンストラクタで`WithMetadata()`メソッドを呼ぶ

```csharp
public class DomainError : Error
{
    public DomainError(string message)
        : base(message)
    { 
        WithMetadata("ErrorCode", "12");
    }
}
```

## 結果のマージ ([Merging](https://github.com/altmann/FluentResults?tab=readme-ov-file#merging))
複数のResultオブジェクトを`Merge()`静的メソッドでマージすることができます。

```csharp
var result1 = Result.Ok();
var result2 = Result.Fail("first error");
var result3 = Result.Ok<int>();

var mergedResult = Result.Merge(result1, result2, result3);
```

`Merge()`拡張メソッドを使ってResultのリストを1つのResultオブジェクトにマージすることもできます。

```csharp
var result1 = Result.Ok();
var result2 = Result.Fail("first error");
var result3 = Result.Ok<int>();

var results = new List<Result> { result1, result2, result3 };

var mergedResult = results.Merge();
```

## 変換 ([Converting and Transformation](https://github.com/altmann/FluentResults?tab=readme-ov-file#converting-and-transformation))
`ToResult()`、`ToResult<TValue>()`メソッドを使って、Resultオブジェクトを別のResultオブジェクトに変換することができます。

```csharp
// 1. 結果値を持たないResultオブジェクトをResult<int>型（値はint型のデフォルト）に変換
Result.Ok().ToResult<int>();

// 2. 結果値を持たないResultオブジェクトをResult<int>型（値は5を指定）に変換
Result.Ok().ToResult<int>(5);

// 3. 失敗を示すResultオブジェクトをResult<int>型に変換
// 失敗しているため、値は不要
Result.Fail("Failed").ToResult<int>();

// 4. Result<int>をResult<float>に変換（引数に変換ロジックを渡す）
Result.Ok<int>(5).ToResult<float>(v => v);

// 5. 失敗を示すResult<int>オブジェクトをResult<float>型に変換
// 失敗しているため、変換ロジックは不要
Result.Fail<int>("Failed").ToResult<float>();

// 6. Result<int>オブジェクトを結果値なしのResultに変換
Result.Ok<int>().ToResult();
```

`Map()`メソッドを使ってResultオブジェクトの結果値を別の形に変換できます。

```csharp
// 7. Result<int>をResult<Dto>に変換
Result.Ok<int>(5).Map(v => new Dto(5));
```

### 訳注
`ToResult(変換ロジック)` と `Map(変換ロジック)` は同じ動作です（`ToResult<TNewValue>(Func<TValue, TNewValue>)` は `Map<TNewValue>(Func<TValue, TNewValue>)` に[処理を委譲](https://github.com/altmann/FluentResults/blob/0c3376ff6a54bc60d0bd92e38f1c064aa2787efb/src/FluentResults/Results/Result.cs#L323)しています。）。つまり、例5と例7は変換先の結果値型が異なるだけで、同じことをしています。

## T型から成功を示す`Result<T>`への暗黙的な変換 ([Implicit conversion from T to success result Result<T>](https://github.com/altmann/FluentResults?tab=readme-ov-file#implicit-conversion-from-t-to-success-result-resultt))
```csharp
string myString = "hello world";
Result<T> result = myString;
```

## Errorから失敗を示す`Result`/`Result<T>`への暗黙的な変換 ([Implicit conversion from Error to fail result Result or Result<T>](https://github.com/altmann/FluentResults?tab=readme-ov-file#implicit-conversion-from-error-to-fail-result-result-or-resultt))
単一のErrorから：
```csharp
Error myError = new Error("error msg");
Result result = myError;
```

Errorのリストから：
```csharp
List<Error> myErrors = new List<Error>() 
    { 
        new Error("error 1"), 
        new Error("error 2") 
    };
    
Result result = myErrors;
```

## ResultをほかのResultにバインド ([Bind the result to another result](https://github.com/altmann/FluentResults?tab=readme-ov-file#bind-the-result-to-another-result))
バインドは`Result`または`Result<T>`を返す変換です。元のResultが成功している場合のみ変換が実行されます。2つのResultのReasonsは1つのフラットなResultにマージされます。

```csharp
// あるResultを、失敗になる可能性のあるResult<T>に変換
Result<string> r = Result.Ok(8)
    .Bind(v => v == 5 ? "five" : Result.Fail<string>("It is not five"));

// 失敗したResultを、同じく失敗になる可能性のあるResult<T>に変換
// 結果 r は最初のResultのエラーに含まれるエラーメッセージのみを含んでおり、
// Bind()に指定した変換は評価されない（最初のResultの値が利用できないため）
Result<string> r = Result.Fail<int>("Not available")
    .Bind(v => v == 5 ? "five" : Result.Fail<string>("It is not five"));

// [訳注] 上記例の実行結果
// r.IsSuccess は false(よってr.Valueは利用できない)
// r.Errors は "Not available" のみ

// converting a result with value to a Result via a transformation which may fail
// 結果値を持つResultを、結果値のないResultに変換（変換が失敗する可能性がある）
Result.Ok(5).Bind(x => Result.OkIf(x == 6, "Number is not 6"));

// [訳注] 上記例の実行結果
// r.IsSuccess は false
// r.Errors は "Number is not 6" のみ
// 最初が Result.Ok(6) であれば、r.IsSuccess は true となる

// 結果値のないResultを、結果値のあるResult<T>に変換
Result.Ok().Bind(() => Result.Ok(5));

// 元のResultが成功している場合にアクションを実行
Result r = Result.Ok().Bind(() => Result.Ok());
```

`Bind()`には非同期版オーバーロードもあります。

```csharp
var result = await Result.Ok(5)
    .Bind(int n => Task.FromResult(Result.Ok(n + 1).WithSuccess("Added one")))
    .Bind(int n => /* next continuation */);
```

## `ISuccess`/`IError`/`IExceptionalError`のグローバルなファクトリを設定 ([Set global factories for ISuccess/IError/IExceptionalError](https://github.com/altmann/FluentResults?tab=readme-ov-file#set-global-factories-for-isuccessierroriexceptionalerror))
FluentResultsのライブラリ内で、`ISuccess`、`IError`、`IExceptionalError`のインスタンスが生成されることがあります。例えば `Result.Fail("My Error")` が呼ばれると、内部で `IError` オブジェクトが生成されます。この動作を上書きして独自のエラークラスを生成させたい場合は、エラーのファクトリを設定することができます。`ISuccess`、`IExceptionalError`についても同様です。

```csharp
Result.Setup(cfg =>
{
    cfg.SuccessFactory = successMessage => new Success(successMessage).WithMetadata("Timestamp", DateTime.Now);
    
    cfg.ErrorFactory = errorMessage => new Error(errorMessage).WithMetadata("Timestamp", DateTime.Now);
    
    cfg.ExceptionalErrorFactory = (errorMessage, exception) => new ExceptionalError(errorMessage ?? exception.Message, exception)
    .WithMetadata("Timestamp", DateTime.Now);
});
```

## ErrorとSuccessのマッピング ([Mapping errors and successes](https://github.com/altmann/FluentResults?tab=readme-ov-file#mapping-errors-and-successes))
1つのResultオブジェクトに含まれるSuccessデータのすべてに対して情報を付加したい場合は、`MapSuccesses()`メソッドを使います。

```csharp
var result = Result.Ok().WithSuccess("Success 1");
var result2 = result.MapSuccesses(e => new Success("Prefix: " + e.Message));
```

Errorに対しても同様に、`MapErrors()`メソッドを使います。ただし、対象となるのは第1階層にあるエラーのみで、たとえば`error.Reasons`の中にあるようなエラーまでは変更されません。

```csharp
var result = Result.Fail("Error 1");
var result2 = result.MapErrors(e => new Error("Prefix: " + e.Message));
```

## Errorオブジェクトのキャッチとハンドリング ([Handling/catching errors](https://github.com/altmann/FluentResults?tab=readme-ov-file#handlingcatching-errors))
例外のcatchブロックおように、Resultオブジェクトに含まれるErrorのチェックとハンドリングを行うことができます。

```csharp
// Resultオブジェクトが特定の型のErrorオブジェクトを含んでいるかどうかをチェック
result.HasError<MyCustomError>();

// Resultオブジェクトが特定の型 かつ 指定された条件を満たすErrorオブジェクトを含んでいるかどうかをチェック
result.HasError<MyCustomError>(myCustomError => myCustomError.MyField == 2);

// Resultオブジェクトが特定のメタデータキーを持つErrorオブジェクトを含んでいるかどうかをチェック
result.HasError(error => error.HasMetadataKey("MyKey"));

// Resultオブジェクトが特定のメタデータキーと値を持つErrorオブジェクトを含んでいるかどうかをチェック
result.HasError(error => error.HasMetadata("MyKey", metadataValue => (string)metadataValue == "MyValue")); 
```

すべての`HasError()`メソッドは、見つかったErrorオブジェクトにアクセスするためのoptionalなout引数を備えています。

## Successオブジェクトのハンドリング ([Handling successes](https://github.com/altmann/FluentResults?tab=readme-ov-file#handling-successes))
```csharp
// check if the Result object contains a success from a specific type
// Resultオブジェクトが特定の型のSuccessオブジェクトを含んでいるかどうかをチェック
result.HasSuccess<MyCustomSuccess>();

// check if the Result object contains a success from a specific type and with a specific condition
// Resultオブジェクトが特定の型 かつ 指定された条件を満たすSuccessオブジェクトを含んでいるかどうかをチェック
result.HasSuccess<MyCustomSuccess>(success => success.MyField == 3);
```

すべての`HasSuccess()`メソッドは、見つかったSuccessオブジェクトにアクセスするためのoptionalなout引数を備えています。

## 例外オブジェクトのキャッチとハンドリング ([Handling/catching exceptions](https://github.com/altmann/FluentResults?tab=readme-ov-file#handlingcatching-exceptions))
```csharp
// check if the Result object contains an exception from a specific type
// Resultオブジェクトが特定の型の例外オブジェクトを含んでいるかどうかをチェック
result.HasException<MyCustomException>();

// check if the Result object contains an exception from a specific type and with a specific condition
// Resultオブジェクトが特定の型 かつ 指定された条件を満たす例外オブジェクトを含んでいるかどうかをチェック
result.HasException<MyCustomException>(MyCustomException => MyCustomException.MyField == 1);
```

すべての`HasException()`メソッドは、見つかった例外オブジェクトにアクセスするためのoptionalなout引数を備えています。

## パターン マッチング ([Pattern matching](https://github.com/altmann/FluentResults?tab=readme-ov-file#pattern-matching))
```csharp
var result = Result.Fail<int>("Error 1");

var outcome = result switch
{
     { IsFailed: true } => $"Errored because {result.Errors}",
     { IsSuccess: true } => $"Value is {result.Value}",
     _ => null
};
```

## Resultをタプルに分解 ([Deconstruct Operators](https://github.com/altmann/FluentResults?tab=readme-ov-file#deconstruct-operators))
```csharp
var (isSuccess, isFailed, value, errors) = Result.Fail<bool>("Failure 1");

var (isSuccess, isFailed, errors) = Result.Fail("Failure 1");
```

## ロギング ([Logging](https://github.com/altmann/FluentResults?tab=readme-ov-file#logging))
まず、loggerを作成します。

```csharp
public class MyConsoleLogger : IResultLogger
{
    public void Log(string context, string content, ResultBase result, LogLevel logLevel)
    {
        Console.WriteLine("Result: {0} {1} <{2}>", result.Reasons.Select(reason => reason.Message), content, context);
    }

    public void Log<TContext>(string content, ResultBase result, LogLevel logLevel)
    {
        Console.WriteLine("Result: {0} {1} <{2}>", result.Reasons.Select(reason => reason.Message), content, typeof(TContext).FullName);
    }
}
```

次に、このロガーをResultクラスのグローバル設定に登録します。

```csharp
var myLogger = new MyConsoleLogger();
Result.Setup(cfg => {
    cfg.Logger = myLogger;
});
```

そして、以下のように使用します。
```csharp
var result = Result.Fail("Operation failed")
    .Log();
```

これに加え、ログのコンテキストを文字列またはジェネリック型で指定することができます。より詳細な情報を提供するメッセージを渡すこともできます。

```csharp
var result = Result.Fail("Operation failed")
    .Log("logger context", "More info about the result");

var result2 = Result.Fail("Operation failed")
    .Log<MyLoggerContext>("More info about the result");
```

ログレベルを指定することも可能です。

```csharp
var result = Result.Ok().Log(LogLevel.Debug);
var result = Result.Fail().Log<MyContext>("Additional context", LogLevel.Error);
```

成功時または失敗時にのみログ記録することもできます。

```csharp
Result<int> result = DoSomething();

// 成功時のみログ記録（ログレベルはデフォルトの 'Information'）
result.LogIfSuccess();

// 失敗時のみログ記録（ログレベルはデフォルトの 'Error'）
result.LogIfFailed();
```

## FluentResultのオブジェクトに対するAssert ([Asserting FluentResult objects](https://github.com/altmann/FluentResults?tab=readme-ov-file#asserting-fluentresult-objects))
FluentAssertionsと[FluentResults.Extensions.FluentAssertions](https://github.com/altmann/FluentResults/wiki#fluentresultsextensionsfluentassertions)パッケージを試してみてください。このパッケージはv2.0から実験フェーズを抜け正式リリースされ、Resultオブジェクトに対するAssertをfluentに行うための強力な機能拡張となっています。

## 対象.NETバージョン ([.NET Targeting](https://github.com/altmann/FluentResults?tab=readme-ov-file#net-targeting))
FluentResults 3.x 以降では、.NET Standard 2.0, 2.1をサポートしています。
.NET Standard 1.1, .NET Framework 4.6.1, 4.5で使用したい場合は、[FluentResults 2.x](https://www.nuget.org/packages/FluentResults/2.6.0)を使用してください。

# サンプルとベストプラクティス ([Samples/Best Practices](https://github.com/altmann/FluentResults?tab=readme-ov-file#samplesbest-practices))
FluentResultやResultパターンを有名な・よく使われているフレームワーク・ライブラリと一緒に使う際に参考にすべきサンプルやベストプラクティスを示します。

## ドメイン駆動設計に基づいたパワフルなドメインモデル
- [コマンドハンドラーを備えたドメインモデル](https://github.com/altmann/FluentResults/tree/master/src/FluentResults.Samples/DomainDrivenDesign)
- Resultオブジェクトを返すファクトリメソッドなどを使用して、ドメインの不変性を守る
- IErrorインターフェースまたはErrorクラスから派生する独自のErrorクラスを作成することで、各エラーを一意にする
- メソッドに失敗ケースがない場合は、Resultクラスを戻り値として使用しない
- 「複数の失敗Resultをマージして返す」「最初の失敗Resultをすぐに返す」という2つの方法があることを覚えておく

## Resultオブジェクトのシリアライズ ([Serializing Result objects (ASP.NET WebApi, Hangfire)](https://github.com/altmann/FluentResults?tab=readme-ov-file#serializing-result-objects-aspnet-webapi-hangfire))
- [ASP.NET WebController](https://github.com/altmann/FluentResults/tree/master/src/FluentResults.Samples/WebController)
- [Hangfire Job](https://github.com/altmann/FluentResults/tree/master/src/FluentResults.Samples/HangfireJobs)
- FluentResultのResultオブジェクトをシリアライズしないようにする
- 独自のResultDtoクラスを作成して公開する
  - どのデータが送信・シリアライズされるかを制御できるようにするため
  - 自身が作成するパブリックAPIがFluentResultのようなサードパーティーAPIに依存しないようにするため
  - 自身が作成するパブリックAPIを安定させるため

## Resultオブジェクトを返す MediatR リクエストハンドラー ([MediatR request handlers returning Result objects](https://github.com/altmann/FluentResults?tab=readme-ov-file#mediatr-request-handlers-returning-result-objects))
- [完全な機能を備えた.NET Coreサンプルコード（コマンド/クエリとValidationPipelineBehaviorを含む）](https://github.com/altmann/FluentResults/tree/master/src/FluentResults.Samples.MediatR)
- MediatRのリクエストハンドラーから業務バリデーションエラーを返す際はResultオブジェクトを介して行う
- 業務バリデーションエラーに基づく例外はスローしない
- コマンドとクエリのバリデーションをMediatR PipelineBehaviorを通じて注入し、例外を返す代わりにResultオブジェクトを返す


# Result Patternに関する有用なリソース([Interesting Resources about Result Pattern](https://github.com/altmann/FluentResults?tab=readme-ov-file#interesting-resources-about-result-pattern))
※原文をそのまま転載

- [Error Handling — Returning Results by Michael Altmann](https://medium.com/@michael_altmann/error-handling-returning-results-2b88b5ea11e9)
- [Operation Result Pattern by Carl-Hugo Marcotte](https://www.forevolve.com/en/articles/2018/03/19/operation-result/)
- [Exceptions for flow control in C# by Vladimir Khorikov](https://enterprisecraftsmanship.com/posts/exceptions-for-flow-control/)
- [Error handling: Exception or Result? by Vladimir Khorikov](https://enterprisecraftsmanship.com/posts/error-handling-exception-or-result/)
- [What is an exceptional situation in code? by Vladimir Khorikov](https://enterprisecraftsmanship.com/posts/what-is-exceptional-situation/)
- [Advanced error handling techniques by Vladimir Khorikov](https://enterprisecraftsmanship.com/posts/advanced-error-handling-techniques/)
- [A Simple Guide by Isaac Cummings](https://medium.com/@cummingsi1993/the-operation-result-pattern-a-simple-guide-fe10ff959080)
- [Flexible Error Handling w/ the Result Class by Khalil Stemmler](https://khalilstemmler.com/articles/enterprise-typescript-nodejs/handling-errors-result-class/)
- [Combining ASP.NET Core validation attributes with Value Objects by Vladimir Khorikov](https://enterprisecraftsmanship.com/posts/combining-asp-net-core-attributes-with-value-objects/)

<details>
<summary>オリジナル文書のライセンス</summary>
MIT License

Copyright (c) 2021 Michael Altmann

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

</details>