ラッパーを介したエンドツーエンド機能

開発中に、ビジネスロジックを実行するときに、ログの書き込み、監査、およびアラートの送信が必要になる状況に遭遇することがよくあります。一般に、いくつかのエンドツーエンドの機能を実装します。



生産規模が小さい場合は、熱心になりすぎて、この方法ですべてを正しく行うことはできません。徐々に、サービスのコンストラクターは、BLおよびエンドツーエンド機能の実装のための着信サービスで大きくなり始めます。そしてこれはILogger、IAuditService、INotizesSericeです。

あなたのことはわかりませんが、一度に多くのアクションを実行する多くの注射や大規模な方法は好きではありません。



コード上で任意のAOP実装を終了できます。 .NETスタックでは、このような実装はアプリケーションの適切な場所に挿入され、レベル80の魔法のように見え、入力とデバッグの問題が発生することがよくあります。



私は中間点を見つけようとしました。これらの問題があなたを免れなかったならば、猫の下で歓迎してください。



ネタバレ。実際、私は上記よりも少し多くの問題を解決することができました。たとえば、BL開発を1人の開発者に提供し、エンドツーエンドの機能や受信データの検証を同時に別の開発者に提供することができます



そして、デコレータとDIアドオンがこれを助けてくれました。誰かがこれは代理人だとさらに言うでしょう、私はコメントでこれについて議論させていただきます。



では、開発者として何が欲しいのでしょうか?



  • BLを実装するときは、左側の機能に気を取られないでください。
  • ユニットテストでBLのみをテストできるようにする。そして、私はすべての補助機能を無効にするために100500モックをするのは好きではありません。2-3-大丈夫ですが、私はしたくありません。
  • 額に7つのスパンがなくても何が起こっているのかを理解します。:)
  • サービスとその各ラッパーの存続期間を個別に管理できるようになります。


デザイナーやチームリーダーとして何が欲しいですか?



  • タスクを最適な方法で一貫性を最小限に抑えて分解できるようにすることで、同時にさまざまなタスクにできるだけ多くの開発者を関与させ、同時に研究に費やす時間をできるだけ少なくすることができます(開発者がBLを開発する必要がある場合、同時に何をどのように保護するかを考える必要がある場合) 、彼は研究により多くの時間を費やすでしょう。そして、BLの各部分についてもそうです。監査記録を取り、プロジェクト全体でそれらを詰め込む方がはるかに簡単です)。
  • コードの開発とは別に、コードが実行される順序を維持します。


このインターフェースはこれを助けてくれます。



    /// <summary>
    ///       .
    /// </summary>
    /// <typeparam name="T">  . </typeparam>
    public interface IDecorator<T>
    {
        /// <summary>
        ///        .
        /// </summary>
        Func<T> NextDelegate { get; set; }
    }


あなたはこのようなものを使うことができます
interface IService
{
    Response Method(Request request);
}

class Service : IService
{
    public Response Method(Request request)
    {
        // BL
    }
}

class Wrapper : IDecorator<IService>, IService
{
    public Func<IService> NextDelegate { get; set; }

    public Response Method(Request request)
    {
        // code before
        var result = NextDelegate().Method(request);
        // code after
        return result;
    }
}




したがって、私たちの行動はより深くなります。



wrapper1
    wrapper2
        service
    end wrapper2
end wrapper1


しかし、ちょっと待ってください。これはすでにOOPにあり、継承と呼ばれます。:D



class Service {}
class Wrapper1: Service {}
class Wrapper2: Wrapper1 {}


追加のエンドツーエンド機能が表示されることを想像したように、これはアプリケーション全体の途中で実装するか、既存の機能を交換する必要があるため、背中の毛が逆立っていました。



しかし、私の怠惰は正当な理由ではありません。正当な理由は、Wrapper1クラスとWrapper2クラスの機能をユニットテストするときに大きな問題が発生するのに対し、私の例ではNextDelegateを単純にモックすることができるためです。さらに、サービスと各ラッパーには、コンストラクターに挿入される独自のツールセットがありますが、継承では、最後のラッパーには、それらを親に渡すために不要なツール必要です。



したがって、このアプローチは受け入れられ、NextDelegateをどこに、どのように、いつ割り当てるかを理解する必要があります。



最も論理的な解決策は、サービスを登録する場所でこれを行うことであると判断しました。(Startup.sc、デフォルト)。



これが基本バージョンでの外観です。
            services.AddScoped<Service>();
            services.AddTransient<Wrapper1>();
            services.AddSingleton<Wrapper2>();
            services.AddSingleton<IService>(sp =>
            {
                var wrapper2 = sp.GetService<Wrapper2>();
                wrapper2.NextDelegate = () =>
                {
                    var wrapper1 = sp.GetService<Wrapper1>();
                    wrapper1.NextDelegate = () =>
                    {
                        return sp.GetService<Service>();
                    };

                    return wrapper1;
                };

                return wrapper2;
            });




