LoginSignup
1
2

.NET 8 で ASP.NET Core でホストされた Blazor WebAssembly アプリにレイヤードアーキテクチャーを実装する⓶

Last updated at Posted at 2024-04-20

やりたいこと

  • .NET 8 で構築した ASP.NET Core でホストされた Blazor WebAssembly アプリからビジネスロジックを分離したい
  • UI プロジェクトと Domain プロジェクトを作ったので、橋渡しをするレイヤーを追加する

前回までの記事

現在のアーキテクチャー

  • SolutionName
    • SolutionName.AppCore.Domain
      • ビジネスロジック
    • SolutionName.UI.Client
      • クライアントアプリ
    • SolutionName.UI.Server
      • API
    • SolutionName.UI.Share
      • クライアントアプリとAPIの間でやり取りするオブジェクト等

問題点

ビジネスロジックに UI が直接依存してしまっている
Domain に置いたビジネスロジックは他の UI やコンポーネントからも使いたい。そんなときに UI から Domain を直接参照していると UI 毎に複雑な変換ロジックが書かれてしまうかもしれません。十分に小さいシステムであれば問題にはなりませんが、今回は UI と Domain の間に UseCase 層を追加してみます。

環境

  • Windows 11 23H2
  • Visual Studio Community 2022 Version 17.9.6

手順

AppCore.UseCase プロジェクトの実装

プロジェクトを追加

  1. クラスライブラリ プロジェクトを追加
    • 名前は SolutionName.AppCore.Domain とする
    • .NET 8 を選択
  2. 不要な Class1.cs を削除
  3. WeatherForecastUseCases フォルダーを追加

出力用 DTO を追加

  1. WeatherForecastUsecases の下に WeatherForecastOutput クラスを追加
WeatherForecastOutput.cs
namespace SolutionName.AppCore.UseCase.WeatherForecastUsecases;

internal sealed class WeatherForecastOutput(
    DateOnly date,
    int temperatureC,
    int temperatureF,
    string summary)
{
    public DateOnly Date { get; } = date;
    public int TemperatureC { get; } = temperatureC;
    public int TemperatureF { get; } = temperatureF;
    public string Summary { get; } = summary;
}

UseCase 層から UI 層へ渡される DTO(Data Transfer Object) です。

UseCase のインターフェイスを追加

  1. WeatherForecastUsecases の下に IGetWeatherForecastUseCase インターフェイスを追加
IGetWeatherForecastUseCase.cs
namespace SolutionName.AppCore.UseCase.WeatherForecastUsecases;

internal interface IGetWeatherForecastUseCase
{
    public Task<WeatherForecastOutput> ExecuteAsync(DateOnly date);
}

WeatherForecast を取得する UseCase です。

プロジェクト参照の追加

UseCase から Domain を使うためのプロジェクト参照を追加します。

  1. プロジェクトの「依存関係」を右クリック、「プロジェクト参照の追加」をクリック
  2. AppCore.Domain プロジェクトをチェック、「OK」をクリック

UseCase を実装

  1. WeatherForecastUsecases の下に GetWeatherForecastUseCase クラスを追加
GetWeatherForecastUseCase.cs
namespace SolutionName.AppCore.UseCase.WeatherForecastUsecases;

internal sealed class GetWeatherForecastUseCase : IGetWeatherForecastUseCase
{
    public async Task<WeatherForecastOutput> ExecuteAsync(DateOnly date)
    {
        // Domain のロジックを使用
        int temperatureC = Random.Shared.Next(-20, 55);
        WeatherForecast weatherForecast = new(date, temperatureC);

        WeatherForecastOutput output = new(
            weatherForecast.Date,
            weatherForecast.TemperatureC,
            weatherForecast.TemperatureF,
            weatherForecast.Summary.ToString());

        return await Task.FromResult(output);
    }
}

現在 UI.Server と同じようにランダムな気温が取得できるようにします。

UI プロジェクトから参照する

プロジェクト参照の追加

  1. プロジェクトの「依存関係」を右クリック、「プロジェクト参照の追加」をクリック
  2. AppCore.UseCase プロジェクトをチェック
  3. AppCore.Domain プロジェクトをチェック解除、「OK」をクリック

以後 UI プロジェクトは直接 Domain プロジェクトを参照しません。

AppCore.UseCase の外から使用するクラスを public に変更

