ทางเลือกอื่นสำหรับรูปแบบพื้นที่เก็บข้อมูลสำหรับห่อหุ้มตรรกะ ORM หรือไม่


24

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

ฉันสามารถคาดการณ์การโพสต์เกี่ยวกับการไม่เพิ่มความซับซ้อนเป็นพิเศษก่อนที่จะจำเป็นต้องมีความคล่องตัว ฯลฯ แต่ฉันแค่สนใจเกี่ยวกับรูปแบบที่มีอยู่ในการแก้ปัญหาที่คล้ายกันในวิธีที่ง่ายขึ้น

ความคิดแรกของฉันคือการมีพื้นที่เก็บข้อมูลประเภททั่วไปซึ่งฉันเพิ่มวิธีการที่จำเป็นในการเรียนที่เก็บประเภทเฉพาะผ่านวิธีการขยาย แต่การทดสอบหน่วยวิธีการคงที่เจ็บปวดอย่างมาก IE:

public static class PersonExtensions
{
    public static IEnumerable<Person> GetRetiredPeople(this IRepository<Person> personRep)
    {
        // logic
    }
}

5
มันเกี่ยวกับรูปแบบพื้นที่เก็บข้อมูลที่คุณพบว่ามีปัญหากับรหัสและการบำรุงรักษาอย่างไร
Matthew Flynn

1
มีทางเลือกอื่นอีกมากมาย หนึ่งในนั้นคือการใช้รูปแบบของ Query Object ซึ่งแม้ว่าฉันไม่ได้ใช้ดูเหมือนว่าเป็นวิธีที่ดี IMHO พื้นที่เก็บข้อมูลเป็นรูปแบบที่ดีถ้าขวามือสอง แต่ไม่ถูกทั้งหมดและสิ้นสุดทั้งหมด
dreza

คำตอบ:


14

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

public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
    public UserRepository(DbContext context){}

    public ICollection<Person> FindRetired()
    {
        return context.Persons.Where(p => p.Status == "Retired").ToList();
    }
}

ประการที่สอง:

IQueryableอย่ากลับมา มันเป็นสิ่งที่เป็นนามธรรมรั่วไหล ลองใช้อินเตอร์เฟสนั้นด้วยตนเองหรือใช้โดยไม่ทราบเกี่ยวกับผู้ให้บริการฐานข้อมูล ตัวอย่างเช่นผู้ให้บริการฐานข้อมูลทุกคนมี API ของตัวเองสำหรับหน่วยงานที่โหลดอย่างกระตือรือร้น นั่นเป็นสาเหตุที่เป็นนามธรรมรั่วไหล

ทางเลือก

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

โดยทั่วไปคุณสร้างคลาสคิวรีที่คุณเรียกใช้

public class GetMyMessages
{
    public GetMyMessages(IDbConnection connection)
    {
    }


    public Message[] Execute(DateTime minDate)
    {
        //query
    }
}

สิ่งที่ดีกับการสืบค้นคือคุณสามารถใช้วิธีการเข้าถึงข้อมูลที่แตกต่างกันสำหรับการสืบค้นที่แตกต่างกันโดยไม่ทำให้รหัสยุ่งเหยิง ตัวอย่างเช่นคุณสามารถใช้บริการเว็บในบริการหนึ่ง, ห้ามมิให้อื่นและ ADO.NET ในหนึ่งในสาม

หากคุณมีความสนใจในการติดตั้ง. NET อ่านบทความของฉัน: http://blog.gauffin.org/2012/10//griffin-decoupled-the-queries/


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

3
หากคำจำกัดความของคุณในการเรียนเล็ก ๆ นั้นวุ่นวายใช่ไหม ในกรณีนี้คุณไม่ควรพยายามทำตามหลักการของ SOLID เพราะการใช้พวกมันจะสร้างคลาส (เล็กกว่า) ให้มากขึ้นเสมอ
jgauffin

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

4
โครงสร้างโครงการมีความสำคัญมากขึ้น หากคุณใส่แบบสอบถามทั้งหมดในเนมสเปซ YourProject.YourDomainModelName.Queriesมันจะง่ายต่อการสำรวจ
jgauffin

สิ่งหนึ่งที่ฉันหายากทำในทางที่ดีกับ CQS คือแบบสอบถามทั่วไป เช่นให้ผู้ใช้แบบสอบถามหนึ่งคำจะได้รับนักเรียนที่เกี่ยวข้องทั้งหมด (ผลการเรียนต่างกัน, ลูก ๆ เป็นต้น) ตอนนี้เคียวรีอื่นต้องการรับลูกสำหรับผู้ใช้จากนั้นทำการดำเนินการบางอย่างกับพวกเขาดังนั้นเคียวรี 2 สามารถใช้ตรรกะทั่วไปในเคียวรี 1 แต่ไม่มีวิธีง่ายๆในการทำเช่นนั้น ฉันไม่สามารถค้นหาการใช้งาน CQS ใด ๆ ที่อำนวยความสะดวกในเรื่องนี้ได้อย่างง่ายดาย ที่เก็บช่วยในเรื่องนี้มาก ไม่ใช่แฟนของทั้งสองรูปแบบ แต่เพียงแบ่งปันของฉัน
Mrchief

8

ครั้งแรกที่ฉันคิดว่าเป็น ORM ที่มีขนาดใหญ่พอที่เป็นนามธรรมมากกว่าฐานข้อมูลของคุณ ORMs บางตัวมีผลผูกพันกับฐานข้อมูลเชิงสัมพันธ์ทั่วไปทั้งหมด (NHibernate มีการเชื่อมโยงกับ MSSQL, Oracle, MySQL, Postgress และอื่น ๆ ) ดังนั้นการสร้าง Abstraction ใหม่บนนั้นไม่ได้สร้างผลกำไรให้กับฉัน นอกจากนี้ยังมีการโต้แย้งว่าคุณต้อง "นามธรรมออกไป" ออมนี้มีความหมาย

