วิธีการใช้รูปแบบพื้นที่เก็บข้อมูลอย่างถูกต้อง?


86

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

ดังนั้นฉันเดาว่าฉันควรจัดกลุ่ม อย่างไรก็ตามฉันไม่แน่ใจว่าจะจัดกลุ่มอย่างไร

ตอนนี้ฉันได้ทำการลงทะเบียน Repository เพื่อจัดการข้อมูลการลงทะเบียนทั้งหมดของฉัน อย่างไรก็ตามมี 4 ตารางที่ฉันต้องอัปเดตและก่อนที่ฉันจะมีที่เก็บ 3 แห่งเพื่อทำสิ่งนี้

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

จุดหนึ่งสามารถล็อกอินได้ (ตรวจสอบว่าคีย์ยังไม่หมดอายุหรือไม่)

แล้วฉันจะทำอย่างไรในสถานการณ์นี้? เขียนโค้ดใหม่อีกครั้ง (แตก DRY)? พยายามรวม 2 ที่เก็บเหล่านี้เข้าด้วยกันและหวังว่าจะไม่มีวิธีใดที่จำเป็นในช่วงเวลาอื่น (เช่นบางทีฉันอาจมีวิธีที่ตรวจสอบว่ามีการใช้ userName หรือไม่ - บางทีฉันอาจจะต้องใช้ที่อื่น)

นอกจากนี้ถ้าฉันรวมเข้าด้วยกันฉันจะต้องใช้ชั้นบริการ 2 ชั้นที่ไปที่ที่เก็บเดียวกันเนื่องจากฉันคิดว่าการมีตรรกะทั้งหมดสำหรับ 2 ส่วนที่แตกต่างกันของไซต์จะยาวและฉันจะต้องมีชื่อเช่น ValidateLogin (), ValdiateRegistrationForm () , ValdiateLoginRetrievePassword () และอื่น ๆ

หรือเรียก Repository ก็ได้และมีชื่อที่ฟังดูแปลก ๆ ?

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


11
+1. คำถามที่ดี
ร้องทุกข์

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

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

ฉันถามคำถามที่คล้ายกัน (แต่ไม่เหมือนกัน) ที่นี่: stackoverflow.com/questions/910156/…
Grokys

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

คำตอบ:


41

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

Repository ควรเป็นต่อAggregate rootไม่ใช่ตาราง หมายความว่า - ถ้าเอนทิตีไม่ควรอยู่คนเดียว (กล่าวคือ - ถ้าคุณมีเอนทิตีRegistrantที่มีส่วนร่วมโดยเฉพาะRegistration) - เป็นเพียงเอนทิตีไม่จำเป็นต้องมีที่เก็บควรอัปเดต / สร้าง / ดึงข้อมูลผ่านที่เก็บของรูทรวม เป็นของ.

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

แต่นั่นไม่ได้ จำกัด ให้เราใช้ที่เก็บแบบเรียงซ้อน (ที่Registrationเก็บได้รับอนุญาตให้อ้างอิงที่Licenseเก็บหากจำเป็น) นั่นไม่ได้ จำกัด ให้เราอ้างอิงที่Licenseเก็บ (ควรใช้ - ผ่าน IoC) โดยตรงจากRegistrationออบเจ็กต์

พยายามอย่าผลักดันการออกแบบของคุณผ่านความซับซ้อนที่มาจากเทคโนโลยีหรือการเข้าใจผิดบางอย่าง การจัดกลุ่มที่เก็บServiceXเพียงเพราะคุณไม่ต้องการสร้าง 2 ที่เก็บไม่ใช่ความคิดที่ดี

จะดีกว่ามากที่จะตั้งชื่อให้ถูกต้องRegistrationServiceนั่นคือ

แต่บริการที่ควรหลีกเลี่ยงในทั่วไป - พวกเขามักจะเป็นสาเหตุที่นำไปสู่รูปแบบโดเมนโรคโลหิตจาง