WeatherForecastOutput.cs
-internal sealed class WeatherForecastOutput(
+public sealed class WeatherForecastOutput(
IGetWeatherForecastUseCase.cs
-internal interface IGetWeatherForecastUseCase
+public interface IGetWeatherForecastUseCase
GetWeatherForecastUseCase.cs
-internal sealed class GetWeatherForecastUseCase : IGetWeatherForecastUseCase
+public sealed class GetWeatherForecastUseCase : IGetWeatherForecastUseCase

またもや「初めから public にしとけや」って声が聞こえた気がしますが、必要になったら public にする、が基本方針です。

UI.Server から IGetWeatherForecastUseCase を使用する

  1. WeatherForecastController に IGetWeatherForecastUseCase を注入する
WeatherForecastController.cs
-using HigeDaruma.DemoNet8BlazorWasm.AppCore.Domain.WeatherModels;
+using HigeDaruma.DemoNet8BlazorWasm.AppCore.UseCase.WeatherForecastUseCases;
using HigeDaruma.DemoNet8BlazorWasm.UI.Share.WeatherForecastModels;
using Microsoft.AspNetCore.Mvc;

namespace SolutionName.UI.Server.Controllers;

[ApiController]
[Route("[controller]")]
public class WeatherForecastController(
+   IGetWeatherForecastUseCase getWeatherForecastUseCase,
    ILogger<WeatherForecastController> logger) : ControllerBase

これで、WeatherForecastController 内で IGetWeatherForecastUseCase を使うことができます。

UseCase へのプロジェクト参照により推移的に Domain へのプロジェクト参照が残ってしまうことに注意しましょう。

2. UseCase を使用する

WeatherForecast のインスタンスを生成していた箇所で IGetWeatherForecastUseCase を使います。入力する日付はそのまま(DateTime.Now から5日間)の仕様にしておきます。

WeatherForecastController.cs
    [HttpGet(Name = "GetWeatherForecast")]
-   public IEnumerable<WeatherForecastViewModel> Get()
+   public async Task<IEnumerable<WeatherForecastViewModel>> Get()
    {
-       return Enumerable.Range(1, 5).Select(index =>
-       {
-           DateOnly date = DateOnly.FromDateTime(DateTime.Now.AddDays(index));
-           int temperatureC = Random.Shared.Next(-20, 55);
-           WeatherForecast weatherForecast = new(date, temperatureC);
-
-           return new WeatherForecastViewModel
-           {
-               Date = weatherForecast.Date,
-               TemperatureC = weatherForecast.TemperatureC,
-               Summary = weatherForecast.Summary.ToString(),
-           };
-       })
-       .ToArray();
+       List<WeatherForecastViewModel> viewModels = [];
+       for (int i = 0; i < 5; i++)
+       {
+           DateTime dateTime = DateTime.Now.AddDays(i);
+           DateOnly dateOnly = DateOnly.FromDateTime(dateTime);
+           WeatherForecastOutput output = await getWeatherForecastUseCase.ExecuteAsync(dateOnly);
+           WeatherForecastViewModel viewModel = new(
+               output.Date,
+               output.TemperatureC,
+               output.Summary,
+               output.TemperatureF);
+           viewModels.Add(viewModel);
+       }
+
+       return viewModels;
    }

3. DI の登録

IGetWeatherForecastUseCase に GetWeatherForecastUseCase を注入するように登録します。

Program.cs
+// DI
+builder.Services.AddScoped<IGetWeatherForecastUseCase, GetWeatherForecastUseCase>();

var app = builder.Build();

using Microsoft.Extensions.DependencyInjection; は global usings に追加されています。

動作確認

データを取得・表示できました!
image.png

まとめ

変更後のアーキテクチャー

  • SolutionName
    • SolutionName.AppCore.Domain
      • ビジネスロジック
    • SolutionName.AppCore.UseCase
      • アプリケーション固有のビジネスロジック
    • SolutionName.UI.Client
      • クライアントアプリ
    • SolutionName.UI.Server
      • API
    • SolutionName.UI.Share
      • クライアントアプリとAPIの間でやり取りするオブジェクト等

問題点

  • WeatherForecast の生成を UseCase で行っている
    • 今後データベースや外部の API を使ってデータを取得しようとした場合に UseCase を変更しなければならない
    • データソースの切り替えが困難

次回は Infra.Data 層を追加して、永続化ロジックを選択できるように実装します。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2