Entity Framework Entity - ข้อมูลบางอย่างจากบริการบนเว็บ - สถาปัตยกรรมที่ดีที่สุด?


10

ขณะนี้เรากำลังใช้ Entity Framework เป็น ORM ในเว็บแอปพลิเคชั่นไม่กี่แห่งและจนถึงตอนนี้มันเหมาะกับเราและข้อมูลทั้งหมดของเราจะถูกเก็บไว้ในฐานข้อมูลเดียว เราใช้รูปแบบพื้นที่เก็บข้อมูลและมีบริการ (โดเมนเลเยอร์) ซึ่งใช้สิ่งเหล่านี้และส่งคืนเอนทิตี EF โดยตรงไปยังตัวควบคุม ASP.NET MVC

อย่างไรก็ตามข้อกำหนดได้เกิดขึ้นเพื่อใช้ API ของบุคคลที่สาม (ผ่านบริการบนเว็บ) ซึ่งจะให้ข้อมูลเพิ่มเติมที่เกี่ยวข้องกับผู้ใช้ในฐานข้อมูลของเรา ในฐานข้อมูลผู้ใช้ในพื้นที่ของเราเราจะจัดเก็บ ID ภายนอกซึ่งเราสามารถมอบให้กับ API เพื่อรับข้อมูลเพิ่มเติม มีข้อมูลค่อนข้างน้อย แต่เพื่อความเรียบง่ายหนึ่งในนั้นเกี่ยวข้องกับ บริษัท ของผู้ใช้ (ชื่อผู้จัดการห้องชื่อตำแหน่งงาน ฯลฯ ) ข้อมูลนี้จะถูกใช้ในสถานที่ต่าง ๆ ทั่วทั้งเว็บแอปของเราซึ่งต่างจากการใช้งานในที่เดียว

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

ความคิดเริ่มต้นของฉันคือการสร้างคลาสโมเดล wrapper ซึ่งจะมีเอนทิตี EF (EFUser) และคลาส 'ApiUser' ใหม่ที่มีข้อมูลใหม่ - และเมื่อเราได้รับผู้ใช้เราจะได้ EFUser จากนั้นรับเพิ่มเติม ข้อมูลจาก API และเติมข้อมูลวัตถุ ApiUser อย่างไรก็ตามในขณะนี้จะเป็นการดีสำหรับการรับผู้ใช้คนเดียวมันตรงข้ามเมื่อรับผู้ใช้หลายคน เราไม่สามารถเข้าถึง API ได้เมื่อรับรายชื่อผู้ใช้

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

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

ใครบ้างมีคำแนะนำหรือข้อเสนอแนะเกี่ยวกับวิธีที่ดีที่สุดในการจัดการสถานการณ์ประเภทนี้?


it's not really sensible to fetch it on an ad-hoc basis- ทำไม ด้วยเหตุผลด้านประสิทธิภาพ?
Robert Harvey

ฉันไม่ได้หมายถึงการชน API บนพื้นฐานเฉพาะกิจฉันแค่หมายถึงการรักษาโครงสร้างเอนทิตีที่มีอยู่ตามเดิมแล้วเรียก API ad-hoc ในเว็บแอปพลิเคชันเมื่อจำเป็น - ฉันแค่หมายความว่าจะไม่เป็นเช่นนั้น มีเหตุผลเพราะจะต้องทำในหลาย ๆ ที่
stevehayter

คำตอบ:


3

กรณีของคุณ

ในกรณีของคุณทั้งสามตัวเลือกจะทำงานได้ ฉันคิดว่าตัวเลือกที่ดีที่สุดน่าจะเป็นแหล่งข้อมูลของคุณบางแห่งที่แอปพลิเคชัน asp.net ไม่ได้ตระหนักถึง นั่นคือหลีกเลี่ยงการดึงข้อมูลทั้งสองในเบื้องหน้าทุกครั้งซิงค์ API กับฐานข้อมูลอย่างเงียบ ๆ ) ดังนั้นหากเป็นตัวเลือกที่ทำงานได้ในกรณีของคุณ - ฉันบอกว่าทำ

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

ฉันจะหลีกเลี่ยงซิงเกิลฉันไม่คิดว่ามันเป็นความคิดที่ดีเลยด้วยเหตุผลมากมาย

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

ฉันเดาว่าจริง ๆ แล้วมันมีหลายคำถาม

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

คำหนึ่งหรือสองคำเกี่ยวกับการแยกข้อกังวล

ให้ฉันเถียงกับสิ่งที่ Bobson พูดเกี่ยวกับการแยกความกังวลที่นี่ ในตอนท้ายของวัน - ใส่ตรรกะที่ในหน่วยงานเช่นนั้นละเมิดความกังวลแยกเช่นเดียวกับที่ไม่ดี

