Làm thế nào để điều trị xác nhận các tài liệu tham khảo giữa các tập hợp?


11

Tôi đang vật lộn một chút với việc tham khảo giữa các tập hợp. Giả sử tổng hợp Carcó tham chiếu đến tổng hợp Driver. Tài liệu tham khảo này sẽ được mô hình hóa bằng cách có Car.driverId.

Bây giờ vấn đề của tôi là tôi nên đi bao xa để xác nhận việc tạo ra một Cartổng hợp trong CarFactory. Tôi có nên tin rằng thông qua DriverIdđề cập đến một hiện tại Driver hay tôi nên kiểm tra bất biến đó?

Để kiểm tra, tôi thấy hai khả năng:

  • Tôi có thể thay đổi chữ ký của nhà máy ô tô để chấp nhận một thực thể tài xế hoàn chỉnh. Nhà máy sau đó sẽ chỉ chọn id từ thực thể đó và chế tạo chiếc xe với nó. Ở đây bất biến được kiểm tra ngầm.
  • Tôi có thể có một tài liệu tham khảo của DriverRepositorytrong CarFactoryvà gọi một cách rõ ràng driverRepository.exists(driverId).

Nhưng bây giờ tôi đang tự hỏi không phải là quá nhiều kiểm tra bất biến? Tôi có thể tưởng tượng rằng các tập hợp đó có thể sống trong bối cảnh giới hạn riêng biệt và bây giờ tôi sẽ làm ô nhiễm BC xe với sự phụ thuộc vào DriverRep repository hoặc thực thể Driver của trình điều khiển BC.

Ngoài ra, nếu tôi nói chuyện với các chuyên gia tên miền, họ sẽ không bao giờ đặt câu hỏi về tính hợp lệ của các tài liệu tham khảo đó. Tôi cảm thấy rằng tôi làm ô nhiễm mô hình miền của mình với những mối quan tâm không liên quan. Nhưng sau đó, một lần nữa, tại một số điểm, đầu vào của người dùng nên được xác nhận.

Câu trả lời:


6

Tôi có thể thay đổi chữ ký của nhà máy ô tô để chấp nhận một thực thể tài xế hoàn chỉnh. Nhà máy sau đó sẽ chỉ chọn id từ thực thể đó và chế tạo chiếc xe với nó. Ở đây bất biến được kiểm tra ngầm.

Cách tiếp cận này hấp dẫn vì bạn nhận được séc miễn phí và nó phù hợp với ngôn ngữ phổ biến. A Carkhông được điều khiển bởi a driverId, mà bởi a Driver.

Cách tiếp cận này trên thực tế được Vaughn Vernon sử dụng trong bối cảnh giới hạn của mẫu Nhận dạng & Truy cập trong đó anh ta chuyển Usertổng hợp sang Grouptổng hợp, nhưng Groupchỉ giữ một loại giá trị GroupMember. Như bạn có thể thấy điều này cũng cho phép anh ta kiểm tra sự hỗ trợ của người dùng (chúng tôi nhận thức rõ rằng kiểm tra có thể đã cũ).

    public void addUser(User aUser) {
        //original code omitted
        this.assertArgumentTrue(aUser.isEnabled(), "User is not enabled.");

        if (this.groupMembers().add(aUser.toGroupMember()) && !this.isInternalGroup()) {
            //original code omitted
        }
    }

Tuy nhiên, bằng cách vượt qua Driverví dụ, bạn cũng mở cho mình một sửa đổi ngẫu nhiên của Driverbên trong Car. Vượt qua tham chiếu giá trị giúp dễ dàng suy luận về các thay đổi theo quan điểm của lập trình viên, nhưng đồng thời, DDD là tất cả về Ngôn ngữ Ubiquitous, vì vậy có lẽ nó đáng để mạo hiểm.

Nếu bạn thực sự có thể đưa ra những cái tên hay để áp dụng Nguyên tắc phân chia giao diện (ISP) thì bạn có thể dựa vào một giao diện không có các phương thức hành vi. Có lẽ bạn cũng có thể đưa ra một khái niệm đối tượng giá trị đại diện cho một tham chiếu trình điều khiển bất biến và chỉ có thể được khởi tạo từ một trình điều khiển hiện có (ví dụ DriverDescriptor driver = driver.descriptor()).

Tôi có thể tưởng tượng rằng các tập hợp đó có thể sống trong bối cảnh giới hạn riêng biệt và bây giờ tôi sẽ làm ô nhiễm BC xe với sự phụ thuộc vào DriverRep repository hoặc thực thể Driver của trình điều khiển BC.

