Как правильно реализовать шаблон хранилища?

При реализации шаблона репозитория для моего проекта ASP.NET я столкнулся с некоторыми проблемами, на которых я не могу опустить голову. Поэтому у меня есть несколько вопросов о том, как правильно реализовать шаблон репозитория.

По моему опыту я считаю, что наличие классов/моделей без поведения только в моем приложении, рядом с их репозиториями, не является хорошим ООП. Но именно так я реализовал шаблон репозитория. Я просто делаю везде, где нужен экземпляр репозитория, для выполнения некоторых действий. Результатом этого подхода было то, что у всех моих классов домена не было поведения.

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

Сценарий:

В моем классе User был список друзей с объектами пользователя в нем, чтобы представлять друзей определенного пользователя. При добавлении нового друга пользователю метод класса домена проверяет, существует ли "знакомый" в списке друзей. Если нет, он будет добавлен в список. Эти изменения необходимо отправить в базу данных для сохранения.

Я знаю, что это должно быть сделано в репозитории для каждого класса домена, но где вызовы методов репозитория принадлежат архитектуре приложения?

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

public void AddFriend(User friend)
    {
        foreach(User f in Friends)
        {
            if(f.Username == friend.Username)
            {
                throw new Exception(String.Format("{0} is already a friend.", friend.Username));
            }
        }

        Friends.Add(friend);
        userRepo.AddFriend(this.Id, friend.Id);
    }

Это хороший подход, по какой-то причине я думаю, что нет. Что касается модульного тестирования, нам нужно с этим подходом использовать некоторую инъекцию зависимости, которая говорит мне, что это не независимый класс (хорошая проверяемая единица). Я прочитал несколько сообщений от людей, говорящих, что они используют дополнительный уровень обслуживания или что-то в этом роде. Я думаю, что здесь нужна такая абстракция, но как выглядит определенный слой обслуживания? Что в нем, какие методы и т.д.?

Я видел, как некоторые другие студенты использовали статические методы в классе домена, который обеспечивает такие функции, как добавление нового объекта, обновление объекта, удаление объекта и получение всех объектов.

Пример:

public class Tram
{
    private static TramRepository Repo = new TramRepository(new DBTram());

    public static void AddTram(int tramID, TramType type, int lineNr)
    {
        Tram tram = new Tram(tramID, type, TramStatus.depot, lineNr, true, null);
        Repo.AddTram(tram);
    }

    public static List<Tram> GetAll()
    { 
        Repo.GetAll();
    }
}

Мне кажется странным, что метод добавляет новый объект в базу данных в классе домена, который является этим объектом. Также для метода GetAll() мне кажется странным иметь метод в самом классе, который получает все трамваи. Таким образом, трамвайный объект может получить все трамваи. Я думаю, что это странный способ реализации шаблона репозитория. Я прав?

Итак, какая абстракция нужна здесь? Должен ли быть дополнительный слой? Если да, то как выглядит этот слой? (Пример кода) Или я искал неправильное направление и есть ли другое решение, которое учитывает проблему модульного тестирования с шаблоном репозитория?

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

Мне сложно объяснить эту проблему, но, надеюсь, вы это понимаете.

Ответ 1

Ваши вопросы абсолютно нормальные, но не ожидайте найти абсолютный ответ. Добро пожаловать в индустрию программного обеспечения!

Вот мое мнение:

  • Хорошо ли OOP иметь приложение, основанное на архитектуре, которая рядом с их репозиториями имеет только модели/классы которые содержат значения без какого-либо поведения?

Я думаю, вы пытаетесь реализовать шаблон репозитория, но вы пропустите более высокий архитектурный вид. Большинство приложений, по крайней мере, разделены на три уровня: просмотр (презентация), бизнес и данные. Шаблоны репозитория имеют место в DataAccess, здесь вы можете найти чистый объект данных. Но этот уровень доступа к данным используется бизнес-уровнем, где вы найдете модель домена, классы с бизнес-поведением и данными. Усилия по тестированию модулей должны быть на модели домена на бизнес-уровне. Эти тесты не должны заботиться о том, как хранятся данные.

  1. Где я могу вызвать методы репозитория в архитектуре приложения?

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

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

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

  1. Способы ввода, обновления и удаления новых объектов в базе данных: они должны быть в самом классе?

Для этого я могу быть ясным и громким: пожалуйста, не делайте этого, и да, операции CRUD принадлежат к Tram Repository. Это, конечно, не бизнес-логика. Поэтому в вашем примере вам нужны два класса в двух разных слоях:

  • Трамвай может быть бизнес-объектом в бизнес-слое (здесь нет операции Crud)
  • TramRepository - это объект, который должен хранить данные для трамвая (где вы найдете операцию CRUD).

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

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

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

Также я написал статью в блоге с центром MVVM, но я думаю, что это поможет понять мой ответ.

Ответ 2

Результатом этого подхода было то, что все мои классы домена буквально не имели никакого поведения. Они были просто объектами, которые содержат данные без каких-либо методов.

  • Хорошо ли OOP иметь приложение, основанное на архитектуре, которая рядом с их репозиториями имеет только модели/классы которые содержат значения без какого-либо поведения?

Нет, конечно, нет, но именно так обучаются веб-разработчики в эти дни.

Он возвращает старый модульный стиль программирования с 1970-х (статические классы + данные в объектах).
Модульное программирование вызвало множество проблем, поэтому они изобрели инкапсуляцию и объектно-ориентированные языки для их решения.

Публикация всех данных будет прекрасной, если вы создаете простые веб-сайты:

  • Загрузка данных
  • отображаемые данные
  • сохранить данные

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

Веб-разработчикам не обучают ООП. Их обучают простому программированию - руководствуясь ими по отношению к простым веб-сайтам (это то, что нужно правительствам).