وظائف شاملة عبر الأغلفة

أثناء التطوير ، غالبًا ما نواجه موقفًا ، عند تنفيذ أي منطق أعمال ، يكون مطلوبًا كتابة السجلات ، والتدقيق ، وإرسال التنبيهات. بشكل عام ، قم بتنفيذ بعض الوظائف الشاملة.



عندما يكون حجم الإنتاج صغيرًا ، لا يمكنك أن تكون متحمسًا بشكل خاص وتقوم بكل هذا بالطرق الصحيحة. تدريجيًا ، يبدأ مُنشئ الخدمة في النمو بشكل متضخم مع الخدمات الواردة لتنفيذ BL والوظائف الشاملة. وهذا هو ILogger ، IAuditService ، INotizesSerice.

لا أعرف عنك ، لكني لا أحب الكثير من الحقن والطرق الكبيرة التي تؤدي العديد من الإجراءات في وقت واحد.



يمكنك إنهاء أي تطبيق AOP على الكود. في .NET stack ، يتم إدخال مثل هذه التطبيقات في التطبيق الخاص بك في الأماكن الصحيحة ، وتبدو مثل المستوى 80 السحري ، وغالبًا ما يكون بها مشكلات في الكتابة وتصحيح الأخطاء.



حاولت إيجاد حل وسط. إذا لم تنجك هذه المشاكل ، فمرحبا بك تحت القط.



المفسد. في الواقع ، لقد تمكنت من حل مشاكل أكثر بقليل مما وصفته أعلاه. على سبيل المثال ، يمكنني إعطاء تطوير BL لمطور واحد ، وتعليق الوظائف الشاملة وحتى التحقق من صحة البيانات الواردة - إلى مطور آخر في نفس الوقت .



وقد ساعدني المصممون وإضافات DI في ذلك. سيقول شخص ما أن هذا وكيل ، وسأناقش ذلك بسعادة في التعليقات.



إذن ماذا أريد كمطور؟



  • عند تنفيذ BL ، لا تشتت انتباهك عن طريق الوظيفة اليسرى.
  • لتكون قادرًا على اختبار BL فقط في اختبارات الوحدة. وأنا لا أحب أن أفعل 100500 mocks لتعطيل جميع الوظائف المساعدة. 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 ويسمى الميراث. : د



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. ليس من الواضح على الفور ما هو عليه وكيفية استخدامه ، ولكن المبرمج المتمرس سيجده ، ولكن يحتاج الشخص عديم الخبرة إلى شرحه مرة واحدة. إنه مثل DbSets في DbContext.



لم أضعه على git hub. لا يوجد الكثير من التعليمات البرمجية ، فهي معممة بالفعل ، لذا يمكنك سحبها من هنا.



في الختام ، لا أريد أن أقول شيئًا.



All Articles