Không, bạn sẽ không thực sự. Luôn có một lớp chống tham nhũng để đảm bảo rằng các khái niệm về một bối cảnh sẽ không bị đổ vào một bối cảnh khác. Nó thực sự dễ dàng hơn nhiều nếu bạn có BC dành riêng cho các hiệp hội lái xe ô tô vì bạn có thể mô hình hóa các khái niệm hiện có như CarDrivercụ thể cho bối cảnh đó.

Do đó, bạn có thể có một DriverLookupServicequy định tại BC chịu trách nhiệm quản lý các hiệp hội lái xe ô tô. Dịch vụ này có thể gọi một dịch vụ web được hiển thị bởi bối cảnh Quản lý trình điều khiển trả về các Drivertrường hợp rất có thể sẽ là các đối tượng giá trị trong ngữ cảnh này.

Lưu ý rằng dịch vụ web không nhất thiết phải là phương thức tích hợp tốt nhất giữa các BC. Bạn cũng có thể dựa vào nhắn tin trong đó, ví dụ, một UserCreatedtin nhắn từ ngữ cảnh Quản lý trình điều khiển sẽ được sử dụng trong ngữ cảnh từ xa sẽ lưu trữ một đại diện của trình điều khiển trong DB riêng của nó. Sau DriverLookupServiceđó có thể sử dụng DB này và dữ liệu của trình điều khiển sẽ được cập nhật với các tin nhắn tiếp theo (ví dụ DriverLicenceRevoked).

Tôi thực sự không thể cho bạn biết cách tiếp cận nào tốt hơn cho tên miền của bạn, nhưng hy vọng điều này sẽ cung cấp cho bạn đủ thông tin chi tiết để đưa ra quyết định.


3

Cách bạn đặt câu hỏi (và đề xuất hai phương án) như thể mối quan tâm duy nhất là driverId vẫn còn hiệu lực tại thời điểm xe được tạo.

Tuy nhiên, bạn cũng phải lo ngại rằng trình điều khiển được liên kết với trình điều khiểnId không bị xóa trước khi xe bị xóa hoặc được cấp cho một trình điều khiển khác (và cũng có thể tài xế không được chỉ định cho một chiếc xe khác (điều này nếu tên miền chỉ giới hạn tài xế được liên kết với một chiếc xe)).

Tôi đề nghị thay vì xác nhận, bạn phân bổ (sẽ bao gồm xác nhận sự hiện diện). Sau đó, bạn sẽ không cho phép xóa trong khi vẫn được phân bổ, do đó bảo vệ chống lại tình trạng chạy đua của dữ liệu cũ trong quá trình xây dựng, cũng như các vấn đề dài hạn khác. (Lưu ý rằng phân bổ cả xác nhận hợp lệ và nhãn hiệu được phân bổ và hoạt động nguyên tử.)

Btw, tôi đồng ý với @priceJones rằng sự liên kết giữa xe và tài xế có lẽ là trách nhiệm tách biệt với xe hoặc tài xế. Loại liên kết này sẽ chỉ phát triển phức tạp theo thời gian, bởi vì nó có vẻ giống như một vấn đề lập lịch trình (trình điều khiển, xe hơi, khe thời gian / cửa sổ, thay thế, vv ...) Ngay cả khi nó giống như một vấn đề đăng ký, người ta có thể muốn lịch sử đăng ký cũng như đăng ký hiện tại. Vì vậy, nó rất có thể xứng đáng với BC của mình hoàn toàn.

Bạn có thể cung cấp một sơ đồ phân bổ (chẳng hạn như số boolean hoặc số tham chiếu) trong BC của các thực thể tổng hợp được phân bổ, hoặc trong một BC riêng biệt, giả sử, người chịu trách nhiệm tạo liên kết giữa xe và tài xế. Nếu bạn thực hiện trước đây, bạn có thể cho phép (hợp lệ) các hoạt động xóa được cấp cho xe hoặc tài xế BC; nếu bạn làm sau, bạn sẽ cần ngăn chặn việc xóa khỏi xe & tài xế BC và thay vào đó hãy gửi cho họ thông qua lịch trình liên kết lái xe và tài xế.

Bạn cũng có thể phân chia một số trách nhiệm phân bổ giữa các BC như sau. Mỗi chiếc xe và tài xế BC đều cung cấp sơ đồ "phân bổ" để xác nhận và thiết lập boolean được phân bổ với BC đó; khi boolean phân bổ của chúng được đặt, BC ngăn chặn xóa các thực thể tương ứng. (Và hệ thống được thiết lập sao cho xe & tài xế BC chỉ cho phép phân bổ và phân bổ từ lịch trình liên kết xe / tài xế BC.)

