การสร้างเลเยอร์ของสิ่งที่เป็นนามธรรมเหนือเลเยอร์ ORM


12

ฉันเชื่อว่าถ้าคุณมีที่เก็บของคุณใช้ ORM ซึ่งมันมีนามธรรมเพียงพอจากฐานข้อมูล

อย่างไรก็ตามที่ที่ฉันทำงานอยู่ตอนนี้บางคนเชื่อว่าเราควรมีเลเยอร์ที่เป็นนามธรรม ORM ในกรณีที่เราต้องการเปลี่ยน ORM ในภายหลัง

มันจำเป็นจริงๆหรือเป็นเรื่องง่ายที่จะสร้างเลเยอร์ที่สามารถใช้งานกับ ORM จำนวนมากได้หรือไม่?

แก้ไข

เพียงเพื่อให้รายละเอียดเพิ่มเติม:

  1. เรามีคลาส POCO และคลาสเอนทิตีที่แมปกับ AutoMapper คลาสเอนทิตีถูกใช้โดยเลเยอร์ Repository เลเยอร์ที่เก็บจากนั้นใช้เลเยอร์เพิ่มเติมของสิ่งที่เป็นนามธรรมเพื่อสื่อสารกับ Entity Framework
  2. เลเยอร์ธุรกิจไม่มีทางเข้าถึงโดยตรงไปยัง Entity Framework แม้ว่าจะไม่มีเลเยอร์เพิ่มเติมของสิ่งที่เป็นนามธรรมเหนือ ORM สิ่งนี้จำเป็นต้องใช้เลเยอร์บริการที่ผู้ใช้เลเยอร์พื้นที่เก็บข้อมูล ในทั้งสองกรณีชั้นธุรกิจจะถูกแยกออกจาก ORM โดยสิ้นเชิง
  3. อาร์กิวเมนต์หลักคือสามารถเปลี่ยน ORM ได้ในอนาคต เนื่องจากมันแปลเป็นภาษาท้องถิ่นภายในเลเยอร์ Repository สำหรับฉันมันแยกกันอยู่แล้วและฉันไม่เห็นว่าทำไมเลเยอร์ abstraction เพิ่มเติมจึงต้องมีรหัส "คุณภาพ"

3
เลเยอร์เพิ่มเติมต้องการเหตุผลมิฉะนั้นจะเป็นการละเมิด YAGNI กล่าวอีกอย่างหนึ่งคือคนที่เชื่อว่าคุณต้องการมีภาระในการพิสูจน์ว่า
ริ้น

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

4
คุณอาจต้องการเลเยอร์ที่สองของ abstraction สำหรับเลเยอร์แรกของ abstraction เหนือ ORM ในกรณีที่คุณต้องการเปลี่ยนเลเยอร์แรกด้วย
David Peterman

1
@David ในขณะที่เราเพิ่ม redudancy ให้เปลี่ยน if (boolean) ทั้งหมดเป็น if (boolean == true) และหากคุณต้องการที่จะสำรอกซ้ำของเดิมมากขึ้นถ้า (boolean == true == true ... ) และอื่น ๆ
brian

1
โพสต์ที่เกี่ยวข้องที่น่าสนใจ: ayende.com/blog/3955/repository-is-the-new-singleton
RMalke

คำตอบ:


12

วิธีนั้นก็คือความบ้าคลั่ง ไม่น่าเป็นไปได้สูงที่คุณจะต้องเปลี่ยน ORMs และถ้าคุณตัดสินใจเปลี่ยน ORM ค่าใช้จ่ายในการเขียนการแมปใหม่จะเป็นเพียงเศษเสี้ยวเล็กน้อยของต้นทุนในการพัฒนาและรักษา meta-ORM ของคุณเอง ฉันคาดหวังว่าคุณสามารถเขียนสคริปต์บางส่วนเพื่อทำงาน 95% ที่จำเป็นในการเปลี่ยน ORM

เฟรมเวิร์กในบ้านมักเป็นภัยพิบัติ การสร้างสิ่งที่คาดหวังความต้องการในอนาคตนั้นเกือบจะเป็นหายนะที่รับประกันได้ เฟรมเวิร์กที่ประสบความสำเร็จนั้นมาจากโครงการที่ประสบความสำเร็จซึ่งไม่ได้สร้างล่วงหน้าเพื่อตอบสนองความต้องการด้านจินตนาการ


12

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

อย่างน้อยที่สุดเลเยอร์ธุรกิจของคุณจำเป็นต้องตั้งโปรแกรมให้อินเทอร์เฟซที่ออบเจ็กต์ที่มีการจัดการ ORM ของคุณซึ่งเป็นตารางที่แมปจากชั้นข้อมูลสามารถนำไปใช้ได้ นอกจากนี้คุณอาจต้องสร้างเลเยอร์แบบอิงอินเตอร์เฟสของการสร้างคิวรีแบบนามธรรมเพื่อซ่อนความสามารถในการสอบถามดั้งเดิมของ ORM ของคุณ เป้าหมายหลักคือการหลีกเลี่ยงการ "อบใน" ORM เฉพาะใด ๆ ลงในโซลูชันของคุณนอกเหนือจากชั้นข้อมูล ตัวอย่างเช่นอาจเป็นการดึงดูดให้สร้างสตริงHQL ( Hibernate Query Language ) ในชั้นธุรกิจ อย่างไรก็ตามการตัดสินใจที่ไร้เดียงสาที่ดูเหมือนจะผูกชั้นธุรกิจของคุณกับไฮเบอร์เนตดังนั้นการตอกย้ำธุรกิจและชั้นการเข้าถึงข้อมูลด้วยกัน คุณควรพยายามหลีกเลี่ยงสถานการณ์นี้ให้มากที่สุด

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

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


