ثبت پیاده سازیهای متعدد یک اینترفیس در سیستم تزریق وابستگیهای NET Core.
داشتن چندین پیاده سازی از یک اینترفیس، شاید یکی از اهداف اصلی وجود آنها باشد. برای نمونه قرار داد ارسال پیامی را در برنامه، مانند IMessageService به صورت زیر طراحی و سپس بر اساس آن، دو پیاده سازی ارسال پیامها را از طریق ایمیل و SMS، اضافه میکنیم:
namespace CoreIocServices { public interface IMessageService { void Send(string message); } public class EmailService : IMessageService { public void Send(string message) { // ... } } public class SmsService : IMessageService { public void Send(string message) { //todo: ... } } }
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<IMessageService, EmailService>(); services.AddTransient<IMessageService, SmsService>(); }
using CoreIocServices; using Microsoft.AspNetCore.Mvc; namespace CoreIocSample02.Controllers { public class MessagesController : Controller { private readonly IMessageService _emailService; private readonly IMessageService _smsService; public MessagesController(IMessageService emailService, IMessageService smsService) { _emailService = emailService; _smsService = smsService; } } }
همانطور که مشاهده میکنید، هر دو پارامتر، با وهلهای از آخرین سرویس معرفی شدهی از نوع IMessageService مقدار دهی شدهاند؛ یعنی SmsService در اینجا. در ادامه روشهای مختلفی را برای رفع این مشکل بررسی میکنیم.
روش اول: سیستم تزریق وابستگیهای NET Core. از سازندههای IEnumerable پشتیبانی میکند
برای مدیریت دریافت سرویسهایی که از یک اینترفیس مشتق شدهاند، خود NET Core. بدون نیاز به تنظیمات اضافهتری، روش تزریق IEnumerableها را در سازندههای کلاسها پشتیبانی میکند:
using System.Collections.Generic; using CoreIocServices; using Microsoft.AspNetCore.Mvc; namespace CoreIocSample02.Controllers { public class MessagesController : Controller { private readonly IEnumerable<IMessageService> _messageServices; public MessagesController(IEnumerable<IMessageService> messageServices) { _messageServices = messageServices; }
به این ترتیب لیست وهلههای تمام سرویسهایی از نوع IMessageService در اختیار ما قرار گرفتهاست و برای اهدافی مانند پیاده سازی الگوهایی در ردهی chain of responsibility و یا الگوی استراتژی، مفید است. در این حالت اگر نیاز به سرویس از نوع خاصی وجود داشت، میتوان از روش زیر استفاده کرد:
var emailService = _messageServices.OfType<EmailService>().First();
var messageServices = serviceProvider.GetServices<IMessageService>();
روش دوم: دریافت شرطی وهلههای مورد نیاز با «دخالت در مراحل وهله سازی اشیاء توسط IoC Container»
روش «دخالت در مراحل وهله سازی اشیاء توسط IoC Container» را در قسمت قبل بررسی کردیم. یکی دیگر از مثالهایی را که در این مورد میتوان ارائه داد، شرطی کردن بازگشت وهلهها است که برای سناریوی مطلب جاری بسیار مفید است:
الف) برای شرطی کردن بازگشت وهلهها، بهتر است این شرط را بجای رشتهها و یا اعدادی خاص، بر اساس یک enum مشخص انجام دهیم:
namespace CoreIocServices { public enum MessageServiceType { EmailService, SmsService }
ب) سپس هر دو سرویس مشتق شدهی از یک اینترفیس را به صورت معمولی ثبت میکنیم:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<EmailService>(); services.AddTransient<SmsService>();
ج) اکنون میتوان مرحلهی شرطی کردن بازگشت این وهلهها را به صورت زیر، با دخالت در مراحل وهله سازی اشیاء، پیاده سازی کرد:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<EmailService>(); services.AddTransient<SmsService>(); services.AddTransient<Func<MessageServiceType, IMessageService>>(serviceProvider => key => { switch (key) { case MessageServiceType.EmailService: return serviceProvider.GetRequiredService<EmailService>(); case MessageServiceType.SmsService: return serviceProvider.GetRequiredService<SmsService>(); default: throw new NotImplementedException($"Service of type {key} is not implemented."); } });
د) مرحلهی آخر کار، روش استفادهی از این امضایء ویژهی Lazy load شدهاست:
namespace CoreIocSample02.Controllers { public class MessagesController : Controller { private readonly Func<MessageServiceType, IMessageService> _messageServiceResolver; public MessagesController(Func<MessageServiceType, IMessageService> messageServiceResolver) { _messageServiceResolver = messageServiceResolver; }
public IActionResult Index() { var emailService = _messageServiceResolver(MessageServiceType.EmailService); //use emailService ... return View(); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: CoreDependencyInjectionSamples-07.zip
20.اسکریپت در پایین صفحه
21.CDN
- سرعت اجرا (سرویس گیرنده): gRPC تقریبا 5 برابر سریعتر از REST پیامها را دریافت کرد.
- حجم دیتای انتقالی (سرویس گیرنده): حجم پیامهای gRPC تقریبا نصف REST بود.
معماری اطلاعات یا Information Architecture و یا به اختصار IA در یک تیم توسعه نرمافزار، یک وظیفه پایه و اساسی است که معمولا بین طراحان، توسعه دهندگان و یا طراحان استراتژی محتوا تقسیم میگردد. اما صرف نظر از اینکه چه کسی در یک تیم آن را بر عهده میگیرد، IA تخصص خاص خود را نیازمند است که این تخصص که شامل ابزارها و شاخصها و منابعی است که باید به درستی تحقیق و گردآوری گردند. در این مقاله قصد داریم تا به این امر بپردازیم که واقعا IA چیست؟ و چرا یک جنبهی مهم و ارزشمند در طراحی فرآیندهای user experience به شمار میرود؟
Information Architecture چیست؟
بیان کردن یک تعریف دقیق برای IA کمی پیچیدهتر از تعاریف دیگری همچون content strategy و یا interaction design به نظر میرسد. بر خلاف content strategy که به وسیله طراحان استراتژی محتوا و یا interaction design که توسط طراحان UI/UX انجام میگیرد، IA به ندرت در زمرهی یک کار جداگانه قرار میگیرد. اما با این وجود، این کار در مهندسی نرم افزار بسیار با ارزش و لازم محسوب میگردد. (منبع)
بر اساس تعریف ویکیپدیا، IA اینگونه تعریف میگردد: "معماری اطلاعات عبارت است از طراحی ساختاری سامانههای اشتراک اطلاعات، که با هدف یافت پذیری و کاربرد پذیری انجام میشود."
در فرهنگ معماری اطلاعات، مفهوم یافتپذیری یا Findability به زبان ساده، شاخصی است که قابلیت یافت شدن و مکانیابی یک مجموعه اطلاعات را محک میزند. به عنوان مثال سامانههای اطلاعرسانی مانند وب سایتها باید به گونهای طراحی شوند که مردم در هر سطحی، به سادگی بتوانند اطلاعات مورد نظر خود را پیدا کنند.
مفهوم کاربردپذیری یا Usability ، شاخصهای است که میزان سهولت کاربری یک ابزار را نشان میدهد. این تعریف را میتوان به این صورت کامل نمود: "میزانی که یک محصول میتواند توسط کاربران خاصی برای رسیدن به هدفی مشخص مورد استفاده قرار گیرد و در حین استفاده، ضمن داشتن اثربخشی و کارآیی، رضایت کاربر را در زمینه مورد استفاده تامین کند." به عنوان مثال هنگامیکه شما قصد خرید اینترنتی را داشته باشید، در درجه اول مفهوم یافتپذیری برای شما پررنگ خواهد شد و پس از آن با انجام خرید حس کاربردپذیری فروشگاه خواهد بود که برای دفعات بعدی شما را به آن وبسایت هدایت خواهد کرد.
بنا بر آنچه که گفته شد، مفهوم یافتپذیری مقدم بر کاربردپذیری است. یعنی اگر کاربر نتواند آنچه را که به دنبال آن است بیابد، هرگز فرصت استفاده از آنرا نخواهد داشت. به علاوه اگر یافتپذیری محقق شود، کاربردپذیری هم بهبود خواهد یافت، چرا که مردم میتوانند به سرعت و سهولت به آنچه که نیاز دارند دست یابند. ذکر این نکته نیز حائز اهمیت است که موتورهای جست و جو، تنها وظیفهی هدایت کاربران را به وبسایت هدف خواهند داشت. اما وظیفه یافت پذیری، با بازدید کاربران از وبسایت آغاز میگردد. به نوعی میتوان گفت که یافتپذیری میکوشد به مردم این امکان را بدهد تا در هنگام گذار در وبسایت، به اطلاعاتی که نیاز دارند دست یابند.
معماری اطلاعات یکی از مهمترین مراحل توسعهی نرمافزار، به خصوص توسعهی نرمافزارهای تحت وب به شمار میرود. در حقیقت با انجام معماری اطلاعات دقیق، میتوان مواردی از قبیل رسیدن به اهداف تعیین شده، کم کردن هزینهی نگهداری و به روز رسانی، افزایش کارآیی و در نهایت کم شدن ریسکها را مدیریت نمود.
بر اساس آنچه که در وبسایت UXmatters و webmonkey گفته شده است، IA در شش مرحله صورت میگیرد:
1. ارزیابی هدف کسب و کار ( Assess business intent )
2. ارزیابی هدف کاربران ( Assess user intent )
3. ارزیابی محتوا ( Assess content )
4. مدیریت محتوا ( Organize content )
5. برقرارسازی ارتباط میان اطلاعات ( Enable information relationships )
6. فراهمسازی فرآیند هدایت محتوا ( Provide navigation )
تصویر زیر که برگرفته از وبسایت SitePoint میباشد برخی از مراحل فوق را به صورت مختصر و ساده نمایش داده است.
اگر به وبسایت رسمی انجمن معماری اطلاعات سری بزنید مطالب مفیدی در زمینهی پیاده سازی IA به دست خواهید آورد.
تصویر زیر ارتباط تنگاتنگ سه مبحث Information Design ، Interaction Design و Sensorial Design را نمایش میدهد. همانطور که میبینید، محتوا در مرکزیت هر سه موضوع قرار دارد. بنابراین میتوان اینگونه استنباط کرد که محتوا اصلیترین بخش یک کسبوکار نرمافزاری را تشکیل میدهد. اما در بخش پیشین دیدیم که محتوا به تنهایی نمیتواند راهگشای نتیجهی عالی از یک نرمافزار باشد. با توجه به دو مفهوم یافتپذیری و کاربردپذیری دیدیم برای آنکه بتوانیم در تولید نرمافزار بهترین باشیم، بایستی در قدم اول مفهوم یافتپذیری را رعایت نماییم. به زبان ساده باید هر چیزی را در جای مورد نظر خود قرار دهیم تا کاربر در مدت زمان خیلی کمی بتواند به آن دسترسی داشته باشد.
همانطور که در تصویر فوق ملاحظه میکنید دو اصطلاح شاید نا آشنای دیگر نیز آورده شده است، Interaction Design و Sensorial Design. در مقالهای دیگر به آنها خواهیم پرداخت.
آشنایی با Jaeger
برای پیاده سازی distributed tracing، میتوانیم از ابزار متن باز و محبوب Jaeger (با تلفظ یِگِر) که ابتدا توسط شرکت Uber منتشر شد، استفاده کنیم. نحوه کارکرد Jaeger بصورت زیر میباشد:
سادهترین روش برای راهاندازی Jager، استفاده از داکر ایمیج All in one که شامل ماژول های agent ، collector، query و ui است. پورت 6831 مربوط به agent و پورت 16686 مربوط به ui میباشد. برای جزئیات مربوط به ماژولهای مختلف از این لینک استفاده کنید.
docker run -d -p 6831:6831/udp -p 6832:6832/udp -p 14268:14268 -p 14250:14250 -p 16686:16686 -p 5778:5778 --name jaeger jaegertracing/all-in-one:latest
بعد از اجرای دستور بالا، اطلاعات مربوط به سرویسها و trace ها در ماژول Jager UI با آدرس http://localhost:16686 قابل مشاهده است.
جهت استفاده از Jaeger از پروژه تستی که شامل دو سرویس User و Gateway میباشد، استفاده میکنیم. در سرویس User، متد AddUser در صورت عدم وجود کاربر در دیتابیس، اطلاعات کاربر از گیتهاب را دریافت و در دیتابیس ذخیره میکند. سرویس Gateway از Ocelot برای مسیردهی درخواستها استفاده میکند. برای آشنایی با ocelot این پست را مطالعه نمایید.
public async Task<ApiResult<Models.User>> AddUserAsync(string username) { var result = new ApiResult<Models.User>(); var user = await _applicationDbContext.Users.FirstOrDefaultAsync(x => x.Login == username); if (user is null) { try { var url = string.Format(_appConfig.Github.ProfileUrl, username); var apiResult = await _httpClient.GetStringAsync(url); var userDto = JsonSerializer.Deserialize<UserDto>(apiResult); user = _mapper.Map<Models.User>(userDto); await _applicationDbContext.Users.AddAsync(user); await _applicationDbContext.SaveChangesAsync(); result.Result = user; result.Message = "User successfully Created"; return result; } catch (Exception e) { result.Message = "User not found"; return result; } } result.Message = "User already exist"; result.Result = user; return result; }
برای ثبت Trace مربوط به درخواستها در Jaeger ، بعد از نصب پکیجهای Jaeger و OpenTracing.Contrib.NetCore در هر دو سرویس، در کانفیگ هریک از سرویسها مورد زیر را اضافه میکنیم:
"JaegerConfig": { "Host": "localhost", "Port": 6831, "IsEnabled": true, "SamplingRate": 0.5 }
و برای اضافه شدن tracer به برنامه از متد الحاقی زیر استفاده میکنیم:
public static class Extensions { public static void AddJaeger(this IServiceCollection services, IConfiguration configuration) { var config = configuration.GetSection("JaegerConfig").Get<JaegerConfig>(); if (!(config?.IsEnabled ?? false)) return; if (string.IsNullOrEmpty(config?.Host)) throw new Exception("invalid JaegerConfig"); services.AddSingleton<ITracer>(serviceProvider => { string serviceName = Assembly.GetEntryAssembly()?.GetName().Name; ILoggerFactory loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>(); var sampler = new ProbabilisticSampler(config.SamplingRate); var reporter = new RemoteReporter.Builder() .WithLoggerFactory(loggerFactory) .WithSender(new UdpSender(config.Host, config.Port, 0)) .WithFlushInterval(TimeSpan.FromSeconds(15)) .WithMaxQueueSize(300) .Build(); ITracer tracer = new Tracer.Builder(serviceName) .WithLoggerFactory(loggerFactory) .WithSampler(sampler) .WithReporter(reporter) .Build(); GlobalTracer.Register(tracer); return tracer; }); services.AddOpenTracing(); } }
برای ثبت traceها استراتژیهای متفاوتی وجود دارد. در اینجا از ProbabilisticSampler استفاده شدهاست که در سازندهی آن میتوان درصد ثبت Traceها را مقدار دهی کرد. در نهایت این متد الحاقی را در Startup اضافه میکنیم:
builder.Services.AddJaeger(builder.Configuration);
بعد از اجرای پروژه و فراخوانی https://localhost:6000/gateway/Users/Add ، سرویس Gateway، درخواست را به سرویس User ارسال میکند و این سرویسها در Jaeger UI قابل مشاهده هستند.
جهت مشاهده trace ها ، سرویس مورد نظر را انتخاب و روی Find Traces کلیک کنید. با کلیک روی Trace مورد نظر، جزئیات فعالیت هایی مثل فراخوانی سرویس و مراجعه به دیتابیس قابل مشاهده است.
برای اضافه کردن لاگ سفارشی به یک span، میتوان از اینترفیس ITracer استفاده کرد:
private readonly IUserService _userService; private readonly ITracer _tracer; public UsersController(IUserService userService, ITracer tracer) { _userService = userService; _tracer = tracer; }
[HttpPost] public async Task<ActionResult> AddUser(AddUserDto model) { var actionName = ControllerContext.ActionDescriptor.DisplayName; using var scope = _tracer.BuildSpan(actionName).StartActive(true); scope.Span.Log($"Add user log username: {model.Username}"); return Ok(await _userService.AddUserAsync(model.Username)); }
کدهای مربوط به این مطلب در اینجا قابل دسترسی است.
سوال:
در صورتی که بخوام از سرویس WCF روی یک سرور جدا استفاده کنم چطور با WinApp خودم به سرویسهای WCF Server وصل شم؟ بدون واسطه Web App ?
و اینکه سرعت واکشی اطلاعات ( رکوردهای زیاد 2 ، 3 هرارتا یا بیشتر ) چگونه هست؟ با WCF و WinApp واسه نرم افزارهای سازمانی که تحت شبکه محلی و وایرلس داخل شهری هستن بخوام ازین روش استفاده شه آیا در بلند مدت با افزایش رکوردها به مشکل برخورد نمیکنم از نظر کار با دیتابیس و داده ها؟