Допустимы ли коллекции внутренних агрегатов? - PullRequest
0 голосов
/ 07 июля 2019

Допустим, у меня есть AggregateRoot с именем Folder с набором подпапок, подобным этому:

class Folder : AggregateRoot
{
  string Name { get; }
  ICollection<Folder> Folders { get; }
}

Внутренняя коллекция здесь - это на самом деле список агрегатных идентификаторов для других папок-агрегатов, который распознается лениво при перечислении.

Является ли эта конструкция допустимой в моделировании предметной области, где агрегаты не только ссылаются на другие агрегаты, но и определяют его свойство Folders как совокупность других агрегатов Folder?

Почему? Приведенный выше пример может быть не особенно удачным, но моя цель в основном состоит в том, чтобы иметь естественный способ работы с совокупными коллекциями и скрывать тот факт, что agg-ссылки разрешаются через хранилище под поверхностью. Я хочу работать с агрегатами так же легко, как с коллекциями сущностей.

Здесь я также думаю, что подпапки каким-то образом принадлежат родительскому агрегату, что коллекция действительно является способом представления места, где хранятся агрегаты, даже если это не совсем так, поскольку они хранятся. в более общем плане - через репозиторий.

Пример с рекурсивностью не был действительно важным. Основное внимание уделяется тому факту, что агрегат «кажется» владеет другими агрегатами. При внесении изменений в две папки их можно будет сохранить только одна за другой, но это должно быть нормально. Я также должен был бы включить какое-то правило, согласно которому только папки можно создавать на месте, но не добавлять вручную, чтобы они могли появляться в более чем одной коллекции agg.

Ответы [ 2 ]

0 голосов
/ 09 июля 2019

Если в агрегате содержится другой, следует избегать прямых ссылок, поскольку обычно возникает некоторая нежелательная боль.

Любой отложенная загрузка будет означать, что вы можете немного изменить дизайн, так как вам следует избегать этого.

Наиболее распространенным шаблоном является либо наличие только списка идентификаторов или списка объектов значений.Последнее представляется более подходящим в вашем случае.Тогда вы всегда можете иметь полностью загруженный AR со всеми соответствующими папками.Чтобы перейти , вам нужно извлечь соответствующую папку.

Этот конкретный пример имеет некоторые особенности, так как он представляет иерархию, но вам придется иметь дело с теми, которые в каждом конкретном случаебазы на основе.

Короче говоря: иметь какую-либо совокупную ссылку на другую, будь то через коллекцию или иным образом, не рекомендуется.

0 голосов
/ 08 июля 2019

Дочерние структуры являются допустимыми вариантами использования в моделировании домена, и часто встречаются в рекурсивных понятиях, таких как группы, теги и т. Д., Как в вашем примере папок. И мне нравится иметь дело с ними как с чистыми коллекционными объектами на уровне предметной области, без намека на постоянную логику. При написании такой доменной логики мне нравится представлять, что я имею дело с объектами, как если бы они постоянно сохранялись в оперативной памяти.

Я рассмотрю ваш рекурсивный пример для моего объяснения, но та же концепция применима к любой «коллекции» дочерних объектов, а не только к рекурсивным отношениям.

Ниже приведен пример реализации в псевдокоде с комментариями. Заранее извиняюсь, что код ближе к Python по структуре. Я хотел передать идею точно, и не беспокоиться о том, как представить ее в C #, в которой я не очень хорошо разбираюсь. Пожалуйста, задавайте вопросы о коде, если что-то не понятно.

Примечания к псевдокоду:

  1. На уровне домена вы имеете дело с коллекциями просто как с другим списком / коллекцией, не беспокоясь о сложностях, связанных с постоянством.
  2. FolderService - это ApplicationService, который обычно вызывается API. Этот сервис отвечает за сборку инфраструктурных сервисов, взаимодействие с уровнем домена и возможное сохранение.
  3. FolderTable - это воображаемое представление базы данных объекта Folder. FolderRepository вместе знает об этом классе и подробностях его реализации
  4. Сложности сохранения и извлечения объекта Folder из БД будут присутствовать только в классе FolderRepository.
  5. Метод репозитория load_by_name загружает и заполняет все вложенные папки в родительскую папку. Мы можем преобразовать это в ленивую загрузку, только при доступе, и никогда не загружать ее, если мы не пересекаем ее (может даже быть разбит на страницы в зависимости от требований, особенно если нет конкретного ограничения на количество подпапок)
class Folder(AggregateRoot):
    name: str
    folders: List[Folder]

    @classmethod
    def construct_from_args(cls, params):
        # This is a factory method
        return Folder(**params)

    def add_sub_folder(self, new_folder: Folder) -> None:
        self.folders.append(new_folder)

    def remove_sub_folder(self, existing_folder: Folder) -> None:
        # Dummy implementation. Actual implementation will be more involved
        for folder in self.folders:
            if folder.name == existing_folder.name:
                self.folders.remove(existing_folder)


class FolderService(ApplicationService):

    def add_sub_folder(parent_folder_name: str, new_folder_params: dict) -> None:
        folder_repo = _system.get_folder_repository()
        parent_folder = folder_repo.load_by_name(parent_folder_name)

        new_sub_folder = Folder.construct_from_args(new_folder_params)
        parent_folder.add_sub_folder(new_sub_folder)

        folder_repo.save(parent_folder)


class FolderTable(DBObject):
    # A DB representation of the folder domain object
    #   `parent_id` will be empty for the root folder
    name: str
    parent_id: integer


class FolderRepository(Repository):
    # Constructor for Repository
    #   that has `connection`s to the database, for later use

    # FolderRepository works exclusively with `FolderTable` class

    def load_by_name(self, folder_name: str) -> Folder:
        # Load a folder, including its subfolders, from database
        persisted_folder = self.connection.find(name=folder_name)

        parent_identifier = persisted_folder.id
        sub_folders = self.connection.find(parent_identifier)
        for sub_folder in sub_folders:
            persisted_folder.folders.append(sub_folder)

        return persisted_folder

    def save(self, folder: Folder) -> None:
        persisted_folder = self.connection.find(name=folder.name)
        parent_identifier = persisted_folder.id

        # Gather the persisted list of folders from database
        persisted_sub_folders = self.connection.find(parent_identifier)

        for sub_folder in folder.folders:
            # The heart of the persistence logic, with three conditions:

            # If the subfolder is already persisted,
            #   Do Nothing

            # If there is a persisted subfolder that is no longer a part of folders,
            #   Remove it

            # If the subfolder is not among those already persisted,
            #   Add it

Если вы видите дыры в этой реализации или моем мыслительном процессе, пожалуйста, укажите на них.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...