แก้ไข:
เริ่มใช้ IoC มันช่วยลดความเจ็บปวดจากการฉีดพึ่งพาได้อย่างแท้จริง
แทนที่จะเขียน:

var registrationService = new RegistrationService(new RegistrationRepository(),  
      new LicenseRepository(), new GodOnlyKnowsWhatElseThatServiceNeeds());

คุณจะสามารถเขียน:

var registrationService = IoC.Resolve<IRegistrationService>();

Ps จะดีกว่าถ้าใช้สิ่งที่เรียกว่าตัวระบุตำแหน่งบริการทั่วไปแต่นั่นเป็นเพียงตัวอย่างเท่านั้น


17
ฮ่าฮ่าฮ่า ... ฉันกำลังให้คำแนะนำในการใช้ที่ตั้งบริการ มีความสุขเสมอที่ได้เห็นว่าฉันเป็นคนโง่แค่ไหนในอดีต
Arnis Lapsa

1
@Arnis ดูเหมือนว่าความคิดเห็นของคุณจะเปลี่ยนไป - ฉันสนใจว่าตอนนี้คุณจะตอบคำถามนี้แตกต่างกันอย่างไร
ngm

10
@ngm มีการเปลี่ยนแปลงมากมายตั้งแต่ฉันตอบคำถามนี้ ฉันยังคงเห็นด้วยว่ารูทรวมควรวาดขอบเขตการทำธุรกรรม (บันทึกโดยรวม) แต่ฉันมองโลกในแง่ดีน้อยกว่ามากเกี่ยวกับการแยกความคงอยู่โดยใช้รูปแบบพื้นที่เก็บข้อมูล เมื่อเร็ว ๆ นี้ - ฉันใช้ ORM โดยตรงเพราะสิ่งต่างๆเช่นการจัดการการโหลดแบบกระตือรือร้น / ขี้เกียจนั้นอึดอัดเกินไป การมุ่งเน้นไปที่การพัฒนาแบบจำลองโดเมนที่สมบูรณ์มีประโยชน์มากกว่าการมุ่งเน้นไปที่การคงอยู่ของนามธรรม
Arnis Lapsa

2
@ ผู้พัฒนาไม่ไม่ว่า พวกเขาควรจะคงอยู่อย่างงมงาย คุณดึงข้อมูลรูทรวมจากภายนอกและเรียกเมธอดที่ใช้งานได้ แบบจำลองโดเมนของฉันมีการอ้างอิงเป็นศูนย์มีเพียงแบบ. net framework มาตรฐาน เพื่อให้บรรลุเป้าหมายนั้นคุณต้องมีโมเดลโดเมนที่สมบูรณ์และเครื่องมือที่ฉลาดพอ (NHibernate เป็นเคล็ดลับ)
Arnis Lapsa

1
ในทางตรงกันข้าม. การดึงข้อมูลมากกว่าหนึ่งรายการมักหมายความว่าคุณสามารถใช้ PK หรือดัชนีได้ เร็วกว่าการใช้ประโยชน์จากความสัมพันธ์ที่ EF สร้างขึ้น การรวมรูทที่น้อยกว่าการรวมรูทก็จะง่ายขึ้น
jgauffin

5

สิ่งหนึ่งที่ฉันเริ่มทำเพื่อแก้ไขปัญหานี้คือการพัฒนาบริการที่ห่อ N ที่เก็บ หวังว่าเฟรมเวิร์ก DI หรือ IoC ของคุณจะช่วยให้ง่ายขึ้น

public class ServiceImpl {
    public ServiceImpl(IRepo1 repo1, IRepo2 repo2...) { }
}

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


1
นภทัตไม่เข้าท่าเท่าไหร่ ฉันไม่ได้ใช้เฟรมเวิร์ก DI หรือ IoC ในขณะนี้เพราะฉันมีเพลทเพียงพอเหมือนเดิม
chobo2