Sau đó, xe và tài xế lên lịch BC duy trì lịch trình của các tài xế liên quan đến xe trong một khoảng thời gian / thời gian, hiện tại và tương lai, và thông báo cho các BC khác về việc phân bổ chỉ trong lần sử dụng cuối cùng của xe hoặc tài xế theo lịch trình.


Là một giải pháp căn cơ hơn, bạn có thể coi xe hơi & tài xế BC là những nhà máy ghi lại lịch sử chỉ nối thêm, để lại quyền sở hữu cho người lập lịch trình hiệp hội xe / lái xe. Chiếc xe BC có thể tạo ra một chiếc xe mới, hoàn chỉnh với tất cả các chi tiết của chiếc xe, cùng với số VIN của nó. Quyền sở hữu của chiếc xe được xử lý bởi lịch trình hiệp hội xe / lái xe. Ngay cả khi một hiệp hội xe / tài xế bị xóa và chính chiếc xe bị phá hủy, hồ sơ của chiếc xe vẫn tồn tại trong xe BC theo định nghĩa và chúng ta có thể sử dụng xe BC để tra cứu dữ liệu lịch sử; trong khi các hiệp hội / chủ sở hữu xe / tài xế (quá khứ, hiện tại và có khả năng trong tương lai theo lịch trình) đang được xử lý bởi một BC khác.


2

Giả sử Xe tổng hợp có tham chiếu đến Trình điều khiển tổng hợp. Tham chiếu này sẽ được mô hình hóa bằng cách có Car.driverId.

Yup, đó là cách đúng đắn để kết hợp cái này lại với nhau.

nếu tôi nói chuyện với các chuyên gia tên miền, họ sẽ không bao giờ đặt câu hỏi về tính hợp lệ của các tài liệu tham khảo đó

Không hoàn toàn là câu hỏi đúng để hỏi các chuyên gia tên miền của bạn. Hãy thử "chi phí cho doanh nghiệp là gì nếu tài xế không tồn tại?"

Tôi có thể sẽ không sử dụng DriverRep repository để kiểm tra driverId. Thay vào đó, tôi sẽ sử dụng một dịch vụ tên miền để làm điều đó. Tôi nghĩ rằng đó là một công việc tốt hơn để thể hiện ý định - dưới vỏ bọc, dịch vụ tên miền vẫn kiểm tra hệ thống hồ sơ.

Vì vậy, một cái gì đó như

class DriverService {
    private final DriverRepository driverRepository;

    boolean doesDriverExist(DriverId driverId) {
        return driverRepository.exists(driverId);
    }
}

Bạn thực sự truy vấn tên miền về driverId tại một số điểm khác nhau

  • Từ máy khách, trước khi gửi lệnh
  • Trong ứng dụng, trước khi truyền lệnh cho mô hình
  • Trong mô hình miền, trong quá trình xử lý lệnh

Bất kỳ hoặc tất cả các kiểm tra này có thể làm giảm lỗi trong đầu vào của người dùng. Nhưng tất cả họ đều làm việc từ dữ liệu cũ; tổng hợp khác có thể thay đổi ngay lập tức sau khi chúng tôi đặt câu hỏi. Vì vậy, luôn luôn có một số nguy cơ của tiêu cực / tích cực sai.

  • Trong một báo cáo ngoại lệ, chạy sau khi lệnh đã hoàn thành

Ở đây, bạn vẫn đang làm việc với dữ liệu cũ (các tổng hợp có thể đang chạy các lệnh trong khi bạn đang chạy báo cáo, bạn có thể không thấy được ghi gần đây nhất cho tất cả các tổng hợp). Nhưng kiểm tra giữa các tập hợp sẽ không bao giờ hoàn hảo (Car.create (trình điều khiển: 7) chạy cùng lúc với Driver.delete (trình điều khiển: 7)) Vì vậy, điều này cung cấp cho bạn thêm một lớp phòng thủ chống lại rủi ro.


1
Driver.deletekhông nên tồn tại Tôi chưa bao giờ thực sự thấy một miền nơi tổng hợp bị phá hủy. Bằng cách giữ AR xung quanh bạn không bao giờ có thể kết thúc với trẻ mồ côi.
plalx

1

