ถ้า Repository Pattern overkill สำหรับ ORM สมัยใหม่ (EF, nHibernate) สิ่งที่เป็นนามธรรมที่ดีกว่าคืออะไร?


12

ฉันเพิ่งอ่านข้อโต้แย้งจำนวนมากต่อต้านการใช้รูปแบบพื้นที่เก็บข้อมูลกับ Entity Framework ORM ที่ทรงพลังเนื่องจากมีการรวมฟังก์ชั่นที่คล้ายกับพื้นที่เก็บข้อมูลพร้อมกับฟังก์ชั่นหน่วยการทำงานเช่นกัน

อีกข้อโต้แย้งต่อการใช้รูปแบบสำหรับสถานการณ์เช่นการทดสอบหน่วยคือรูปแบบที่เก็บเป็นสิ่งที่เป็นนามธรรมเนื่องจากการใช้งานทั่วไปมีประโยชน์มากขึ้น IQueryable

ข้อโต้แย้งเกี่ยวกับการใช้รูปแบบพื้นที่เก็บข้อมูลมีเหตุผลสำหรับฉัน แต่วิธีการทางเลือกของ abstractions ที่แนะนำมักทำให้เกิดความสับสนมากขึ้นและดูเหมือนจะเกินความเป็นปัญหาเช่นกัน

วิธีการแก้ปัญหาของ Jimmy Bogards ดูเหมือนจะเป็นการผสมผสานของนามธรรมที่ออกไป แต่ยังแนะนำสถาปัตยกรรมของเขาเอง https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

อีกตัวอย่างของที่เก็บที่ไม่จำเป็น .... แต่ใช้สถาปัตยกรรมของฉัน! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

อื่น ... http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework

ฉันไม่พบวิธีการแทนที่ที่ชัดเจนหรือทางเลือกแทนวิธีการเก็บข้อมูลแบบ "ซับซ้อนมากเกินไป" ซึ่งไม่ได้มีการออกแบบตัวเองมากกว่า


4
คุณพยายามทำอะไรให้สำเร็จเป็นพิเศษ บทคัดย่อควรมีวัตถุประสงค์ หากคุณกำลังเขียนแอพ CRUD ORM อาจเป็นนามธรรมเพียงพอ
JacquesB

@JacquesB ฉันกำลังพยายามหลีกเลี่ยงปัญหาความต้านทานเชิงวัตถุด้วยโมเดลโดเมนที่มีประสิทธิภาพ แต่ยังสรุปได้ว่าอยู่ห่างจากมุมมองของฉันในการใช้ mvc
AnotherDeveloper

Reed Cospey มีสิ่งดีๆมากมายที่จะพูดเกี่ยวกับ IQuerable ที่นี่: stackoverflow.com/questions/1578778/using-iqueryable-with-linq นี่หมายถึงการขนส่งเลเยอร์ดีกว่า เท่าที่เป็นนามธรรมฉันพบว่าการใช้งานสำหรับรูปแบบทั่วไปที่เก็บทำงานได้ดีเมื่อฉันต้องการที่จะฉีด EntityType แต่ยังคงต้องการที่จะรักษาวิธีการทั่วไป ในทางกลับกันตัวฉันเองได้โต้เถียงในฟอรัม MSDN LINQ ว่า EF เป็นรูปแบบที่เก็บเพราะมันอยู่ในหน่วยความจำ โครงการหนึ่งใช้หลายอย่างในกรณีที่คำสั่งเรียกใช้ซึ่งทำงานได้ดี
John Peters

แต่สิ่งหนึ่งที่ฉันมองเข้าไป แต่ถูกปฏิเสธคือธุรกิจของ Expression Tree บางคนชอบมันมาก แต่ .... คุณต้องศึกษาอย่างหนักก่อนที่คุณจะพบว่ามีประโยชน์
John Peters

2
เราควรกลับไปที่การเรียก SQL จากตัวควบคุม
bobek

คำตอบ:


12

ฉันคิดว่าคุณกำลังสร้างที่เก็บและที่เก็บทั่วไป

พื้นที่เก็บข้อมูลพื้นฐานเพียงเชื่อมต่อที่เก็บข้อมูลของคุณและให้วิธีการส่งคืนข้อมูล

IRepository {
   List<Data> GetDataById(string id);
}

มันจะไม่รั่วเลเยอร์ข้อมูลลงในโค้ดของคุณผ่านทาง IQueryable หรือวิธีอื่น ๆ ในการส่งแบบสอบถามแบบสุ่มและให้พื้นผิวของวิธีการทดสอบและการฉีดที่กำหนดไว้อย่างดี

