2013-12-09 8 views
18

यह पहली बार है जब मैं एक और अधिक डोमेन संचालित डिजाइन दृष्टिकोण लागू कर रहा हूं। मैंने Onion Architecture को आजमाने का निर्णय लिया है क्योंकि यह आधारभूत संरचना/प्लेटफॉर्म/आदि के बजाय डोमेन पर केंद्रित है।प्याज वास्तुकला, कार्य इकाई और एक सामान्य रिपोजिटरी पैटर्न

enter image description here

आदेश में इकाई की रूपरेखा से दूर सार करने के लिए, मैं काम कार्यान्वयन का एक यूनिट के साथ एक सामान्य भंडार बनाया है।

IRepository<T> और IUnitOfWork इंटरफेस:

IRepository<T> की
public interface IRepository<T> 
{ 
    void Add(T item); 

    void Remove(T item); 

    IQueryable<T> Query(); 
} 

public interface IUnitOfWork : IDisposable 
{ 
    void SaveChanges(); 
} 

इकाई की रूपरेखा कार्यान्वयन और IUnitOfWork:

public class EntityFrameworkRepository<T> : IRepository<T> where T : class 
{ 
    private readonly DbSet<T> dbSet; 

    public EntityFrameworkRepository(IUnitOfWork unitOfWork) 
    { 
     var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork; 

     if (entityFrameworkUnitOfWork == null) 
     { 
      throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork"); 
     } 

     dbSet = entityFrameworkUnitOfWork.GetDbSet<T>(); 
    } 

    public void Add(T item) 
    { 
     dbSet.Add(item); 
    } 

    public void Remove(T item) 
    { 
     dbSet.Remove(item); 
    } 

    public IQueryable<T> Query() 
    { 
     return dbSet; 
    } 
} 

public class EntityFrameworkUnitOfWork : IUnitOfWork 
{ 
    private readonly DbContext context; 

    public EntityFrameworkUnitOfWork() 
    { 
     this.context = new CustomerContext();; 
    } 

    internal DbSet<T> GetDbSet<T>() 
     where T : class 
    { 
     return context.Set<T>(); 
    } 

    public void SaveChanges() 
    { 
     context.SaveChanges(); 
    } 

    public void Dispose() 
    { 
     context.Dispose(); 
    } 
} 

ग्राहक भंडार:

public interface ICustomerRepository : IRepository<Customer> 
{ 

} 

public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository 
{ 
    public CustomerRepository(IUnitOfWork unitOfWork): base(unitOfWork) 
    { 
    } 
} 

ASP.NET MVC नियंत्रक भंडार का उपयोग करते हुए:

container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>(); 
container.RegisterType<ICustomerRepository, CustomerRepository>(); 

समाधान::

enter image description here

समस्याओं

public class CustomerController : Controller 
{ 
    UnityContainer container = new UnityContainer(); 

    public ActionResult List() 
    { 
     var unitOfWork = container.Resolve<IUnitOfWork>(); 
     var customerRepository = container.Resolve<ICustomerRepository>(); 

     return View(customerRepository.Query()); 
    } 

    [HttpPost] 
    public ActionResult Create(Customer customer) 
    { 
     var unitOfWork = container.Resolve<IUnitOfWork>(); 
     var customerRepository = container.Resolve<ICustomerRepository>();; 

     customerRepository.Add(customer); 

     unitOfWork.SaveChanges(); 

     return RedirectToAction("List"); 
    } 
} 

एकता के साथ निर्भरता इंजेक्शन?

  • रिपोजिटरी कार्यान्वयन (ईएफ कोड) बहुत सामान्य है। यह सब EntityFrameworkRepository<T> वर्ग के पक्ष में बैठता है। कंक्रीट मॉडल भंडार में इस तर्क में से कोई भी शामिल नहीं है। यह मुझे अनावश्यक कोड लिखने से बचाता है, लेकिन संभवतः लचीलापन बलिदान देता है?

  • ICustomerRepository और CustomerRepository कक्षाएं मूल रूप से खाली हैं। वे पूरी तरह से अमूर्तता प्रदान करने के लिए वहाँ हैं। जहां तक ​​मैं समझता हूं, यह प्याज आर्किटेक्चर की दृष्टि से फिट बैठता है, जहां बुनियादी ढांचे और मंच-निर्भर कोड आपके सिस्टम के बाहर बैठते हैं, लेकिन खाली कक्षाएं और खाली इंटरफेस होने पर गलत लगता है?

  • एक अलग दृढ़ता कार्यान्वयन (Azure टेबल संग्रहण कहें) का उपयोग करने के लिए, फिर एक नया CustomerRepository कक्षा बनाने की आवश्यकता होगी और AzureTableStorageRepository<T> का उत्तराधिकारी होगा। लेकिन इससे अनावश्यक कोड (एकाधिक ग्राहक रिपोर्ट) हो सकता है? यह प्रभाव मजाक कैसे करेगा?

  • एक अन्य कार्यान्वयन (Azure टेबल संग्रहण कहें) में अंतर्राष्ट्रीय समर्थन पर सीमाएं हैं, इसलिए AzureTableStorageUnitOfWork क्लास इस संदर्भ में काम नहीं करेगी।

