Giảm kho lưu trữ thành Gốc tổng hợp


83

Tôi hiện có một kho lưu trữ cho mọi bảng trong cơ sở dữ liệu và tôi muốn tự điều chỉnh thêm DDD bằng cách giảm chúng xuống chỉ gốc tổng hợp.

Giả sử rằng tôi có các bảng sau, UserPhone. Mỗi người dùng có thể có một hoặc nhiều điện thoại. Nếu không có khái niệm về gốc tổng hợp, tôi có thể làm điều gì đó như sau:

//assuming I have the userId in session for example and I want to update a phone number
List<Phone> phones = PhoneRepository.GetPhoneNumberByUserId(userId);
phones[0].Number = “911”;
PhoneRepository.Update(phones[0]);

Khái niệm về rễ tổng hợp dễ hiểu trên giấy hơn là trong thực tế. Tôi sẽ không bao giờ có số điện thoại không thuộc về Người dùng, vì vậy có hợp lý nếu loại bỏ PhoneRepository và kết hợp các phương pháp liên quan đến điện thoại vào UserRepository không? Giả sử câu trả lời là có, tôi sẽ viết lại mẫu mã trước đó.

Tôi có được phép có một phương thức trên UserRepository trả về số điện thoại không? Hoặc nó phải luôn trả về một tham chiếu cho Người dùng, sau đó chuyển qua mối quan hệ thông qua Người dùng để truy cập số điện thoại:

List<Phone> phones = UserRepository.GetPhoneNumbers(userId);
// Or
User user = UserRepository.GetUserWithPhoneNumbers(userId); //this method will join to Phone

Bất kể tôi mua điện thoại bằng cách nào, giả sử tôi đã sửa đổi một trong số chúng, thì làm cách nào để cập nhật chúng? Hiểu biết hạn chế của tôi là các đối tượng dưới gốc phải được cập nhật thông qua gốc, điều này sẽ hướng tôi đến lựa chọn số 1 bên dưới. Mặc dù điều này sẽ hoạt động hoàn toàn tốt với Entity Framework, nhưng điều này có vẻ cực kỳ khó mô tả, bởi vì đọc mã, tôi không biết mình đang thực sự cập nhật những gì, mặc dù Entity Framework đang giữ tab trên các đối tượng đã thay đổi trong biểu đồ.

UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);

Cuối cùng, giả sử tôi có một số bảng tra cứu mà không phải là thực sự gắn liền với bất cứ điều gì, chẳng hạn như CountryCodes, ColorsCodes, SomethingElseCodes. Tôi có thể sử dụng chúng để điền các trình đơn thả xuống hoặc vì bất kỳ lý do gì khác. Đây có phải là những kho lưu trữ độc lập không? Chúng có thể được kết hợp thành một số loại nhóm / kho lưu trữ hợp lý chẳng hạn như CodesRepository? Hoặc là chống lại các phương pháp hay nhất.


2
Quả thực là một câu hỏi rất hay, mà tôi đã phải đấu tranh với chính mình rất nhiều. Có vẻ như một trong những điểm đánh đổi mà không có giải pháp "đúng". Mặc dù các câu trả lời có sẵn tại thời điểm tôi viết, câu trả lời này là tốt và bao gồm hầu hết các vấn đề, tôi không cảm thấy như họ cung cấp bất kỳ giải pháp "cuối cùng" nào .. :(
cwap

Tôi nghe bạn, không có giới hạn nào về mức độ gần với giải pháp "đúng" mà người ta có thể nhận được. Tôi đoán chúng ta phải cố gắng hết sức mình cho đến khi chúng ta học được cách tốt hơn :)
e36M3 Ngày

+1 - Tôi cũng đang đấu tranh với điều này. Trước khi tôi có repo và lớp dịch vụ riêng biệt cho mỗi bảng. Tôi bắt đầu kết hợp những thứ này khi nó có ý nghĩa, nhưng sau đó tôi kết thúc với một lớp repo và dịch vụ với hơn 1k dòng mã. Trong phần ứng dụng mới nhất của mình, tôi đã sao lưu một chút để chỉ đặt các khái niệm có liên quan chặt chẽ trong cùng một lớp repo / dịch vụ ngay cả khi mục đó phụ thuộc. ví dụ - đối với một blog, tôi đã thêm nhận xét vào tổng hợp repo bài đăng, nhưng bây giờ tôi đã tách chúng ra thành repo / dịch vụ nhận xét riêng biệt.
jpshook

Câu trả lời:


12