1
หากคุณสามารถสร้างอินสแตนซ์ repos ของคุณด้วย () ใหม่คุณสามารถลองใช้ ... Public ServiceImpl (): สิ่งนี้ (Repo1 ใหม่, Repo2 ใหม่ ... ) {} เป็นตัวสร้างเพิ่มเติมในบริการ
neouser99

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

ฉันจะดำเนินการโดยเฉพาะหลังจากการรวมที่เก็บ รหัสใดไม่สมเหตุสมผล หากคุณกำลังใช้ DI รหัสในคำตอบจะทำงานในแง่ที่กรอบงาน DI ของคุณจะฉีดบริการ IRepo เหล่านั้นในรหัสความคิดเห็นโดยพื้นฐานแล้วมันเป็นเพียงงานเล็ก ๆ น้อย ๆ ในการทำ DI (โดยทั่วไปแล้วตัวสร้างพารามิเตอร์ของคุณไม่มี 'การแทรก' การอ้างอิงเหล่านั้นใน ServiceImpl ของคุณ)
neouser99

ฉันใช้ DI อยู่แล้วจึงสามารถทดสอบหน่วยได้ดีขึ้น ปัญหาของฉันคือถ้าคุณสร้างเลเยอร์บริการและ repos พร้อมรายละเอียดชื่อ ถ้าคุณต้องการใช้ที่อื่นมันจะดูแปลก ๆ เช่นชั้นการลงทะเบียนบริการที่เรียก RegRepo ในคลาสอื่น ๆ พูดเหมือน ProfileClass ดังนั้นฉันไม่เห็นตัวอย่างจากคุณว่าคุณทำอะไรเต็มที่ เช่นเดียวกับถ้าคุณเริ่มมี Repos มากเกินไปในชั้นบริการเดียวกันคุณจะมีตรรกะทางธุรกิจและตรรกะการตรวจสอบที่แตกต่างกันมาก เนื่องจากในชั้นบริการคุณมักจะใส่ตรรกะการตรวจสอบ ฉันต้องการอีกมากมาย ...
chobo2

2

สิ่งที่ฉันกำลังทำคือฉันมีคลาสฐานนามธรรมที่กำหนดไว้ดังนี้:

public abstract class ReadOnlyRepository<T,V>
{
     V Find(T lookupKey);
}

public abstract class InsertRepository<T>
{
     void Add(T entityToSave);
}

public abstract class UpdateRepository<T,V>
{
     V Update(T entityToUpdate);
}

public abstract class DeleteRepository<T>
{
     void Delete(T entityToDelete);
}

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

public class RegistrationRepository: ReadOnlyRepository<int, IRegistrationItem>,
                                     ReadOnlyRepository<string, IRegistrationItem> 

ฯลฯ ....

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


คุณพยายามสร้างที่เก็บข้อมูลทั่วไปเพื่อจัดการกับสิ่งนี้ทั้งหมดหรือไม่?
chobo2

ดังนั้นสิ่งที่พูดถึงวิธีการอัปเดต เช่นคุณมีการอัปเดต V นี้และผ่านใน T entitytoUpdate แต่ไม่มีรหัสอัปเดตจริงหรือมีอยู่หรือไม่?
chobo2

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

2