वहाँ जिस तरह से मैंने यह किया है के साथ किसी भी अन्य मुद्दों कर रहे हैं?

(मैं this post से मेरी प्रेरणा के सबसे ले लिया है)

+2

आप आईओसी कंटेनर पर निर्भरता लेकर [सेवा लोकेटर एंटी-पैटर्न] (http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) का उपयोग कर रहे हैं। इसके बजाय, आपको अपने नियंत्रक में इंजेक्ट करने के लिए एक फैक्ट्री क्लास पंजीकृत करनी चाहिए। कुछ आईओसी कंटेनर आपके लिए इनमें से एक को 'Func ' निर्भरता – AlexFoxGill

उत्तर

22

मैं कह सकता हूँ कि इस कोड पहली बार कोशिश के लिए काफी अच्छा है, लेकिन इसे सुधारने के लिए कुछ स्थानों है।

की उनमें से कुछ के माध्यम से चलते हैं

1. निर्भरता इंजेक्शन (डीआई) और आईओसी के उपयोग

आप Service Locator pattern सबसे सरल संस्करण का उपयोग करें -।।। container उदाहरण ही

मेरा सुझाव है कि आप 'कन्स्ट्रक्टर इंजेक्शन' का उपयोग करें। आप अधिक जानकारी here (ASP.NET MVC 4 Dependency Injection) पा सकते हैं।

public class CustomerController : Controller 
{ 
    private readonly IUnitOfWork unitOfWork; 
    private readonly ICustomerRepository customerRepository; 

    public CustomerController(
     IUnitOfWork unitOfWork, 
     ICustomerRepository customerRepository) 
    { 
     this.unitOfWork = unitOfWork; 
     this.customerRepository = customerRepository; 
    } 

    public ActionResult List() 
    { 
     return View(customerRepository.Query()); 
    } 

    [HttpPost] 
    public ActionResult Create(Customer customer) 
    { 
     customerRepository.Add(customer); 
     unitOfWork.SaveChanges(); 
     return RedirectToAction("List"); 
    } 
} 

2. कार्य (UOW) गुंजाइश यूनिट।

मुझे IUnitOfWork और ICustomerRepository की जीवनशैली नहीं मिल रही है। मैं एकता से परिचित नहीं हूं लेकिन msdn says that TransientLifetimeManager is used by default। इसका मतलब यह है कि जब आप टाइप हल करते हैं तो आपको हर बार एक नया उदाहरण मिल जाएगा।

तो, निम्नलिखित परीक्षण विफल रहता है:

[Test] 
public void MyTest() 
{ 
    var target = new UnityContainer(); 
    target.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>(); 
    target.RegisterType<ICustomerRepository, CustomerRepository>(); 

    //act 
    var unitOfWork1 = target.Resolve<IUnitOfWork>(); 
    var unitOfWork2 = target.Resolve<IUnitOfWork>(); 

    // assert 
    // This Assert fails! 
    unitOfWork1.Should().Be(unitOfWork2); 
} 

और मैं उम्मीद अपने नियंत्रक में UnitOfWork की कि उदाहरण के अपने भंडार में UnitOfWork के कहने से अलग है। कभी-कभी इसका परिणाम बग में हो सकता है। लेकिन यह एकता के लिए एक मुद्दे के रूप में ASP.NET MVC 4 Dependency Injection में हाइलाइट नहीं किया गया है।

Castle WindsorPerWebRequest जीवनशैली का उपयोग उसी http अनुरोध के समान प्रकार के साझा करने के लिए किया जाता है।

यह सामान्य दृष्टिकोण है जब UnitOfWorkPerWebRequest घटक है। विधि के दौरान Commit() को आमंत्रित करने के लिए कस्टम ActionFilter का उपयोग किया जा सकता है।

मैं भी SaveChanges() विधि का नाम बदलने और बस इसे कहते Commit के रूप में यह example में और PoEAA में कहा जाता है जाएगा।

public interface IUnitOfWork : IDisposable 
{ 
    void Commit(); 
} 