Bạn được phép có bất kỳ phương thức nào bạn muốn trong kho lưu trữ của mình :) Trong cả hai trường hợp bạn đề cập, bạn nên trả về người dùng với danh sách điện thoại đã được điền. Thông thường, đối tượng người dùng sẽ không được điền đầy đủ tất cả thông tin phụ (ví dụ như tất cả địa chỉ, số điện thoại) và chúng tôi có thể có các phương pháp khác nhau để đưa đối tượng người dùng vào các loại thông tin khác nhau. Đây được gọi là tải chậm.

User GetUserDetailsWithPhones()
{
    // Populate User along with Phones
}

Để cập nhật, trong trường hợp này, người dùng đang được cập nhật, không phải chính số điện thoại. Mô hình lưu trữ có thể lưu trữ điện thoại trong bảng khác nhau và theo cách đó bạn có thể nghĩ rằng chỉ có điện thoại đang được cập nhật nhưng đó không phải là trường hợp nếu bạn nghĩ từ góc độ DDD. Về khả năng đọc, trong khi dòng

UserRepository.Update(user)

một mình nó không truyền đạt những gì đang được cập nhật, đoạn mã trên nó sẽ làm rõ những gì đang được cập nhật. Ngoài ra, nó rất có thể sẽ là một phần của cuộc gọi phương thức giao diện người dùng có thể báo hiệu những gì đang được cập nhật.

Đối với các bảng tra cứu, và thậm chí là ngược lại, sẽ rất hữu ích nếu có GenericRepository và sử dụng nó. Kho lưu trữ tùy chỉnh có thể kế thừa từ GenericRepository.

public class UserRepository : GenericRepository<User>
{
    IEnumerable<User> GetUserByCustomCriteria()
    {
    }

    User GetUserDetailsWithPhones()
    {
        // Populate User along with Phones
    }

    User GetUserDetailsWithAllSubInfo()
    {
        // Populate User along with all sub information e.g. phones, addresses etc.
    }
}

Tìm kiếm Khung thực thể kho lưu trữ chung và bạn sẽ có nhiều triển khai tốt. Sử dụng một trong những hoặc viết của riêng bạn.


@amit_g, cảm ơn vì thông tin. Tôi đã sử dụng một kho lưu trữ chung / cơ sở mà từ đó tất cả những người khác kế thừa. Ý tưởng của tôi về một nhóm hợp lý các bảng "tra cứu" vào một kho lưu trữ chỉ đơn giản là để tiết kiệm thời gian và cắt giảm số lượng kho lưu trữ. Vì vậy, thay vì tạo ColorCodeRepository và AnotherCodeRepository, tôi chỉ cần tạo CodesRepository.GetColorCodes () và CodesRepository.GetAosystemCodes (). Nhưng tôi không chắc liệu việc nhóm hợp lý các thực thể không liên quan vào một kho lưu trữ có phải là một phương pháp sai hay không.
e36M3

Ngoài ra, sau đó bạn xác nhận rằng theo quy tắc DDD, các phương thức trong kho lưu trữ tương ứng với gốc phải trả về gốc chứ không phải thực thể cơ bản trong biểu đồ. Vì vậy, trong ví dụ của tôi, bất kỳ phương thức nào trên UserRepository chỉ có thể trả về Kiểu người dùng, bất kể phần còn lại của biểu đồ trông như thế nào (hoặc một phần của biểu đồ mà tôi thực sự quan tâm như Địa chỉ hoặc Điện thoại)?
e36M3

CodesRepository là tốt nhưng sẽ khó duy trì nhất quán những gì thuộc về nó. Điều tương tự có thể được thực hiện đơn giản bằng GenericRepository <ColorCodes> GetAll (). Vì GenericRepository sẽ chỉ có các phương thức rất chung chung (GetAll, GetByID, v.v.), nó sẽ hoạt động tốt cho các bảng Tra cứu.
amit_g


2
Thật không may câu trả lời này là sai. Kho lưu trữ nên được coi như một tập hợp các đối tượng trong bộ nhớ và bạn nên tránh tải chậm. Dưới đây là bài viết tốt đẹp về điều đó besnikgeek.blogspot.com/2010/07/...
Rafał Łużyński

9

Ví dụ của bạn về kho lưu trữ Gốc tổng hợp là hoàn toàn tốt, tức là bất kỳ thực thể nào không thể tồn tại một cách hợp lý mà không phụ thuộc vào một thực thể khác không nên có kho lưu trữ của riêng nó (trong trường hợp của bạn là Điện thoại). Nếu không có sự cân nhắc này, bạn có thể nhanh chóng thấy mình với sự bùng nổ của Kho lưu trữ trong ánh xạ 1-1 đến các bảng db.