หากคุณยังต้องการที่จะสร้างสิ่งที่เป็นนามธรรมนี้ฉันจะไปกับรูปแบบ Repository มันยอดเยี่ยมมากในยุคของ SQL ที่บริสุทธิ์ แต่มันค่อนข้างลำบากเมื่อต้องเผชิญกับ ORM ที่ทันสมัย สาเหตุหลักมาจากคุณท้ายการใช้งานคุณลักษณะ ORM ส่วนใหญ่อีกครั้งเช่นการดำเนินการ CRUD และการสืบค้น

ถ้าฉันจะสร้าง abstractions ฉันจะใช้กฎ / รูปแบบเหล่านี้

  • CQRS
  • ใช้คุณลักษณะ ORM ที่มีอยู่ให้มากที่สุด
  • Encapsulate เฉพาะตรรกะที่ซับซ้อนและแบบสอบถาม
  • พยายามให้ "การประปา" ของ ORM ถูกซ่อนอยู่ภายในสถาปัตยกรรม

ในการนำไปปฏิบัติอย่างเป็นรูปธรรมฉันจะใช้การดำเนินงาน CRUD ของ ORM โดยตรงโดยไม่ต้องปิดบังใด ๆ ฉันจะทำแบบสอบถามง่ายๆในรหัสโดยตรง แต่แบบสอบถามที่ซับซ้อนจะถูกห่อหุ้มในวัตถุของตัวเอง ในการ "ซ่อน" ORM ฉันจะพยายามฉีดบริบทข้อมูลลงในวัตถุบริการ / UI และทำแบบสอบถามวัตถุเดียวกัน

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

ในฐานะที่เป็นอ่านแนะนำผมจะบอกว่าบล็อก Ayende ของโดยเฉพาะอย่างยิ่งในบทความนี้


+1 "สิ่งสุดท้ายที่ฉันอยากจะบอกก็คือว่าคนจำนวนมากใช้ออมโดยไม่ต้องรู้วิธีการใช้งานและวิธีการที่จะทำให้ดีที่สุด 'กำไร' ออกจากมันคนแนะนำให้เก็บเป็นเรื่องปกติของชนิดนี้."
บุรุษ

1

ปัญหาของการติดตั้งรั่วคือคุณต้องการเงื่อนไขการกรองที่แตกต่างกัน

คุณสามารถ จำกัด API นี้ถ้าคุณใช้วิธีการเก็บข้อมูล FindByExample และใช้มันเช่นนี้

// find all retired persons
Person filter = new Person {Status=PersonStatus.Retired};
IEnumerable<Person> found = personRepository.FindByExample(filter);


// find all persons who have a dog named "sam"
Person filter = new Person();
filter.AddPet(new Dog{Name="sam"});
IEnumerable<Person> found = personRepository.FindByExample(filter);

0

ลองพิจารณาให้ส่วนขยายของคุณทำงานด้วย IQueryable แทนที่จะเป็น IRepository

public static class PersonExtensions
{
    public static IQueryable<Person> AreRetired(this IQueryable<Person> people)
    {
        return people.Where(p => p.Status == "Retired");
    }
}

เพื่อทดสอบหน่วย:

List<Person> people = new List<Person>();
people.Add(new Person() { Name = "Bob", Status = "Retired" });
people.Add(new Person() { Name = "Sam", Status = "Working" });

var retiredPeople = people.AsQueryable().AreRetired();
// assert that Bob is in the list
// assert that Sam is not in the list

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


5
-1 a) IQueryableเป็นแม่ที่ไม่ดีหรือค่อนข้างเป็นอินเตอร์เฟสของ GOD การนำไปใช้งานนั้นมีอยู่มากและมีผู้ให้บริการ LINQ to Sql ที่สมบูรณ์น้อยมาก (ถ้ามี) b) วิธีการต่อขยายทำให้การขยาย (หนึ่งในหลักการ OOP ที่เป็นประโยชน์) เป็นไปไม่ได้
jgauffin

0

ฉันเขียนรูปแบบวัตถุแบบสอบถามที่สวยประณีตสำหรับ NHibernate ได้ที่นี่: https://github.com/shaynevanasperen/NHibernate.Sessions.Operations

มันทำงานโดยใช้อินเทอร์เฟซเช่นนี้:

public interface IDatabases
{
    ISessionManager SessionManager { get; }

    T Query<T>(IDatabaseQuery<T> query);
    T Query<T>(ICachedDatabaseQuery<T> query);

    void Command(IDatabaseCommand command);
    T Command<T>(IDatabaseCommand<T> command);
}

รับคลาสเอนทิตี POCO ดังนี้:

class Database1Poco
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
}

คุณสามารถสร้างวัตถุคิวรีแบบนี้:

class Database1PocoByProperty1 : DatabaseQuery<Database1Poco>
{
    public override Database1Poco Execute(ISessionManager sessionManager)
    {
        return sessionManager.Session.Query<Database1Poco>().SingleOrDefault(x => x.Property1 == Property1);
    }

    public int Property1 { get; set; }
}

และจากนั้นใช้พวกเขาเช่นนี้:

var database1Poco = _databases.Query(new Database1PocoByProperty1 { Property1 = 1 });

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

ฉันขอโทษฉันรีบ คำตอบได้รับการอัปเดตเพื่อแสดงรหัสตัวอย่าง
เชน

1
+1 สำหรับการปรับปรุงการโพสต์ด้วยเนื้อหาเพิ่มเติม
Songo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.