3.1। भंडार पर निर्भरता।

यदि आपकी रिपॉजिटरीज 'खाली' होने जा रही हैं तो उनके लिए विशिष्ट इंटरफेस बनाने की आवश्यकता नहीं है। यह IRepository<Customer> को हल करने और एक परीक्षण है कि यह परीक्षण नहीं है अपने नियंत्रक

public CustomerController(
    IUnitOfWork unitOfWork, 
    IRepository<Customer> customerRepository) 
{ 
    this.unitOfWork = unitOfWork; 
    this.customerRepository = customerRepository; 
} 

में निम्नलिखित कोड होना संभव नहीं है।

[Test] 
public void MyTest() 
{ 
    var target = new UnityContainer(); 
    target.RegisterType<IRepository<Customer>, CustomerRepository>(); 

    //act 
    var repository = target.Resolve<IRepository<Customer>>(); 

    // assert 
    repository.Should().NotBeNull(); 
    repository.Should().BeOfType<CustomerRepository>(); 
} 

लेकिन आप खजाने हैं कि करना चाहते हैं, तो 'मानचित्रण परत जहाँ क्वेरी निर्माण कोड ध्यान केंद्रित किया है से अधिक अमूर्त की परत।' (PoEAA, Repository)

भंडार, डोमेन और डेटा मानचित्रण परतों के बीच मध्यस्थता करता है एक में स्मृति डोमेन वस्तु संग्रह की तरह कार्य कर। क्लाइंट ऑब्जेक्ट्स घोषणा विनिर्देशों का घोषणा घोषणात्मक रूप से करें और उन्हें संतोष के लिए रिपोजिटरी सबमिट करें।

3.2। EntityFrameworkRepository पर विरासत।

इस मामले में मैं एक साधारण IRepository

public interface IRepository 
{ 
    void Add(object item); 

    void Remove(object item); 

    IQueryable<T> Query<T>() where T : class; 
} 

और इसके कार्यान्वयन को पता है कि कैसे EntityFramework बुनियादी सुविधाओं के साथ काम करने के लिए और आसानी से एक दूसरे से (जैसे AzureTableStorageRepository) द्वारा बदला जा सकता है पैदा करेगा।

public class EntityFrameworkRepository : IRepository 
{ 
    public readonly EntityFrameworkUnitOfWork unitOfWork; 

    public EntityFrameworkRepository(IUnitOfWork unitOfWork) 
    { 
     var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork; 

     if (entityFrameworkUnitOfWork == null) 
     { 
      throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork"); 
     } 

     this.unitOfWork = entityFrameworkUnitOfWork; 
    } 

    public void Add(object item) 
    { 
     unitOfWork.GetDbSet(item.GetType()).Add(item); 
    } 

    public void Remove(object item) 
    { 
     unitOfWork.GetDbSet(item.GetType()).Remove(item); 
    } 

    public IQueryable<T> Query<T>() where T : class 
    { 
     return unitOfWork.GetDbSet<T>(); 
    } 
} 

public interface IUnitOfWork : IDisposable 
{ 
    void Commit(); 
} 

public class EntityFrameworkUnitOfWork : IUnitOfWork 
{ 
    private readonly DbContext context; 

    public EntityFrameworkUnitOfWork() 
    { 
     this.context = new CustomerContext(); 
    } 

    internal DbSet<T> GetDbSet<T>() 
     where T : class 
    { 
     return context.Set<T>(); 
    } 

    internal DbSet GetDbSet(Type type) 
    { 
     return context.Set(type); 
    } 

    public void Commit() 
    { 
     context.SaveChanges(); 
    } 

    public void Dispose() 
    { 
     context.Dispose(); 
    } 
} 

और अब CustomerRepository प्रॉक्सी हो सकता है और इसका संदर्भ लें।

public interface IRepository<T> where T : class 
{ 
    void Add(T item); 

    void Remove(T item); 
} 

public abstract class RepositoryBase<T> : IRepository<T> where T : class 
{ 
    protected readonly IRepository Repository; 

    protected RepositoryBase(IRepository repository) 
    { 
     Repository = repository; 
    } 

    public void Add(T item) 
    { 
     Repository.Add(item); 
    } 

    public void Remove(T item) 
    { 
     Repository.Remove(item); 
    } 
} 

public interface ICustomerRepository : IRepository<Customer> 
{ 
    IList<Customer> All(); 

    IList<Customer> FindByCriteria(Func<Customer, bool> criteria); 
} 

public class CustomerRepository : RepositoryBase<Customer>, ICustomerRepository 
{ 
    public CustomerRepository(IRepository repository) 
     : base(repository) 
    { } 

