概要#
この記事は、ABP フレームワークを使用して、単一のエンティティ (Entity) に対する CRUD RESTful API を提供することを目的としています。つまり、UI 層のないソリューションです。
Web アプリケーションの開発が必要な場合は、こちらを参照してください:https://docs.abp.io/en/abp/latest/Tutorials/Part-1
前提条件(Pre-Requirements)#
- .NET 7+
- Node.js v16+ (Angular が提供するデフォルトのページ実装、例えばログイン / 登録、テナントおよびユーザー管理を使用しているため)
ABP CLI ツールのインストール#
ABP CLI を使用して新しい ABP ソリューションを作成するため、事前に以下のツールをグローバルにインストールする必要があります:
dotnet tool install -g Volo.Abp.Cli
ABP ソリューションの作成#
空のディレクトリでターミナルを開き、以下のコマンドを実行すると、TodoApp という名前のレイヤードソリューションが作成されます:
以下のコマンドでは、app テンプレートを指定し、デフォルトのフロントエンド UI を使用せず、ORM は EF Core(デフォルト)、データベースは MySQL(デフォルトは MS SQL)、接続文字列を指定しています。
abp new TodoApp --template app --ui none --database-provider ef --database-management-system MySQL --connection-string "Server=localhost;Database=TodoApp;Uid=root;Pwd=123456;"
app
テンプレートのレイヤードアーキテクチャと依存関係は以下の通りで、ドメイン駆動設計 のデザインパターンを採用しています。プロジェクト構造と責任についての詳細は、こちらを参照してください:
new
コマンドはデフォルトで --template app --ui mvc
を使用し、Application Startup Template を使用して ASP.NET Core MVC フレームワークが提供するデフォルトの UI 実装を選択します。ABP は合計で 6 種類の Startup Template を提供しており、それぞれ app、app-nolayers、module、console、WPF、MAUI です。
--ui none
を指定すると、デフォルトの動作として現在のディレクトリに aspnet-core
サブディレクトリが作成されますが、気に入らない場合は自分で処理できます。
new
コマンドの後に --output-folder
または --create-solution-folder
パラメータを追加して、出力ディレクトリやソリューションフォルダの作成を指定できます。
ABP CLI の new コマンドのパラメータ説明について詳しく知ることをお勧めします。公式にはいくつかのサンプルも提供されています。
作成時に Find the following template in your cache directory
というメッセージが表示された場合、ネットワーク経由で最新の ABP Startup Template バージョンを取得できないことを意味します。その場合、ABP CLI はローカルキャッシュ内を検索します。キャッシュが 1 つでも複数でも、--version x.x.x
を指定して使用したいバージョン番号を指定する必要があります。このロジックのソースコードはこちらを参照してください。ローカルキャッシュの Startup Template ディスクディレクトリはこちらを参照してください。つまり、%USERPROFILE%\.abp\templates
ディレクトリです。
データベースの作成#
TodoApp.DbMigrator
プロジェクトのディレクトリでターミナルを開き、dotnet run
コマンドを実行してデータベースを作成します:
プロジェクト作成後にデータベースを変更したい場合は、こちらを参照してください。
アプリケーションの実行#
ABP 公式は、開発前に TodoApp.Web
プロジェクトを一度実行して、プロジェクトが正常にコンパイルおよび実行できることを確認することを推奨しています。しかし、以前に new
コマンドでソリューションを作成する際に --ui none
パラメータを使用したため、.Web
プロジェクトは存在しません。そこで、TodoApp.HttpApi.Host
ディレクトリでターミナルを開き、dotnet run
コマンドを実行します。
最後に、ブラウザで https://localhost:44398 にアクセスして、正常に動作するか確認します。ABP はデータベースを作成する際に、デフォルトのユーザーを生成し、ユーザー名は admin
、パスワードは 1q2w3E*
です。
Swagger 統合について、デフォルトの ABP インターフェースを非表示にしたり、OAUTH 認証を有効にしたりする方法については、こちらを参照してください。デフォルトの ABP インターフェースを非表示にすると、Swagger 上の Default Endpoints が確かに消えますが、下の Schemas は残ります。これは実際には期待される結果です。こちらを参照してください。
すべてが整ったので、次はコーディングを開始できます。
ドメイン層#
これはソリューションの **ドメイン層(Domain Layer)** で、主に entities, aggregate roots、domain services、value objects、repository interfaces およびその他のドメインオブジェクト(domain objects)を含みます。
TodoApp.Domain プロジェクトに TodoItem クラスを作成します。これはエンティティ(Entity)であり、リレーショナルデータベーステーブルにマッピングされるオブジェクトです:
using System;
using Volo.Abp.Domain.Entities;
namespace TodoApp
{
public class TodoItem : BasicAggregateRoot<Guid>
{
public string Text { get; set; }
}
}
[BasicAggregateRoot](https://docs.abp.io/en/abp/latest/Entities#basicaggregateroot-class)
は、根エンティティ(root entity)を作成するための最も簡単な基底クラスです(並行処理の競合や追加の拡張属性を処理する必要がない場合に使用)。そのジェネリックパラメータに渡される Guid
は、このエンティティの主キー(Id
)を表します。
データベース統合#
次に、Entity Framework Core を設定します。
マッピング設定#
TodoApp.EntityFrameworkCore/EntityFrameworkCore ディレクトリ内の TodoAppDbContex
クラスを開き、DbSet プロパティを追加します:
public DbSet<TodoItem> TodoItems { get; set; }
次に、このクラスの OnModelCreating
関数内に TodoItem
エンティティのマッピング(データベーステーブル)コードを追加します:
using Humanizer;
...
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Include modules to your migration db context */
builder.ConfigurePermissionManagement();
...
/* Configure your own tables/entities inside here */
builder.Entity<TodoItem>(builder =>
{
builder.ToTable($"{TodoAppConsts.DbTablePrefix}TodoItem".Pluralize());
});
}
[Pluralize](https://github.com/Humanizr/Humanizer#pluralize)
関数は Humanizer.Core.zh-CN NuGet パッケージから来ており、TodoApp.Domain プロジェクトにインストールするのが適切です。これは最も下層のプロジェクトであり、他のプロジェクトはそれに依存しているためです。この関数の役割は、「不規則名詞や不可算名詞を考慮して、提供された入力を複数形に変換する」ことです。これにより、単語の複数形を誤って書くことを避けることができますが、もちろんこれは細かい点です。
コードファーストマイグレーション#
私たちが作成したこのソリューションは、EF Core の Code First Migrations を使用しています。上記でデータベースマッピング設定を変更したため、新しいマイグレーションを作成し、変更をデータベースに適用する必要があります。
TodoApp.EntityFrameworkCore プロジェクトのディレクトリでターミナルを開き、以下のコマンドを実行します:
dotnet ef migrations add Added_TodoItem
これにより、プロジェクトに新しいマイグレーションクラスが追加されます:
その後、同じターミナルで以下のコマンドを実行して、変更をデータベースに適用します:
dotnet ef database update
データベースを確認すると、このテーブルが正常に作成されているはずです:
アプリケーション層#
アプリケーションサービス は、一連の関連するユースケース(use cases)を実行するために使用され、ドメインロジックをプレゼンテーション層に公開し、DTO を入出力パラメータとして使用します(オプション)。これは三層アーキテクチャのサービス層と理解できます。私たちは以下のユースケースを実行する必要があります:
- タスクのリストを取得
- 新しいタスクを作成
- 既存のタスクを更新
- 既存のタスクを削除
アプリケーションサービスインターフェース#
まず、アプリケーションサービスのインターフェースを定義します。TodoApp.Application.Contracts プロジェクトに ITodoAppService
インターフェースを作成します:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace TodoApp
{
public interface ITodoAppService : IApplicationService
{
Task<List<TodoItemDto>> GetListAsync();
Task<TodoItemDto> CreateAsync(string text);
Task<TodoItemDto> UpdateAsync(Guid id, string text);
Task DeleteAsync(Guid id);
}
}
ここでは簡便さのため、入出力パラメータを DTO にカプセル化していません。
データ転送オブジェクト#
GetListAsync、CreateAsync、UpdateAsync 関数は TodoItemDto オブジェクトを返します。ApplicationService の入出力パラメータは通常、エンティティではなく DTO です。一般的に、TodoApp.Application.Contracts プロジェクト内で DTO クラスを定義します。上記で使用した TodoItemDto
を作成します:
using System;
namespace TodoApp
{
public class TodoItemDto
{
public Guid Id { get; set; }
public string Text { get; set; }
}
}
アプリケーションサービスの実装#
TodoApp.Application プロジェクトに TodoAppService
クラスを作成します:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
namespace TodoApp
{
public class TodoAppService : TodoAppAppService, ITodoAppService
{
private readonly IRepository<TodoItem, Guid> _todoItemRepository;
public TodoAppService(IRepository<TodoItem, Guid> todoItemRepository)
{
_todoItemRepository = todoItemRepository;
}
public async Task<List<TodoItemDto>> GetListAsync()
{
var items = await _todoItemRepository.GetListAsync();
return items
.Select(item => new TodoItemDto
{
Id = item.Id,
Text = item.Text
}).ToList();
}
public async Task<TodoItemDto> CreateAsync(string text)
{
var todoItem = await _todoItemRepository.InsertAsync(
new TodoItem { Text = text }
);
return new TodoItemDto
{
Id = todoItem.Id,
Text = todoItem.Text
};
}
public async Task<TodoItemDto> UpdateAsync(Guid id, string text)
{
var item = await _todoItemRepository.SingleOrDefaultAsync(x => x.Id.Equals(id));
if (item == null) throw new EntityNotFoundException();
item.Text = text;
var newItem = await _todoItemRepository.UpdateAsync(item);
return new TodoItemDto
{
Id = id,
Text = newItem.Text
};
}
public async Task DeleteAsync(Guid id)
{
await _todoItemRepository.DeleteAsync(id);
}
}
}
ABP はエンティティにデフォルトのジェネリック リポジトリ を提供しています。
自分で定義したアプリケーションサービスの実装クラスは、ABP が提供する ApplicationService クラスを継承する必要があります。これにより、ABP は規約に従って自動的にアプリケーションサービスの API コントローラーを生成し、冗長なコードを削減します(一般的にコントローラーはアプリケーションサービス内で公開されたドメインロジックを単純に呼び出すだけです)。自動生成された API コントローラーについては、こちらを参照してください。
エンティティに対して単純な CRUD 操作を行う場合は、ABP が提供する CRUD アプリケーションサービス を参考にして、さらに冗長なコードを削減できます。
もちろん、エンティティと DTO の間の型変換については、こちらを参照してください。
最後に、TodoApp.HttpApi.Host プロジェクトを実行すると、Swagger ページに上記で実装した CRUD アプリケーションサービスが自動生成された API が表示されます。
まとめ#
全体として、ABP は確かに非常に包括的で、Java の Spring Boot や Node.js の NestJS フレームワークに似ており、フレームワーク自体を超えた、より完全で成熟したソリューションです。これは確かに開発効率を向上させ、コード規範を統一し、引き継ぎコストを削減しますが、私は初心者には ASP.NET Core のドキュメントを多く参照してから、進化版の ABP フレームワークを使用することをお勧めします。