Generic Repository ช่วยให้คุณสามารถส่งผ่านแบบสอบถามเช่น ORM ได้

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
    //or
    IQueryable<T> Get<T>();
}

ฉันยอมรับว่ามีจุดไม่มากนักในการใช้พื้นที่เก็บข้อมูลทั่วไปด้านบนของออมซึ่งโดยทั่วไปเป็นพื้นที่เก็บข้อมูลทั่วไป

คำตอบคือการใช้รูปแบบพื้นที่เก็บข้อมูลขั้นพื้นฐานเพื่อซ่อนออมของคุณ


1
ORM ของ ORM หรือไม่ ฉันพยายามเป็นคนตลก ทำไมคุณต้องสรุป ORM
johnny

1
ด้วยเหตุผลเดียวกันกับที่คุณนามธรรมอะไร เพื่อหลีกเลี่ยงการทำให้รหัสของคุณสกปรกด้วยคลาสที่เป็นกรรมสิทธิ์
Ewan

6

อาร์กิวเมนต์ส่วนใหญ่ที่คุณกล่าวถึงคุณลักษณะอย่างไม่ถูกต้องไปยังคุณลักษณะรูปแบบของที่เก็บซึ่งไม่มี

ตามหลักการแล้ว Repository ที่กำหนดไว้ใน DDD นั้นเป็นเพียงชุดของวัตถุที่คุณสามารถค้นหาหรือเพิ่มเข้าไปได้ กลไกการติดตาที่อยู่ข้างหลังนั้นถูกขจัดออกไปดังนั้นในฐานะผู้บริโภคคุณจะได้รับภาพลวงตาว่าเป็นคอลเลกชันในหน่วยความจำ

การใช้พื้นที่เก็บข้อมูลที่มี abstractions รั่วไหล (การเปิดเผยIQueryablesเป็นต้น) เป็นการใช้พื้นที่เก็บข้อมูลที่ไม่ดี

การใช้พื้นที่เก็บข้อมูลที่แสดงมากกว่าการดำเนินการรวบรวม (ตัวอย่างเช่นคุณสมบัติหน่วยงาน) เป็นการใช้พื้นที่เก็บข้อมูลที่ไม่ดี

มีทางเลือกอื่นใน Repository สำหรับการเข้าถึงข้อมูลหรือไม่? ใช่ แต่สิ่งเหล่านี้ไม่เกี่ยวข้องกับปัญหาที่คุณพบในคำถาม



1

สำหรับฉันที่เก็บรวมกับ ORM หรือเลเยอร์การติดตา DB อื่น ๆ มีข้อเสียเหล่านี้:

  1. ครอบคลุมหน่วยงาน UoW ต้องถูกเขียนโปรแกรมโดยโปรแกรมเมอร์และแทบจะไม่สามารถนำไปใช้เป็นเวทมนตร์ในพื้นหลังได้ซึ่งผู้ใช้เพียงแค่ทำการสอบถามและแก้ไขโดยไม่ต้องกำหนดขอบเขต UoW และอาจเป็นจุดกระทำ บางครั้ง UoW จะถูกละทิ้งโดยการลดลงใน micro UoW (เช่นเซสชัน NHibernate) ในแต่ละวิธีการเข้าถึง Repository
  2. ปิดบังหรือในกรณีที่เลวร้ายที่สุดทำลายการเพิกเฉยต่อการคงอยู่: วิธีการเช่น "โหลด ()", "รับ ()", "บันทึก ()" หรือ "อัปเดต ()" แนะนำการดำเนินการวัตถุเดี่ยวทันที SQL / DML หรือเหมือนกับทำงานกับไฟล์ ในความเป็นจริงตัวอย่างเช่นวิธี NHibernate ที่มีชื่อที่ทำให้เข้าใจผิดเหล่านี้มักจะไม่ทำการเข้าถึงแต่ละรายการ แต่จัดคิวสำหรับการโหลดที่ขี้เกียจหรือชุด / แทรก / อัปเดต (Persistence Ignorance) บางครั้งผู้เขียนโปรแกรมสงสัยว่าทำไมพวกเขาไม่ได้รับการดำเนินงานฐานข้อมูลทันทีและบังคับให้เลิกการเพิกเฉยต่อความไม่รู้ซึ่งเป็นการฆ่าประสิทธิภาพและการใช้ความพยายามหลักในการทำให้ระบบแย่ลง (มาก!)
  3. การเจริญเติบโตที่ไม่สามารถควบคุมได้ แหล่งเก็บข้อมูลอย่างง่ายอาจสะสมวิธีการมากขึ้นเพื่อตอบสนองความต้องการเฉพาะ

เช่น:

public interface ICarsRepository  /* initial */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); // bad, should be for multiple IDs.
    void SaveCar(ICar carToSave); // bad, no individual saves, use UoW commit!
}