ฉันดีใจที่. NET ได้แก้ไขปัญหานี้ด้วยการอบ Query Object ลงในแพลตฟอร์ม พอร์ต. NET ของ Hibernate ยังรองรับ
Michael Brown

2
@MikeBrown Yeah และ. NET ยังให้บริการเทคโนโลยี ORM ของคู่แข่งสองรายการด้วยตนเองโดยใช้เทคโนโลยี LINQ!
dasblinkenlight

@dasblinkenlight ฉันได้อัปเดตคำถามเพื่อให้ข้อมูลเพิ่มเติมแก่คุณ
Patrick Desjardins

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

2

UnitOfWork มักจะให้สิ่งที่เป็นนามธรรม เป็นที่ที่ต้องเปลี่ยนสถานที่เก็บของคุณขึ้นอยู่กับมันผ่านทางส่วนต่อประสาน หากคุณจำเป็นต้องเปลี่ยน O / RM เพียงใช้ UoW ใหม่เหนือสิ่งนั้น หนึ่งและทำ

BTW มันเป็นมากกว่าแค่การสลับ O / RM ให้คิดถึงการทดสอบหน่วย ฉันมีการใช้งาน UnitOfWork สามครั้งหนึ่งครั้งสำหรับ EF หนึ่งรายการสำหรับ NH (เพราะจริง ๆ แล้วฉันต้องสลับ O / RMs โครงการกลางสำหรับลูกค้าที่ต้องการการสนับสนุน Oracle) และหนึ่งในการคงอยู่ของ InMemory การคงอยู่ของ InMemory นั้นสมบูรณ์แบบสำหรับการทดสอบหน่วยหรือแม้แต่การทำต้นแบบอย่างรวดเร็วก่อนที่ฉันจะพร้อมที่จะวางฐานข้อมูลไว้ข้างหลัง

เฟรมเวิร์กนั้นใช้งานง่าย ก่อนอื่นคุณต้องมีอินเทอร์เฟซ IRepository ทั่วไป

public interface IRepository<T>
  where T:class
{
  IQueryable<T> FindBy(Expression<Func<T,Bool>>filter);
  IQueryable<T> GetAll();
  T FindSingle(Expression<Func<T,Bool>> filter);
  void Add(T item);
  void Remove(T item);

}

และส่วนต่อประสาน IUnitOfWork

public interface IUnitOfWork
{
   IQueryable<T> GetSet<T>();
   void Save();
   void Add<T>(T item) where T:class;
   void Remove<T>(T item) where T:class;
}

ถัดไปเป็นที่เก็บฐาน (ที่คุณเลือกว่าควรจะเป็นนามธรรมหรือไม่

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

   protected RepositoryBase(IUnitOfWork uow)
   { 
      _uow=uow;
   }

   public IQueryable<T> FindBy(Expression<Func<T,Bool>>filter)
   {
      return _uow.GetSet<T>().Where(filter);
   }

   public IQueryable<T> GetAll()
   {
      return _uow.GetSet<T>();
   }

   public T FindSingle(Expression<Func<T,Bool>> filter)
   {
      return _uow.GetSet<T>().SingleOrDefault(filter);
   }

   public void Add(T item)
   {
      return _uow.Add(item);
   }

   public void Remove(T item)
   {
      return _uow.Remove(item);
   }
}

การใช้ IUnitOfWork คือการเล่นของเด็ก ๆ สำหรับ EF, NH และ In Memory เหตุผลที่ฉันส่งคืน IQueryable นั้นมีเหตุผลเดียวกัน Ayende ที่กล่าวถึงในโพสต์ของเขาลูกค้าสามารถกรองเรียงลำดับจัดกลุ่มและแม้แต่ฉายผลลัพธ์โดยใช้ LINQ และคุณยังได้รับประโยชน์จากการทำฝั่งเซิร์ฟเวอร์ทั้งหมด


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

ต้องการฉันสามารถชี้ไปที่โพสต์บล็อกของฉันในการใช้งานหน่วยงาน / พื้นที่เก็บข้อมูล มันกล่าวถึงข้อกังวลที่แน่นอนจาก OP
Michael Brown

การให้เลเยอร์ชื่อไม่ได้หมายความว่าจำเป็นหรือมีประโยชน์
วินไคลน์

หมายเหตุตาม OP เขามีการทำแผนที่พิเศษระหว่างการเข้าถึงข้อมูลและชั้นธุรกิจ สำหรับฉันวัตถุธุรกิจและวัตถุเอนทิตีเหมือนกัน EF และ NH ให้บริการการทำแผนที่ API ที่น่าทึ่งซึ่งการทำแผนที่ข้อมูลมักไม่ค่อยมีความกังวล
Michael Brown

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