Bạn nên xem xét việc sử dụng mẫu Đơn vị Công việc cho các thay đổi dữ liệu chứ không phải chính các kho lưu trữ vì tôi nghĩ chúng khiến bạn nhầm lẫn về ý định khi nói đến các thay đổi liên tục trở lại db. Trong giải pháp EF, Đơn vị công việc về cơ bản là một trình bao bọc giao diện xung quanh Ngữ cảnh EF của bạn.

Liên quan đến kho lưu trữ dữ liệu tra cứu của bạn, chúng tôi chỉ cần tạo một ReferenceDataRepository chịu trách nhiệm về dữ liệu không thuộc về một thực thể miền cụ thể (Quốc gia, Màu sắc, v.v.).


1
cảm ơn bạn. Tôi không chắc Unit of Work thay thế kho lưu trữ như thế nào? Tôi đã sử dụng UOW theo nghĩa là sẽ có một lệnh gọi SaveChanges () duy nhất đến ngữ cảnh Entity Framework ở cuối mỗi giao dịch kinh doanh (kết thúc yêu cầu HTTP). Tuy nhiên, tôi vẫn đi qua Kho lưu trữ (nơi chứa ngữ cảnh EF) để truy cập dữ liệu. Chẳng hạn như UserRepository.Delete (người dùng) và UserRepository.Add (người dùng).
e36M3

5

Nếu điện thoại không có ý nghĩa gì đối với người dùng, thì đó là một thực thể (nếu Bạn quan tâm đến danh tính của nó) hoặc đối tượng giá trị và phải luôn được sửa đổi thông qua người dùng và được truy xuất / cập nhật cùng nhau.

Hãy nghĩ về các gốc tổng hợp như các trình xác định ngữ cảnh - chúng vẽ các ngữ cảnh cục bộ nhưng lại nằm trong ngữ cảnh toàn cầu (Ứng dụng của bạn).

Nếu Bạn tuân theo thiết kế theo hướng miền, kho lưu trữ phải là 1: 1 cho mỗi gốc tổng hợp.
Không có lời bào chữa.

Tôi cá rằng đây là những vấn đề Bạn đang gặp phải:

  • khó khăn kỹ thuật - đối tượng trở kháng không phù hợp. Bạn đang vật lộn với các đồ thị toàn bộ đối tượng liên tục một cách dễ dàng và khung thực thể không giúp được gì.
  • mô hình miền là trung tâm dữ liệu (trái ngược với trung tâm hành vi). vì điều đó - Bạn mất kiến ​​thức về hệ thống phân cấp đối tượng (các bối cảnh đã đề cập trước đó) và về mặt kỳ diệu, mọi thứ trở thành một gốc tổng hợp.

Tôi không chắc làm thế nào để khắc phục sự cố đầu tiên, nhưng tôi nhận thấy rằng việc sửa lỗi thứ hai sẽ khắc phục sự cố đầu tiên đủ tốt. Để hiểu ý tôi về hành vi là trung tâm, hãy thử bài báo này .

Ps Giảm kho lưu trữ xuống gốc tổng hợp không có ý nghĩa gì.
Pps Tránh "CodeRepositories". Điều đó dẫn đến dữ liệu làm trung tâm -> mã thủ tục.
Ppps Tránh đơn vị của mẫu công việc. Các gốc tổng hợp nên xác định ranh giới giao dịch.


1
Khi liên kết đến bài báo không còn hoạt động, sử dụng một này thay vì: web.archive.org/web/20141021055503/http://www.objectmentor.com/...
JwJosefy

3

Đây là một câu hỏi cũ, nhưng tôi nghĩ đáng để đăng một giải pháp đơn giản.

  1. EF Context đã cung cấp cho bạn cả Đơn vị công việc (theo dõi các thay đổi) và Kho lưu trữ (tham chiếu trong bộ nhớ đến nội dung từ DB). Không bắt buộc phải trừu tượng hóa thêm.
  2. Xóa DBSet khỏi lớp ngữ cảnh của bạn, vì Điện thoại không phải là cơ sở tổng hợp.
  3. Thay vào đó, hãy sử dụng thuộc tính điều hướng 'Điện thoại' trên Người dùng.

static void updateNumber (int userId, string oldNumber, string newNumber)

static void updateNumber(int userId, string oldNumber, string newNumber)
    {
        using (MyContext uow = new MyContext()) // Unit of Work
        {
            DbSet<User> repo = uow.Users; // Repository
            User user = repo.Find(userId); 
            Phone oldPhone = user.Phones.Where(x => x.Number.Trim() == oldNumber).SingleOrDefault();
            oldPhone.Number = newNumber;
            uow.SaveChanges();
        }

    }