一般に、すべての要件が満たされましたが、別の問題が発生しました-ネスト。



この問題は、ブルートフォースまたは再帰によって解決できます。しかし、内部では。外見上、すべてがシンプルで理解しやすいように見える必要があります。



これは私が何とか達成したことです
            services.AddDecoratedScoped<IService, Service>(builder =>
            {
                builder.AddSingletonDecorator<Wrapper1>();
                builder.AddTransientDecorator<Wrapper2>();
                builder.AddScopedDecorator<Wrapper3>();
            });




そして、これらの拡張メソッドはこれを助けてくれました。



そして、これらの拡張方法と風景ビルダーはこれを助けてくれました。
    /// <summary>
    ///        .
    /// </summary>
    public static class DecorationExtensions
    {
        /// <summary>
        ///        .
        /// </summary>
        /// <typeparam name="TDefinition">  . </typeparam>
        /// <typeparam name="TImplementation">  . </typeparam>
        /// <param name="lifeTime"></param>
        /// <param name="serviceCollection">  . </param>
        /// <param name="decorationBuilder">  . </param>
        /// <returns>     . </returns>
        public static IServiceCollection AddDecorated<TDefinition, TImplementation>(
            this IServiceCollection serviceCollection, ServiceLifetime lifeTime,
            Action<DecorationBuilder<TDefinition>> decorationBuilder)
            where TImplementation : TDefinition
        {
            var builder = new DecorationBuilder<TDefinition>();
            decorationBuilder(builder);

            var types = builder.ServiceDescriptors.Select(k => k.ImplementationType).ToArray();

            var serviceDescriptor = new ServiceDescriptor(typeof(TImplementation), typeof(TImplementation), lifeTime);

            serviceCollection.Add(serviceDescriptor);

            foreach (var descriptor in builder.ServiceDescriptors)
            {
                serviceCollection.Add(descriptor);
            }

            var resultDescriptor = new ServiceDescriptor(typeof(TDefinition),
                ConstructServiceFactory<TDefinition>(typeof(TImplementation), types), ServiceLifetime.Transient);
            serviceCollection.Add(resultDescriptor);

            return serviceCollection;
        }

        /// <summary>
        ///            Scoped.
        /// </summary>
        /// <typeparam name="TDefinition">  . </typeparam>
        /// <typeparam name="TImplementation">  . </typeparam>
        /// <param name="serviceCollection">  . </param>
        /// <param name="decorationBuilder">  . </param>
        /// <returns>     . </returns>
        public static IServiceCollection AddDecoratedScoped<TDefinition, TImplementation>(
            this IServiceCollection serviceCollection,
            Action<DecorationBuilder<TDefinition>> decorationBuilder)
            where TImplementation : TDefinition
        {
            return serviceCollection.AddDecorated<TDefinition, TImplementation>(ServiceLifetime.Scoped,
                decorationBuilder);
        }

        /// <summary>
        ///            Singleton.
        /// </summary>
        /// <typeparam name="TDefinition">  . </typeparam>
        /// <typeparam name="TImplementation">  . </typeparam>
        /// <param name="serviceCollection">  . </param>
        /// <param name="decorationBuilder">  . </param>
        /// <returns>     . </returns>
        public static IServiceCollection AddDecoratedSingleton<TDefinition, TImplementation>(
            this IServiceCollection serviceCollection,
            Action<DecorationBuilder<TDefinition>> decorationBuilder)
            where TImplementation : TDefinition
        {
            return serviceCollection.AddDecorated<TDefinition, TImplementation>(ServiceLifetime.Singleton,
                decorationBuilder);
        }

        /// <summary>
        ///            Transient.
        /// </summary>
        /// <typeparam name="TDefinition">  . </typeparam>
        /// <typeparam name="TImplementation">  . </typeparam>
        /// <param name="serviceCollection">  . </param>
        /// <param name="decorationBuilder">  . </param>
        /// <returns>     . </returns>
        public static IServiceCollection AddDecoratedTransient<TDefinition, TImplementation>(
            this IServiceCollection serviceCollection,
            Action<DecorationBuilder<TDefinition>> decorationBuilder)
            where TImplementation : TDefinition
        {
            return serviceCollection.AddDecorated<TDefinition, TImplementation>(ServiceLifetime.Transient,
                decorationBuilder);
        }

        /// <summary>
        ///     
        /// </summary>
        /// <typeparam name="TService"></typeparam>
        /// <param name="implType"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        private static Func<IServiceProvider, TService> ConstructDecorationActivation<TService>(Type implType,
            Func<IServiceProvider, TService> next)
        {
            return x =>
            {
                var service = (TService) x.GetService(implType);

                if (service is IDecorator<TService> decorator)
                    decorator.NextDelegate = () => next(x);
                else
                    throw new InvalidOperationException(" ");

                return service;
            };
        }

        /// <summary>
        ///         .
        /// </summary>
        /// <typeparam name="TDefinition">   . </typeparam>
        /// <param name="serviceType">   . </param>
        /// <param name="decoratorTypes">     . </param>
        /// <returns>     DI. </returns>
        private static Func<IServiceProvider, object> ConstructServiceFactory<TDefinition>(Type serviceType,
            Type[] decoratorTypes)
        {
            return sp =>
            {
                Func<IServiceProvider, TDefinition> currentFunc = x =>
                    (TDefinition) x.GetService(serviceType);
                foreach (var decorator in decoratorTypes)
                {
                    currentFunc = ConstructDecorationActivation(decorator, currentFunc);
                }

                return currentFunc(sp);
            };
        }
    }




    /// <summary>
    ///       .
    /// </summary>
    /// <typeparam name="TService">  . </typeparam>
    public class DecorationBuilder<TService>
    {
        private readonly List<ServiceDescriptor> _serviceDescriptors = new List<ServiceDescriptor>();

        /// <summary>
        ///       .
        /// </summary>
        public IReadOnlyCollection<ServiceDescriptor> ServiceDescriptors => _serviceDescriptors;

        /// <summary>
        ///      .
        /// </summary>
        /// <typeparam name="TDecorator">  . </typeparam>
        /// <param name="lifeTime">   . </param>
        public void AddDecorator<TDecorator>(ServiceLifetime lifeTime) where TDecorator : TService, IDecorator<TService>
        {
            var container = new ServiceDescriptor(typeof(TDecorator), typeof(TDecorator), lifeTime);
            _serviceDescriptors.Add(container);
        }

        /// <summary>
        ///          Scoped.
        /// </summary>
        /// <typeparam name="TDecorator">  . </typeparam>
        public void AddScopedDecorator<TDecorator>() where TDecorator : TService, IDecorator<TService>
        {
            AddDecorator<TDecorator>(ServiceLifetime.Scoped);
        }

        /// <summary>
        ///          Singleton.
        /// </summary>
        /// <typeparam name="TDecorator">  . </typeparam>
        public void AddSingletonDecorator<TDecorator>() where TDecorator : TService, IDecorator<TService>
        {
            AddDecorator<TDecorator>(ServiceLifetime.Singleton);
        }

        /// <summary>
        ///          Transient.
        /// </summary>
        /// <typeparam name="TDecorator">  . </typeparam>
        public void AddTransientDecorator<TDecorator>() where TDecorator : TService, IDecorator<TService>
        {
            AddDecorator<TDecorator>(ServiceLifetime.Transient);
        }
    }




