Fungsionalitas ujung-ke-ujung melalui pembungkus

Selama pengembangan, kami sering menjumpai situasi ketika, saat menjalankan logika bisnis apa pun, diperlukan untuk menulis log, mengaudit, dan mengirimkan peringatan. Secara umum, terapkan beberapa fungsi ujung ke ujung.



Ketika skala produksinya kecil, Anda tidak bisa terlalu bersemangat dan melakukan semua ini dengan benar dalam metode. Secara bertahap, konstruktor layanan mulai tumbuh ditumbuhi layanan masuk untuk implementasi BL dan fungsionalitas ujung ke ujung. Dan ini adalah ILogger, IAuditService, INotifiesSerice.

Saya tidak tahu tentang Anda, tetapi saya tidak suka banyak suntikan dan metode besar yang melakukan banyak tindakan dalam satu waktu.



Anda dapat mengakhiri penerapan AOP apa pun pada kode. Dalam tumpukan .NET, implementasi seperti itu dimasukkan ke dalam aplikasi Anda di tempat yang tepat, terlihat seperti keajaiban level 80, dan sering kali mengalami masalah pengetikan dan debugging.



Saya mencoba mencari jalan tengah. Jika masalah ini tidak menyelamatkan Anda, selamat datang di bawah cat.



Spoiler. Nyatanya, saya bisa menyelesaikan lebih banyak masalah daripada yang saya jelaskan di atas. Misalnya, saya dapat memberikan pengembangan BL ke satu pengembang, dan menggantung fungsionalitas ujung ke ujung dan bahkan validasi data yang masuk - ke yang lain pada waktu yang sama .



Dan dekorator dan add-in DI membantu saya dengan ini. Seseorang akan mengatakan lebih lanjut bahwa ini adalah proxy, saya akan dengan senang hati membahas ini di komentar.



Jadi apa yang saya inginkan sebagai pengembang?



  • Saat menerapkan BL, jangan terganggu oleh fungsi kiri.
  • Untuk dapat menguji BL hanya dalam tes unit. Dan saya tidak suka melakukan 100.500 olok-olok untuk menonaktifkan semua fungsi tambahan. 2-3 - oke, tapi saya tidak mau.
  • Pahami apa yang terjadi tanpa memiliki 7 bentang di dahi Anda. :)
  • Mampu mengelola masa pakai layanan dan setiap pembungkusnya SECARA TERPISAH!


Apa yang saya inginkan sebagai desainer dan pemimpin tim?



  • Untuk dapat menguraikan tugas dengan cara yang paling optimal dan dengan koherensi paling sedikit, sehingga pada saat yang sama dimungkinkan untuk melibatkan sebanyak mungkin pengembang pada tugas yang berbeda dan pada saat yang sama sehingga mereka menghabiskan waktu sesedikit mungkin untuk penelitian (jika pengembang perlu mengembangkan BL, dan pada saat yang sama memikirkan tentang apa dan bagaimana mengamankan , dia akan menghabiskan lebih banyak waktu untuk penelitian. Begitu juga dengan setiap BL. Jauh lebih mudah untuk mengambil catatan audit dan menjejalkannya selama proyek).
  • Pertahankan urutan kode dieksekusi secara terpisah dari pengembangannya.


Antarmuka ini akan membantu saya dalam hal ini.



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


Anda bisa menggunakan sesuatu seperti ini
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;
    }
}




Dengan demikian, tindakan kita akan lebih dalam.



wrapper1
    wrapper2
        service
    end wrapper2
end wrapper1


Tapi tunggu. Ini sudah ada dalam OOP dan disebut warisan. : D



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


Saat saya membayangkan bahwa fungsionalitas ujung ke ujung tambahan akan muncul, yang harus diterapkan di seluruh aplikasi di tengah atau untuk menukar yang sudah ada, rambut saya berdiri tegak di punggung saya.



Tapi kemalasan saya bukanlah alasan yang baik. Alasan yang bagus adalah bahwa akan ada masalah besar ketika unit menguji fungsionalitas di kelas Wrapper1 dan Wrapper2, sedangkan dalam contoh saya NextDelegate dapat dengan mudah dibuang. Selain itu, layanan dan setiap pembungkus memiliki seperangkat alat mereka sendiri yang dimasukkan ke dalam konstruktor, sedangkan dengan pewarisan, pembungkus terakhir harus memiliki alat yang tidak diperlukan untuk meneruskannya ke orang tua mereka.



Jadi, pendekatannya diterima, tinggal mencari tahu di mana, bagaimana dan kapan menetapkan NextDelegate.



Saya memutuskan bahwa solusi paling logis adalah melakukan ini di mana saya mendaftarkan layanan. (Startup.sc, default).



Ini adalah tampilannya di versi dasar.
            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;
            });




Secara umum, semua persyaratan terpenuhi, tetapi masalah lain muncul - bersarang.



Masalah ini dapat diatasi dengan kekerasan atau rekursi. Tapi di bawah tenda. Secara lahiriah, semuanya harus terlihat sederhana dan dapat dimengerti.



Inilah yang berhasil saya raih
            services.AddDecoratedScoped<IService, Service>(builder =>
            {
                builder.AddSingletonDecorator<Wrapper1>();
                builder.AddTransientDecorator<Wrapper2>();
                builder.AddScopedDecorator<Wrapper3>();
            });




Dan metode penyuluhan ini membantu saya dalam hal ini.



Dan metode penyuluhan dan pembuat pemandangan ini membantu saya dalam hal ini.
    /// <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);
        }
    }




Sekarang gula untuk fungsionalis



Sekarang gula untuk fungsionalis
    /// <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));
    }




Masih ada sedikit keajaiban yang tersisa. Yaitu, tujuan properti NextDelegate. Tidak segera jelas apa itu dan bagaimana menggunakannya, tetapi programmer berpengalaman akan menemukannya, tetapi seorang yang tidak berpengalaman perlu menjelaskannya sekali. Ini seperti DbSets di DbContext.



Saya tidak meletakkannya di hub git. Tidak banyak kode, ini sudah digeneralisasikan, jadi Anda dapat menariknya langsung dari sini.



Sebagai kesimpulan, saya tidak ingin mengatakan apa-apa.



All Articles