Nó có thể giúp để hỏi: Bạn có chắc chắn xe ô tô được xây dựng với trình điều khiển? Tôi chưa bao giờ nghe nói về một chiếc xe bao gồm một người lái xe trong thế giới thực. Lý do tại sao câu hỏi này quan trọng là bởi vì nó có thể chỉ cho bạn theo hướng độc lập tạo ra ô tô và người lái xe và sau đó tạo ra một số cơ chế bên ngoài để gán một người lái xe cho một chiếc xe. Một chiếc xe có thể tồn tại mà không cần tài liệu tham khảo và vẫn là một chiếc xe hợp lệ.

Nếu một chiếc xe phải hoàn toàn có một trình điều khiển trong bối cảnh của bạn, thì bạn có thể muốn xem xét mô hình xây dựng. Mẫu này sẽ chịu trách nhiệm đảm bảo xe ô tô được chế tạo với các trình điều khiển hiện có. Các nhà máy sẽ phục vụ những chiếc xe và tài xế được xác nhận độc lập, nhưng người xây dựng sẽ đảm bảo chiếc xe có tham chiếu cần thiết trước khi nó phục vụ cho chiếc xe.


Tôi cũng nghĩ về mối quan hệ xe hơi / tài xế - nhưng giới thiệu tổng hợp DriverAssocation chỉ là di chuyển mà tham chiếu cần được xác nhận.
VoiceOfUnreason

1

Nhưng bây giờ tôi đang tự hỏi không phải là quá nhiều kiểm tra bất biến?

Tôi nghĩ vậy. Lấy một DriverId đã cho từ DB trả về một tập hợp trống nếu nó không tồn tại. Vì vậy, kiểm tra kết quả trả về làm cho việc hỏi nếu nó tồn tại (và sau đó tìm nạp) không cần thiết.

Sau đó, thiết kế lớp làm cho nó cũng không cần thiết

  • Nếu có yêu cầu "một chiếc xe đang đỗ có thể có hoặc không có tài xế"
  • Nếu một đối tượng Driver yêu cầu a DriverIdvà được đặt trong hàm tạo.
  • Nếu Carnhu cầu chỉ có DriverId, có một Driver.Idgetter. Không có setter.

Kho lưu trữ không phải là nơi dành cho các quy tắc kinh doanh

  • Một Carquan tâm nếu nó có Driver(hoặc ít nhất ID của mình). Một Driverquan tâm nếu nó có một DriverId. Quan Repositorytâm đến tính toàn vẹn dữ liệu và không quan tâm đến những chiếc xe không có tài xế.
  • DB sẽ có các quy tắc toàn vẹn dữ liệu. Các khóa không null, các ràng buộc không null, v.v. Nhưng tính toàn vẹn dữ liệu là về lược đồ dữ liệu / bảng, chứ không phải các quy tắc nghiệp vụ. Chúng tôi có mối quan hệ cộng sinh, tương quan mạnh mẽ trong trường hợp này nhưng không trộn lẫn cả hai.
  • Thực tế DriverIdlà một điều trong lĩnh vực kinh doanh được xử lý trong các lớp thích hợp.

Tách biệt các mối quan tâm Vi phạm

... xảy ra khi Repository.DriverIdExists()đặt câu hỏi.

Xây dựng một đối tượng miền. Nếu không phải là một Driverthì có thể là một đối tượng DriverInfo(chỉ là một DriverIdName, giả sử). Các DriverIdđược xác nhận khi xây dựng. Nó phải tồn tại, và là đúng loại, và bất cứ điều gì khác. Sau đó, đây là vấn đề thiết kế lớp máy khách làm thế nào để đối phó với trình điều khiển / driverId không tồn tại.

Có lẽ a Carlà tốt mà không có trình điều khiển cho đến khi bạn gọi Car.Drive(). Trong trường hợp đó, Carđối tượng của khóa học đảm bảo trạng thái riêng của nó. Không thể lái xe mà không có Driver- tốt, chưa hoàn toàn.

Tách một tài sản khỏi lớp của nó là xấu

Chắc chắn, có một Car.DriverIdnếu bạn muốn. Nhưng nó sẽ trông giống như thế này:

public class Car {
    // Non-null driver has a driverId by definition/contract.
    protected DriverInfo myDriver;
    public DriverId {get { return myDriver.Id; }}

    public void Drive() {
       if (myDriver == null ) return errorMessage; // or something
       // ... continue driving
    }
}

Không phải cái này:

public class Car {
    public int DriverId {get; protected set;}
}

Bây giờ Carphải giải quyết tất cả các DriverIdvấn đề hợp lệ - vi phạm nguyên tắc trách nhiệm duy nhất; và mã dự phòng có lẽ.

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.