public interface ICarsRepository  /* a few years later */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); 
    IList<ICar> GetBlueCars();
    IList<ICar> GetRedYellowGreenCars();
    IList<ICar> GetCarsByColor(Color colorOfCars); // a bit better
    IList<ICar> GetCarsByColor(IEnumerable<Color> colorsOfCars); // better!
    IList<ICar> GetCarsWithPowerBetween(int hpFrom, int hpTo);
    IList<ICar> GetCarsWithPowerKwBetween(int kwFrom, int kwTo);
    IList<ICar> GetCarsBuiltBetween(int yearFrom, int yearTo);
    IList<ICar> GetCarsBuiltBetween(DateTime from, DateTime to); // some also need month and day
    IList<ICar> GetHybridCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetElectricCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetCarsFromManufacturer(IManufacturer carManufacturer); 
    bool HasCarMeanwhileBeenChangedBySomebodyElseInDb(ICar car); // persistence ignorance broken
    void SaveCar(ICar carToSave);
}

4. วัตถุอันตรายของพระเจ้า: คุณอาจถูกล่อลวงให้สร้างคลาสพระเจ้าหนึ่งชั้นซึ่งครอบคลุมรุ่นหรือชั้นการเข้าถึงข้อมูลทั้งหมดของคุณ คลาสที่เก็บจะไม่เพียง แต่มีวิธีการรถยนต์ แต่วิธีการสำหรับหน่วยงานทั้งหมด

ในความคิดของฉันมันจะดีกว่าที่จะเสนออย่างน้อยโอกาสในการค้นหาเพื่อหลีกเลี่ยงความยุ่งเหยิงขนาดใหญ่ของวิธีการหลายวัตถุประสงค์เดียว ไม่ว่าจะเป็น LINQ ภาษาคิวรีของตัวเองหรือแม้แต่บางสิ่งที่นำมาโดยตรงจาก ORM (OK ปัญหาการมีเพศสัมพันธ์ชนิด ...


4
วิธีการทั้งหมดเหล่านี้จะไปที่ไหนถ้าไม่ได้ใช้รูปแบบพื้นที่เก็บข้อมูล?
johnny

@johnny ที่นี่คุณไป: softwareengineering.stackexchange.com/a/220126/100617
lexeme

1

ถ้าวัตถุประสงค์ของ Repository-interface คือการเยาะเย้ยฐานข้อมูลสำหรับunittest (= test in isolated) สิ่งที่ดีที่สุดคือสิ่งที่ง่ายต่อการเยาะเย้ย

มันแตกต่างกันในการจำลองส่วนต่อประสานที่เก็บซึ่งขึ้นอยู่กับผลลัพธ์ของ IQueryable

จากมุมมองการทดสอบหน่วย

IRepository {
   List<Data> GetDataById(string id);
}

สามารถเยาะเย้ยได้อย่างง่ายดาย

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
}

สามารถเยาะเย้ยได้ง่ายเฉพาะในกรณีที่เยาะเย้ยละเว้นเนื้อหาของพารามิเตอร์แบบสอบถาม

IGenericRepository<T> {
    IQueryable<T> Get<T>(some_parameters);
}

ไม่สามารถล้อเลียนได้ง่าย


0

ฉันไม่คิดว่ารูปแบบพื้นที่เก็บข้อมูลเป็น overkill ถ้าคุณใช้ฟังก์ชั่นแลมบ์ดาสำหรับการสอบถาม โดยเฉพาะอย่างยิ่งเมื่อคุณต้องสรุป ORM (ในความคิดของฉันคุณควรเสมอ) จากนั้นฉันจะไม่สนใจรายละเอียดการใช้งานของที่เก็บเอง

ตัวอย่างเช่น:

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        UserRepository ur = new UserRepository();
        var userWithA = ur.GetBy(u => u.Name.StartsWith("A"));

        Console.WriteLine(userWithA.Name);


        ur.GetAllBy(u => u.Name.StartsWith("M"))
          .ForEach(u => Console.WriteLine(u.Name));


        ur.GetAllBy(u => u.Age > 13)
          .ForEach(u => Console.WriteLine(u.Name));
    }
}

public class UserRepository 
{
    List<User> users = new List<User> { 
        new User{Name="Joe", Age=10},
            new User{Name="Allen", Age=12},
            new User{Name="Martin", Age=14},
            new User{Name="Mary", Age=15},
            new User{Name="Ashton", Age=29}
    };

    public User GetBy(Predicate<User> userPredicate)
    {
        return users.Find(userPredicate);
    }

    public List<User> GetAllBy(Predicate<User> userPredicate)
    {
        return users.FindAll(userPredicate);
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.