    public IList<Customer> All() 
    { 
     return Repository.Query<Customer>().ToList(); 
    } 

    public IList<Customer> FindByCriteria(Func<Customer, bool> criteria) 
    { 
     return Repository.Query<Customer>().Where(criteria).ToList(); 
    } 
} 
+0

महान उत्तर के लिए धन्यवाद। (1) हो गया (2) मैं इस समस्या से जूझ रहा था। अब PerResolveLifetimeManager (3.1) का उपयोग कर अब मैं विशिष्ट fetch विधियों (जैसे GetCustomerByName) के लिए विशिष्ट इंटरफेस का उपयोग कर रहा हूं। मैंने रिपॉजिटरीज़ से IQueryable क्वेरी() विधि को निकालने का निर्णय लिया है क्योंकि यह क्लाइंट कोड में ईएफ विवरण लीक कर रहा था। (3.2) मैं इसे करीब देख लूंगा। – davenewza

2

केवल चोर मैं देख रहा हूँ है कि आप अपने आईओसी उपकरण पर अत्यधिक निर्भर हैं, इसलिए सुनिश्चित करें कि आपके कार्यान्वयन ठोस है या नहीं। हालांकि, यह प्याज डिजाइन के लिए अद्वितीय नहीं है। मैं परियोजनाओं के एक नंबर पर प्याज का इस्तेमाल किया है और किसी भी असली 'gotchas में चलाने नहीं किया है "।

+0

के रूप में इनमें से एक का उत्पादन कर सकते हैं IonC क्या काम करने के लिए प्याज का मतलब नहीं है? मैं अपने कॉलिंग कोड में कंक्रीट कक्षाएं घोषित कर सकता हूं, लेकिन फिर मैं युग्मन बढ़ा दूंगा और टेस्टेबिलिटी को काफी हद तक कम कर दूंगा? उत्तर के लिए धन्यवाद :) – davenewza

+1

हां यही कारण है कि मैं कहता हूं कि इसके कार्यान्वयन को ठोस होना जरूरी है। प्याज के काम को करने के लिए आपको डीआई और आईओसी का उपयोग करने की आवश्यकता है। – Maess

0

मुझे कोड में कुछ गंभीर समस्याएं दिखाई देती हैं।

पहली समस्या भंडारों और यूओडब्ल्यू के बीच रिलेशनशिप है।

var unitOfWork = container.Resolve<IUnitOfWork>(); 
    var customerRepository = container.Resolve<ICustomerRepository>(); 

यहां निहित निर्भरता है। रिपोजिटरी यूओडब्ल्यू के बिना खुद काम नहीं करेगा! सभी भंडारों को यूओडब्ल्यू से कनेक्ट करने की आवश्यकता नहीं है। उदाहरण के लिए संग्रहीत प्रक्रियाओं के बारे में क्या? आपने प्रक्रिया को संग्रहीत किया है और आप इसे भंडार के पीछे छुपाते हैं। संग्रहीत प्रक्रिया invokation अलग लेनदेन का उपयोग करता है! कम से कम सभी मामलों में नहीं। इसलिए यदि मैं केवल भंडार को हल करता हूं और आइटम जोड़ता हूं तो यह काम नहीं करेगा। इसके अलावा यदि मैं क्षणिक जीवन लाइसेंस सेट करता हूं तो यह कोड काम नहीं करेगा क्योंकि भंडार में एक और यूओडब्ल्यू उदाहरण होगा। तो हमारे पास सख्त अंतर्निहित युग्मन है।

दूसरी समस्या आप डी कंटेनर इंजन के बीच तंग युग्मन बनाते हैं और इसे सेवा लोकेटर के रूप में उपयोग करते हैं! आईओसी और एकत्रीकरण को लागू करने के लिए सेवा लोकेटर अच्छा दृष्टिकोण नहीं है। कुछ मामलों में यह विरोधी पैटर्न है। डी कंटेनर का इस्तेमाल किया जाना चाहिए

+0

क्षमा करें .. जल्दी टाइप और त्रुटियों के साथ प्रकाशित। डी कंटेनरों का उपयोग शीर्ष स्तर पर किया जाना चाहिए।आपको नियंत्रक के माध्यम से नियंत्रक कारखाना लागू करना और निर्भरता इंजेक्शन लागू करना है। इस तरह से आप कंटेनर पर अनावश्यक निर्भरता को हटा देंगे और स्पष्ट निर्भरता प्राप्त करेंगे जो सीटीआर में सेट हैं। –

संबंधित मुद्दे