Trừu tượng là không bắt buộc, nhưng được khuyến khích. Entity Framework vẫn chỉ là một nhà cung cấp và một phần của cơ sở hạ tầng. Thậm chí không chỉ là vấn đề điều gì sẽ xảy ra nếu nhà cung cấp thay đổi, mà trong các hệ thống lớn hơn, bạn có thể có nhiều loại nhà cung cấp duy trì các khái niệm miền khác nhau cho các phương tiện lâu dài khác nhau. Đây là kiểu trừu tượng cực kỳ dễ thực hiện từ rất sớm, nhưng lại gây khó khăn cho việc cấu trúc lại theo đủ thời gian và độ phức tạp.
Joseph Ferris

1
Tôi thấy rất khó để giữ lại các lợi ích của EF'S ORM (ví dụ như tải chậm, có thể truy vấn) khi tôi thử và tóm tắt vào giao diện kho lưu trữ.
Chalky

Đó là một cuộc thảo luận thú vị, chắc chắn. Vì tải chậm rất cụ thể cho việc triển khai, tôi thấy giá trị của nó bị giới hạn ở cơ sở hạ tầng (đối tượng miền vào và ra với bản dịch giới hạn lớp). Rất nhiều triển khai mà tôi đã thấy gặp phải sự cố khi thực hiện một phép trừu tượng chung. Tôi có xu hướng thực hiện một cách rõ ràng, bởi vì các phương thức chung có rất ít giá trị miền. EF thực sự làm cho các truy vấn có khả năng sử dụng cao, nhưng vấn đề trở thành vai trò của kho lưu trữ - tức là các kho lưu trữ được bộ điều khiển sử dụng bỏ lỡ lợi ích của tính trừu tượng.
Joseph Ferris

0

Nếu thực thể Điện thoại chỉ có ý nghĩa cùng với một Người dùng gốc tổng hợp, thì tôi cũng sẽ nghĩ rằng thao tác thêm bản ghi Điện thoại mới là trách nhiệm của đối tượng miền Người dùng thông qua một phương thức cụ thể (hành vi DDD) và điều đó có thể hoàn toàn hợp lý vì một số lý do, lý do không thể thiếu là chúng ta nên kiểm tra đối tượng Người dùng tồn tại vì thực thể Điện thoại phụ thuộc vào sự tồn tại của nó và có thể giữ khóa giao dịch trên đó trong khi thực hiện thêm kiểm tra xác thực để đảm bảo không có quy trình nào khác đã xóa tổng hợp gốc trước đó chúng tôi đã hoàn thành xác nhận hoạt động. Trong các trường hợp khác với các loại tổng hợp gốc khác, bạn có thể muốn tổng hợp hoặc tính toán một số giá trị và duy trì nó trên các thuộc tính cột của tổng hợp gốc để xử lý hiệu quả hơn bằng các thao tác khác sau này.

Ngoài ra, nếu bạn muốn sử dụng các phương pháp truy xuất tất cả điện thoại bất kể người dùng sở hữu chúng, bạn vẫn có thể mặc dù nó thông qua Kho lưu trữ người dùng, bạn chỉ cần một phương thức trả về tất cả người dùng dưới dạng IQueryable sau đó bạn có thể ánh xạ chúng để lấy tất cả điện thoại của người dùng và thực hiện tinh chỉnh truy vấn với điều đó. Vì vậy, bạn thậm chí không cần PhoneRepository trong trường hợp này. Bên cạnh đó, tôi muốn sử dụng một lớp có phương thức mở rộng cho IQueryable mà tôi có thể sử dụng ở mọi nơi không chỉ từ một lớp Kho lưu trữ nếu tôi muốn tóm tắt các truy vấn đằng sau các phương thức.

Chỉ một lưu ý để có thể xóa các thực thể Điện thoại bằng cách chỉ sử dụng đối tượng miền chứ không phải kho lưu trữ Điện thoại, bạn cần đảm bảo UserId là một phần của khóa chính Điện thoại hay nói cách khác khóa chính của Bản ghi điện thoại là khóa tổng hợp được tạo thành từ UserId và một số thuộc tính khác (tôi đề xuất một danh tính được tạo tự động) trong thực thể Điện thoại. Điều này có ý nghĩa một cách trực quan vì bản ghi Điện thoại thuộc quyền sở hữu của bản ghi Người dùng và việc xóa nó khỏi bộ sưu tập Điều hướng của người dùng sẽ tương đương với việc xóa hoàn toàn khỏi cơ sở dữ liệu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.