การมีที่เก็บข้อมูลดังกล่าวละเมิดการแยกข้อกังวลเช่นเดียวกับที่ไม่ดีโดยการนำเสนอศูนย์กลางแบบตรรกะในเลเยอร์ตรรกะทางธุรกิจ ตอนนี้ที่เก็บข้อมูลของคุณจะรับรู้ถึงการนำเสนอที่เกี่ยวข้องกับสิ่งต่างๆเช่นวิธีที่คุณแสดงผู้ใช้ในคอนโทรลเลอร์ asp.net mvc ของคุณ

ในคำถามที่เกี่ยวข้องฉันถามเกี่ยวกับการเข้าถึงเอนทิตีโดยตรงจากตัวควบคุม ให้ฉันพูดคำตอบอย่างใดอย่างหนึ่งที่นั่น:

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

แคชเชียร์ไปที่ห้องครัว "มีคนโรคจิตอยู่ที่เคาน์เตอร์เขาต้องการมีพิซซ่าด้วย ... มันเป็นหินแกรนิตด้วย ... รอ ... เราต้องมีชื่อก่อน" เขาบอกกับพ่อครัว

“ ไม่!” พ่อครัวทำอาหารกรีดร้อง“ ไม่ใช่อีกครั้ง! คุณรู้ไหมว่าเราได้ลองทำแล้ว เขาหยิบกระดาษที่มี 400 หน้า "ที่นี่เรามีหินแกรนิตจากปี 2005 แต่ ... มันไม่มีมะกอก แต่ paprica แทน ... หรือที่นี่เป็นมะเขือเทศยอดนิยม ... แต่ลูกค้าต้องการมัน อบเพียงครึ่งนาที " "บางทีเราควรเรียกมันว่า TopTomatoGraniteRockSpecial?" "แต่ไม่คำนึงถึงชีสที่อยู่ด้านล่าง ... " แคชเชียร์: "นั่นคือสิ่งที่พิเศษควรแสดง" “ แต่การมีหินพิซซ่าก่อตัวขึ้นเหมือนปิรามิดก็เป็นสิ่งที่พิเศษเช่นกัน” พ่อครัวตอบ "อืมม ... มันเป็นเรื่องยาก ... " แคชเชียร์ที่หายากพูด

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

(อ่านคำตอบที่เหลือมันเป็น IMO ที่ดีจริงๆ)

มันไร้เดียงสาที่จะเพิกเฉยต่อความจริงที่ว่ามีฐานข้อมูล - มีฐานข้อมูลและไม่ว่าคุณต้องการให้นามธรรมยากแค่ไหน แอปพลิเคชันของคุณจะรับรู้ถึงแหล่งข้อมูล คุณจะไม่สามารถ 'สลับร้อน' ORMs มีประโยชน์แต่มันรั่วเพราะความซับซ้อนของปัญหาที่พวกเขาแก้คือและด้วยเหตุผลด้านประสิทธิภาพมากมาย (เช่นเลือก Select n + 1)


ขอบคุณสำหรับการตอบกลับอย่างละเอียด @Benjamin ฉันเริ่มสร้างต้นแบบโดยใช้โซลูชันของ Bobson ด้านบน (ก่อนที่เขาจะโพสต์คำตอบของเขา) แต่คุณเพิ่มประเด็นสำคัญบางอย่าง เพื่อตอบคำถามของคุณ: - การโทร API นั้นไม่แพงมาก (ฟรีและเร็วมาก) - ข้อมูลบางส่วนจะมีการเปลี่ยนแปลงอย่างสม่ำเสมอ (บางครั้งถึงสองสามชั่วโมง) - ความเร็วมีความสำคัญพอสมควร แต่ผู้ชมของแอปพลิเคชั่นดังกล่าวจะเห็นว่าการโหลดอย่างรวดเร็วลดน้ำหนักไม่ได้เป็นข้อกำหนดที่แน่นอน
stevehayter

@stevehayter ในกรณีนั้นฉันน่าจะทำการเรียกไปยัง API จากฝั่งไคลเอ็นต์ มันถูกกว่าและเร็วกว่าและให้การควบคุมที่ละเอียดยิ่งขึ้น
Benjamin Gruenbaum

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

@BenjaminGruenbaum - ฉันไม่แน่ใจว่าฉันทำตามอาร์กิวเมนต์ของคุณ คำแนะนำของฉันทำให้ที่เก็บทราบถึงการนำเสนอได้อย่างไร แน่นอนว่าทราบว่ามีการเข้าถึงฟิลด์ที่มีการสนับสนุน API แต่ไม่มีอะไรเกี่ยวข้องกับมุมมองที่ทำกับข้อมูลนั้น
Bobson

