🗂️

Avanade Beef のレイヤー構造を理解する

に公開

はじめに

Avanade Beef (以下、Beef) は ASP.NET Core をベースとする Web API の自動生成ツールです。

https://github.com/Avanade/Beef

概要については以下のスライドもご覧ください。

Beef を理解するには、レイヤーの構造を把握することが非常に重要です。基本的には以下の図に示されていますが、英語なので分かりにくい場合もあるため、概要を解説します。

なお今回の記事では、自動生成されるコードの例は見やすさのために一部改変しています。

サービス インターフェイス層 (XxxController)

サービス インターフェイス層は、ドメイン ロジックのファサード (外観) となるレイヤーです。Api プロジェクトに XxxController という名前のクラスが作成されます。これは ASP.NET Core MVC のコントローラーと同等です。このクラスは外部からパラメーターを受け取り、下層のドメインロジック (XxxManager) を呼び出し、HTTP ステータスコードおよび結果のコンテンツ (JSON) を返します。メソッドを追加してカスタマイズ (たとえば JSON ではなく XML を返すなど) が可能です。

以下は自動生成されるコードの例です。

namespace Foo.Bar.Api.Controllers
{
    [Route("persons")]
    public partial class PersonController : ControllerBase
    {
        private readonly IPersonManager _manager;

        public PersonController(IPersonManager manager)
            { _manager = Check.NotNull(manager, nameof(manager)); PersonControllerCtor(); }

        [HttpGet("{id}")]
        [ProducesResponseType(typeof(Person), (int)HttpStatusCode.OK)]
        [ProducesResponseType((int)HttpStatusCode.NotFound)]
        public IActionResult Get(Guid id) =>
            new WebApiGet<Person?>(this, () => _manager.GetAsync(id),
                operationType: OperationType.Read, statusCode: HttpStatusCode.OK, alternateStatusCode: HttpStatusCode.NotFound);
    }
}

ドメイン ロジック層 (XxxManager)

ドメインロジック層は、ビジネス ドメインやワークフロー ロジックを提供するレイヤーです。Business プロジェクトに XxxManager という名前のクラスが作成されます。このクラスは必要なクリーンアップ (文字列や日時の Trim など) を行い、下層のサービスオーケストレーション (XxxDataSvc) を呼び出します。アイテムの作成時に ID となる GUID の生成も行います。

以下は自動生成されるコードの例です。

namespace Foo.Bar.Business
{
    public partial class PersonManager : IPersonManager
    {
        private readonly IPersonDataSvc _dataService;

        public PersonManager(IPersonDataSvc dataService, IGuidIdentifierGenerator guidIdentifierGenerator)
            { _dataService = Check.NotNull(dataService, nameof(dataService)); _guidIdentifierGenerator = Check.NotNull(guidIdentifierGenerator, nameof(guidIdentifierGenerator)); PersonManagerCtor(); }

        public Task<Person?> GetAsync(Guid id) => ManagerInvoker.Current.InvokeAsync(this, async () =>
        {
            Cleaner.CleanUp(id);
            await id.Validate(nameof(id)).Mandatory().RunAsync(throwOnError: true).ConfigureAwait(false);
            return Cleaner.Clean(await _dataService.GetAsync(id).ConfigureAwait(false));
        }, BusinessInvokerArgs.Read);
    }
}

サービス オーケストレーション層 (XxxDataSvc)

サービス オーケストレーション層は、データ アクセスのオーケストレーションを提供するレイヤーです。Business プロジェクトに XxxDataSvc という名前のクラスが作成されます。このクラスはデータキャッシュを検索し、キャッシュがあればその値を、キャッシュがなければ下層のデータアクセス (XxxData) を呼び出します。キャッシュの有効期間は webapisettings.json で設定できます。

以下は自動生成されるコードの例です。

namespace Foo.Bar.Business.DataSvc
{
    public partial class PersonDataSvc : IPersonDataSvc
    {
        private readonly IPersonData _data;
        private readonly IRequestCache _cache;

        public PersonDataSvc(IPersonData data, IRequestCache cache)
            { _data = Check.NotNull(data, nameof(data)); _cache = Check.NotNull(cache, nameof(cache)); PersonDataSvcCtor(); }

        public Task<Person?> GetAsync(Guid id) => DataSvcInvoker.Current.InvokeAsync(this, async () =>
        {
            var __key = new UniqueKey(id);
            if (_cache.TryGetValue(__key, out Person? __val))
                return __val;

            var __result = await _data.GetAsync(id).ConfigureAwait(false);
            return _cache.SetAndReturnValue(__key, __result);
        });
    }
}

データ アクセス層 (XxxData)

データ アクセス層は、データ ソースへのアクセス (ストアド プロシージャの呼び出しなど) を提供するレイヤーです。Business プロジェクトに XxxData という名前のクラスが作成されます。このクラスは Cosmos DB、SQL Database、OData などのデータソースを呼び出します。データを取得する場合はクエリの作成も行います。メソッドを追加してカスタマイズ (前述した以外のデータソースに接続するなど) が可能です。

以下は自動生成されるコードの例です。

namespace Foo.Bar.Business.Data
{
    public partial class PersonData : IPersonData
    {
        private readonly IEfDb _ef;
        private readonly AutoMapper.IMapper _mapper;
        private readonly IEventPublisher _evtPub;

        public PersonData(IEfDb ef, AutoMapper.IMapper mapper, IEventPublisher evtPub)
            { _ef = Check.NotNull(ef, nameof(ef)); _mapper = Check.NotNull(mapper, nameof(mapper)); _evtPub = Check.NotNull(evtPub, nameof(evtPub)); PersonDataCtor(); }

        public Task<Person?> GetAsync(Guid id) => DataInvoker.Current.InvokeAsync(this, async () =>
        {
            var __dataArgs = EfDbArgs.Create(_mapper);
            return await _ef.GetAsync<Person, EfModel.Person>(__dataArgs, id).ConfigureAwait(false);
        });
    }
}

サービス エージェント層 (XxxAgent)

サービス エージェント層は、サービス インターフェイス層の呼び出しのプロキシを提供します。主な用途はテスト プロジェクトでサービス インターフェイス (Controller) の呼び出しを簡潔にすることです。

エンティティ (DTO)

エンティティはビジネスロジックを含みませんが、ICloneable、ICopyFrom、IEquatable などの機能を提供します。

おわりに

Beef の特長は自動生成されるコードをパーシャ ルクラスを使うことで比較的自由にカスタマイズできる点です。ただし、レイヤーの構造を知らないと困ることもあります。このあたりを理解しておくとカスタマイズがしやすくなります。

Discussion