Service Layer
نقش لایهی سرویس این است که به عنوان یک مدخل ورودی به برنامه کاربردی عمل کند. در برخی مواقع این لایه را به عنوان لایهی Facadeنیز میشناسند. این لایه، دادهها را در قالب یک نوع داده ای قوی (Strongly Typed)به نام View Model، برای لایهی Presentationفراهم میکند. کلاس View Modelیک Strongly Typedمحسوب میشود که نماهای خاصی از دادهها را که متفاوت از دید یا نمای تجاری آن است، بصورت بهینه ارائه مینماید. در مورد الگوی View Modelدر مباحث بعدی بیشتر صحبت خواهم کرد.
الگوی Facadeیک Interfaceساده را به منظور کنترل دسترسی به مجموعه ای از Interfaceها و زیر سیستمهای پیچیده ارائه میکند. در مباحث بعدی در مورد آن بیشتر صحبت خواهم کرد.
کلاسی با نام ProductViewModelرا با کد زیر به پروژه SoCPatterns.Layered.Serviceاضافه کنید:
public class ProductViewModel { Public int ProductId {get; set;} public string Name { get; set; } public string Rrp { get; set; } public string SellingPrice { get; set; } public string Discount { get; set; } public string Savings { get; set; } }
برای اینکه کلاینت با لایهی سرویس در تعامل باشد باید از الگوی Request/Response Messageاستفاده کنیم. بخش Requestتوسط کلاینت تغذیه میشود و پارامترهای مورد نیاز را فراهم میکند. کلاسی با نام ProductListRequestرا با کد زیر به پروژه SoCPatterns.Layered.Serviceاضافه کنید:
using SoCPatterns.Layered.Model; namespace SoCPatterns.Layered.Service { public class ProductListRequest { public CustomerType CustomerType { get; set; } } }
در شی Responseنیز بررسی میکنیم که درخواست به درستی انجام شده باشد، دادههای مورد نیاز را برای کلاینت فراهم میکنیم و همچنین در صورت عدم اجرای صحیح درخواست، پیام مناسب را به کلاینت ارسال مینماییم. کلاسی با نام ProductListResponseرا با کد زیر به پروژه SoCPatterns.Layered.Serviceاضافه کنید:
public class ProductListResponse { public bool Success { get; set; } public string Message { get; set; } public IList<ProductViewModel> Products { get; set; } }
به منظور تبدیل موجودیت Productبه ProductViewModel، به دو متد نیاز داریم، یکی برای تبدیل یک Productو دیگری برای تبدیل لیستی از Product. شما میتوانید این دو متد را به کلاس Productموجود در Domain Modelاضافه نمایید، اما این متدها نیاز واقعی منطق تجاری نمیباشند. بنابراین بهترین انتخاب، استفاده از Extension Methodها میباشد که باید برای کلاس Productو در لایهی سرویس ایجاد نمایید. کلاسی با نام ProductMapperExtensionMethodsرا با کد زیر به پروژه SoCPatterns.Layered.Serviceاضافه کنید:
public static class ProductMapperExtensionMethods { public static ProductViewModel ConvertToProductViewModel(this Model.Product product) { ProductViewModel productViewModel = new ProductViewModel(); productViewModel.ProductId = product.Id; productViewModel.Name = product.Name; productViewModel.RRP = String.Format(“{0:C}”, product.Price.RRP); productViewModel.SellingPrice = String.Format(“{0:C}”, product.Price.SellingPrice); if (product.Price.Discount > 0) productViewModel.Discount = String.Format(“{0:C}”, product.Price.Discount); if (product.Price.Savings < 1 && product.Price.Savings > 0) productViewModel.Savings = product.Price.Savings.ToString(“#%”); return productViewModel; } public static IList<ProductViewModel> ConvertToProductListViewModel( this IList<Model.Product> products) { IList<ProductViewModel> productViewModels = new List<ProductViewModel>(); foreach(Model.Product p in products) { productViewModels.Add(p.ConvertToProductViewModel()); } return productViewModels; } }
حال کلاس ProductServiceرا جهت تعامل با کلاس سرویس موجود در Domain Modelو به منظور برگرداندن لیستی از محصولات و تبدیل آن به لیستی از ProductViewModel، ایجاد مینماییم. کلاسی با نام ProductServiceرا با کد زیر به پروژه SoCPatterns.Layered.Serviceاضافه کنید:
public class ProductService { private Model.ProductService _productService; public ProductService(Model.ProductService ProductService) { _productService = ProductService; } public ProductListResponse GetAllProductsFor( ProductListRequest productListRequest) { ProductListResponse productListResponse = new ProductListResponse(); try { IList<Model.Product> productEntities = _productService.GetAllProductsFor(productListRequest.CustomerType); productListResponse.Products = productEntities.ConvertToProductListViewModel(); productListResponse.Success = true; } catch (Exception ex) { // Log the exception… productListResponse.Success = false; // Return a friendly error message productListResponse.Message = ex.Message; } return productListResponse; } }
کلاس Serviceتمامی خطاها را دریافت نموده و پس از مدیریت خطا، پیغامی مناسب را به کلاینت ارسال میکند. همچنین این لایه محل مناسبی برای Logکردن خطاها میباشد. در اینجا کد نویسی لایه سرویس به پایان رسید و در ادامه به کدنویسی Data Layerمیپردازیم.
Data Layer
برای ذخیره سازی محصولات، یک بانک اطلاعاتی با نام Shop01ایجاد کنید که شامل جدولی به نام Productبا ساختار زیر باشد:
برای اینکه کدهای بانک اطلاعاتی را سریعتر تولید کنیم از روش Linq to SQL در Data Layerاستفاده میکنم. برای این منظور یک Data Contextبرای Linq to SQLبه این لایه اضافه میکنیم. بر روی پروژه SoCPatterns.Layered.Repositoryکلیک راست نمایید و گزینه Add > New Itemرا انتخاب کنید. در پنجره ظاهر شده و از سمت چپ گزینه Dataو سپس از سمت راست گزینه Linq to SQL Classesرا انتخاب نموده و نام آن را Shop.dbmlتعیین نمایید.
از طریق پنجره Server Explorerبه پایگاه داده مورد نظر متصل شوید و با عمل Drag & Dropجدول Productرا به بخش Designکشیده و رها نمایید.
اگر به یاد داشته باشید، در لایه Modelبرای برقراری ارتباط با پایگاه داده از یک Interfaceبه نام IProductRepositoryاستفاده نمودیم. حال باید این Interfaceرا پیاده سازی نماییم. کلاسی با نام ProductRepositoryرا با کد زیر به پروژه SoCPatterns.Layered.Repositoryاضافه کنید:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using SoCPatterns.Layered.Model; namespace SoCPatterns.Layered.Repository { public class ProductRepository : IProductRepository { public IList<Model.Product> FindAll() { var products = from p in new ShopDataContext().Products select new Model.Product { Id = p.ProductId, Name = p.ProductName, Price = new Model.Price(p.Rrp, p.SellingPrice) }; return products.ToList(); } } }
در متد FindAll، با استفاده از دستورات Linq to SQL، لیست تمامی محصولات را برگرداندیم. کدنویسی لایهی Dataهم به پایان رسید و در ادامه به کدنویسی لایهی Presentationو UIمیپردازیم.
Presentation Layer
به منظور جداسازی منطق نمایش (Presentation)از رابط کاربری (User Interface)، از الگوی Model View Presenterیا همان MVPاستفاده میکنیم که در مباحث بعدی با جزئیات بیشتری در مورد آن صحبت خواهم کرد. یک Interfaceبا نام IProductListViewرا با کد زیر به پروژه SoCPatterns.Layered.Presentationاضافه کنید:
using SoCPatterns.Layered.Service; public interface IProductListView { void Display(IList<ProductViewModel> Products); Model.CustomerType CustomerType { get; } string ErrorMessage { set; } }
این Interfaceتوسط Web Formهای ASP.NETو یا Win Formها باید پیاده سازی شوند. کار با Interfaceها موجب میشود تا تست Viewها به راحتی انجام شوند. کلاسی با نام ProductListPresenterرا با کد زیر به پروژه SoCPatterns.Layered.Presentationاضافه کنید:
using SoCPatterns.Layered.Service; namespace SoCPatterns.Layered.Presentation { public class ProductListPresenter { private IProductListView _productListView; private Service.ProductService _productService; public ProductListPresenter(IProductListView ProductListView, Service.ProductService ProductService) { _productService = ProductService; _productListView = ProductListView; } public void Display() { ProductListRequest productListRequest = new ProductListRequest(); productListRequest.CustomerType = _productListView.CustomerType; ProductListResponse productResponse = _productService.GetAllProductsFor(productListRequest); if (productResponse.Success) { _productListView.Display(productResponse.Products); } else { _productListView.ErrorMessage = productResponse.Message; } } } }
کلاس Presenterوظیفهی واکشی داده ها، مدیریت رویدادها و بروزرسانی UIرا دارد. در اینجا کدنویسی لایهی Presentationبه پایان رسیده است. از مزایای وجود لایهی Presentationاین است که تست نویسی مربوط به نمایش دادهها و تعامل بین کاربر و سیستم به سهولت انجام میشود بدون آنکه نگران دشواری Unit Testنویسی Web Formها باشید. حال میتوانید کد نویسی مربوط به UIرا انجام دهید که در ادامه به کد نویسی در Win Formsو Web Formsخواهیم پرداخت.