ฉันมีสิ่งนี้เป็นคลาสพื้นที่เก็บข้อมูลของฉันและใช่ฉันขยายในที่เก็บตาราง / พื้นที่ แต่บางครั้งฉันก็ต้องทำลาย DRY

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MvcRepository
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected System.Data.Linq.DataContext _dataContextFactory;

        public IQueryable<T> All()
        {
            return GetTable.AsQueryable();
        }

        public IQueryable<T> FindAll(Func<T, bool> exp)
        {
            return GetTable.Where<T>(exp).AsQueryable();
        }

        public T Single(Func<T, bool> exp)
        {
            return GetTable.Single(exp);
        }

        public virtual void MarkForDeletion(T entity)
        {
            _dataContextFactory.GetTable<T>().DeleteOnSubmit(entity);
        }

        public virtual T CreateInstance()
        {
            T entity = Activator.CreateInstance<T>();
            GetTable.InsertOnSubmit(entity);
            return entity;
        }

        public void SaveAll()
        {
            _dataContextFactory.SubmitChanges();
        }

        public Repository(System.Data.Linq.DataContext dataContextFactory)
        {
            _dataContextFactory = dataContextFactory;
        }

        public System.Data.Linq.Table<T> GetTable
        {
            get { return _dataContextFactory.GetTable<T>(); }
        }

    }
}

แก้ไข

public class AdminRepository<T> : Repository<T> where T: class
{
    static AdminDataContext dc = new AdminDataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MY_ConnectionString"].ConnectionString);

    public AdminRepository()
        : base( dc )
    {
    }

ฉันยังมี datacontext ซึ่งสร้างโดยใช้คลาส Linq2SQL.dbml

ตอนนี้ฉันมีที่เก็บมาตรฐานที่ใช้การเรียกมาตรฐานเช่น All และ Find และใน AdminRepository ของฉันฉันมีการเรียกเฉพาะ

ไม่ตอบคำถามเรื่อง DRY แม้ว่าฉันจะไม่คิดก็ตาม


"Repository" มีไว้เพื่ออะไร? และชอบ CreateInstance? ไม่แน่ใจว่าทุกสิ่งที่คุณทำคืออะไร
chobo2

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


1

รูปแบบ Repository เป็นรูปแบบการออกแบบที่ไม่ดี ฉันทำงานกับโปรเจ็กต์. Net เก่า ๆ มากมายและรูปแบบนี้มักทำให้เกิดข้อผิดพลาด "Distributed Transactions", "Partial Rollback" และ "Connection Pool Exhausted" ซึ่งสามารถหลีกเลี่ยงได้ ปัญหาคือรูปแบบพยายามจัดการการเชื่อมต่อและธุรกรรมภายใน แต่ควรจัดการที่เลเยอร์คอนโทรลเลอร์ นอกจากนี้ EntityFramework ยังสรุปตรรกะจำนวนมาก ฉันขอแนะนำให้ใช้รูปแบบบริการแทนเพื่อใช้รหัสที่แชร์ซ้ำ


0

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


ฉันจะให้ +1 ที่นี่ แต่เขาได้ระบุว่าคอนเทนเนอร์ DI หรือ IoC ไม่ใช่ตัวเลือก (ให้สิ่งเหล่านี้ไม่ใช่ประโยชน์เฉพาะของ Sharp Arch) ฉันเดาว่ามีโค้ดที่มีอยู่มากมายที่เขากำลังดำเนินการอยู่
neouser99

สิ่งที่ถือว่าเป็นเอนทิตี? นั่นคือฐานข้อมูลทั้งหมดหรือไม่? หรือนั่นคือตารางฐานข้อมูล? คอนเทนเนอร์ DI หรือ IoC ไม่ใช่ตัวเลือกในตอนนี้เนื่องจากฉันไม่ต้องการเรียนรู้ว่าสิ่งอื่น ๆ อีก 10 อย่างที่ฉันกำลังเรียนรู้ในเวลาเดียวกัน พวกเขาเป็นสิ่งที่ฉันจะตรวจสอบในการแก้ไขไซต์ครั้งต่อไปหรือโครงการถัดไปของฉัน แม้ว่าฉันไม่แน่ใจว่ามันจะเป็นไซต์นี้หรือไม่และดูเหมือนว่าต้องการให้คุณใช้ nhirbrate และฉันกำลังใช้ linq เพื่อ sql ในเวลาปัจจุบันนี้
chobo2
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.