1
ฉันเลือกที่จะย้ายทุกอย่างไปยังฝั่งไคลเอ็นต์ - แต่เป็นวิธีการขยายใน EFUser (ซึ่งมีอยู่ในเลเยอร์การนำเสนอในชุดประกอบแยกต่างหาก) วิธีการนี้จะคืนค่าข้อมูลจาก API และตั้งค่าซิงเกิลตันดังนั้นจึงไม่ได้รับผลกระทบซ้ำ ๆ อายุการใช้งานของวัตถุสั้นมากจนฉันไม่มีปัญหาในการใช้ซิงเกิลที่นี่ วิธีนี้มีการแยกระดับ แต่ฉันก็ยังได้รับความสะดวกในการทำงานกับเอนทิตี EFUser ขอบคุณผู้ตอบแบบสอบถามทุกคนสำหรับความช่วยเหลือ การสนทนาที่น่าสนใจแน่นอน :)
stevehayter

2

ด้วยการแยกข้อกังวลอย่างเหมาะสมไม่มีอะไรเหนือระดับ Entity Framework / API ควรตระหนักได้ว่าข้อมูลมาจากไหน เว้นแต่ว่าการเรียก API นั้นมีราคาแพง (ในแง่ของเวลาหรือการประมวลผล) การเข้าถึงข้อมูลที่ใช้ควรมีความโปร่งใสเท่ากับการเข้าถึงข้อมูลจากฐานข้อมูล

วิธีที่ดีที่สุดในการใช้สิ่งนี้คือการเพิ่มคุณสมบัติพิเศษให้กับEFUserวัตถุที่สันหลังยาวโหลดข้อมูล API ตามที่ต้องการ บางสิ่งเช่นนี้

partial class EFUser
{
    private APIUser _apiUser;
    private APIUser ApiUser
    {
       get { 
          if (_apiUser == null) _apiUser = API.LoadUser(this.ExternalID);
          return _apiUser;
       }
    }
    public string CompanyName { get { return ApiUser.UserCompanyName; } }
    public string JobTitle{ get { return ApiUser.UserJobTitle; } }
}

ภายนอกครั้งแรกที่ใช้CompanyNameหรือJobTitleจะมีการเรียก API เดียว (และทำให้เกิดความล่าช้าเล็กน้อย) แต่การเรียกใช้ครั้งต่อไปทั้งหมดจนกว่าวัตถุจะถูกทำลายจะเป็นการเข้าถึงฐานข้อมูลที่รวดเร็วและง่ายดาย


ขอบคุณ @Bobson ... นี่เป็นเส้นทางเริ่มต้นจริง ๆ ที่ฉันเริ่มลง (ด้วยวิธีการขยายที่เพิ่มเข้ามาเพื่อโหลดรายละเอียดจำนวนมากสำหรับรายชื่อผู้ใช้ - ตัวอย่างเช่นการแสดงชื่อ บริษัท สำหรับรายชื่อผู้ใช้) ดูเหมือนว่าจะเหมาะกับความต้องการของฉันมาก - แต่เบนจามินด้านล่างยกประเด็นสำคัญบางอย่างดังนั้นฉันจะประเมินต่อไปในสัปดาห์นี้
stevehayter

0

แนวคิดหนึ่งคือการปรับเปลี่ยน ApiUser ให้มีข้อมูลเพิ่มเติมไม่ได้เสมอไป คุณวางวิธีบน ApiUser เพื่อดึงข้อมูล:

ApiUser apiUser = backend.load($id);
//Locally available data directly on the ApiUser like so:
String name = apiUser.getName();
//Expensive extra data available after extra call:
UserExtraData extraData = apiUser.fetchExtraData();
String managerName = extraData.getManagerName();

คุณสามารถแก้ไขสิ่งนี้ได้เล็กน้อยเพื่อใช้การโหลดข้อมูลพิเศษอย่างขี้เกียจดังนั้นคุณไม่จำเป็นต้องแตก UserExtraData จากวัตถุ ApiUser:

//Extra data fetched on first get:
String managerName = apiUser.lazyGetExtraData().getManagerName();

ด้วยวิธีนี้เมื่อคุณมีรายการข้อมูลเพิ่มเติมจะไม่ถูกเรียกใช้โดยค่าเริ่มต้น แต่คุณยังสามารถเข้าถึงได้ในขณะที่สำรวจรายการ!


ไม่แน่ใจจริงๆว่าคุณหมายถึงอะไรที่นี่แม้ว่าใน backend.load () เรากำลังทำการโหลดอยู่แล้ว - แน่นอนว่าจะโหลดข้อมูล API หรือไม่
stevehayter

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