💻

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

2022/06/15に公開

はじめに

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