機能主義者のための砂糖



機能主義者のための砂糖
    /// <summary>
    ///       .
    /// </summary>
    /// <typeparam name="T">   . </typeparam>
    public class DecoratorBase<T> : IDecorator<T>
    {
        /// <summary>
        ///           .
        /// </summary>
        public Func<T> NextDelegate { get; set; }

        /// <summary>
        ///           .
        /// </summary>
        /// <typeparam name="TResult">   . </typeparam>
        /// <param name="lambda">  . </param>
        /// <returns></returns>
        protected Task<TResult> ExecuteAsync<TResult>(Func<T, Task<TResult>> lambda)
        {
            return lambda(NextDelegate());
        }

        /// <summary>
        ///           .
        /// </summary>
        /// <param name="lambda">  . </param>
        /// <returns></returns>
        protected Task ExecuteAsync(Func<T, Task> lambda)
        {
            return lambda(NextDelegate());
        }
    }


, , ,



    public Task<Response> MethodAsync(Request request)
    {
        return ExecuteAsync(async next =>
        {
            // code before
            var result = await next.MethodAsync(request);
            // code after
            return result;
        });
    }


, :



    public Task<Response> MethodAsync(Request request)
    {
        return ExecuteAsync(next => next.MethodAsync(request));
    }




まだ少し魔法が残っています。つまり、NextDelegateプロパティの目的です。それが何であり、どのように使用するかはすぐにはわかりませんが、経験豊富なプログラマーはそれを見つけるでしょうが、経験の浅い人は一度それを説明する必要があります。これは、DbContextのDbSetのようなものです。



私はそれをgitハブに置きませんでした。コードはそれほど多くなく、すでに一般化されているので、ここから直接プルできます。



結論として、私は何も言いたくありません。



All Articles