Как сервисный уровень вписывается в мою реализацию репозитория? - PullRequest
52 голосов
/ 26 мая 2011

Я создал класс модели POCO и класс репозитория, который обрабатывает постоянство.Поскольку POCO не может получить доступ к хранилищу, в хранилище есть много задач бизнес-логики, что кажется неправильным.Из того, что я прочитал, похоже, мне нужен сервисный уровень, который находится между потребителями пользовательского интерфейса и уровнем хранилища.В чем я не уверен, так это в том, как он должен работать ...

В дополнение к уровню обслуживания, должен ли также быть отдельный уровень бизнес-логики, или это роль уровня обслуживания?

Должен ли быть один сервис для каждого репозитория?

Является ли уровень сервиса единственным способом, которым пользовательский интерфейс может создавать экземпляры объектов модели, или репозиторий предоставляет новый экземпляр модели для сервиса?

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

Могут ли модель, хранилище и пользовательский интерфейс выполнять вызовы на уровне обслуживания, или это просто для использования пользовательским интерфейсом?

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

Каков типичный способ вызова сервисного уровня из пользовательского интерфейса?

Какие проверки должны быть на модели по сравнению с сервисным уровнем?

Вот пример кода для моегосуществующие слои:

public class GiftCertificateModel
{
    public int GiftCerticiateId {get;set;}
    public string Code {get;set;}
    public decimal Amount {get;set;}
    public DateTime ExpirationDate {get;set;}

    public bool IsValidCode(){}
}


public class GiftCertificateRepository
{
    //only way to access database
    public GiftCertificateModel GetById(int GiftCertificateId) { }
    public List<GiftCertificateModel> GetMany() { }
    public void Save(GiftCertificateModel gc) { }
    public string GetNewUniqueCode() { //code has to be checked in db }

    public GiftCertificateModel CreateNew()
    {
        GiftCertificateModel gc = new GiftCertificateModel();
        gc.Code = GetNewUniqueCode();
        return gc;
    }              
}

ОБНОВЛЕНИЕ: В настоящее время я использую веб-формы и классический ADO.NET.Я надеюсь перейти на MVC и EF4.

ОБНОВЛЕНИЕ: Большое спасибо @Lester за его великолепное объяснение.Теперь я понимаю, что мне нужно добавить сервисный слой для каждого из моих репозиториев.Этот уровень будет ЕДИНСТВЕННЫМ способом, которым пользовательский интерфейс или другие службы могут взаимодействовать с репозиторием, и будет содержать любые проверки, которые не соответствуют объекту домена (например, проверки, которые должны вызывать репо)

public class GiftCertificateService()
{

    public void Redeem(string code, decimal amount)
    {
        GiftCertificate gc = new GiftCertificate();
        if (!gc.IsValidCode(code))
        {
            throw new ArgumentException("Invalid code");
        }

        if (amount <= 0 || GetRemainingBalance(code) < amount)
        {
            throw new ArgumentException("Invalid amount");
        }

        GiftCertificateRepository gcRepo = new GiftCertificateRepository();
        gcRepo.Redeem(code, amount);
    }

    public decimal GetRemainingBalance(string code)
    {
        GiftCertificate gc = new GiftCertificate();            
        if (!gc.IsValidCode(code))
        {
            throw new ArgumentException("Invalid code");
        }

        GiftCertificateRepository gcRepo = new GiftCertificateRepository();
        gcRepo.GetRemainingBalance(code);
    }

    public SaveNewGC(GiftCertificate gc)
    {
        //validates the gc and calls the repo save method
        //updates the objects new db ID
    }

}

Вопросы

  1. Добавляю ли я к сервису те же (и, возможно, дополнительные) свойства, которые есть на моей модели (количество, код и т. Д.) Или толькопредложить методы, которые принимают объекты GiftCertificate и прямые параметры?

  2. Я создаю экземпляр по умолчанию сущности GiftCertificate при вызове конструктора Service или просто создаю новые по мере необходимости (например, - для методов проверки в службе, которые должны вызывать методы проверкив сущности? Кроме того, тот же вопрос о создании экземпляра репозитория по умолчанию ...?

  3. Я знаю, я раскрываю функциональность репо через службу, я также выставляю методы иза также сущность (например, - IsValidCode и т. д.)?

  4. Пользовательский интерфейс может просто создать новый объект GiftCertificate напрямую, не проходя через службу (например, - вызвать проверку параметров)методы от объекта.) Если нет, как применить его?

  5. На уровне пользовательского интерфейса, когда я хочу создать новый подарочный сертификат, я вызываю проверки модели / сервиса (как IsValidExpirationDate и т. д.) непосредственно со слоя пользовательского интерфейса ИЛИ сначала я гидратирую объект, затем передам его для проверки, а затем вернусь к некоторому виду проверкиИонное резюме обратно в интерфейс?

Кроме того, если я хочу выкупить из уровня пользовательского интерфейса, сначала я вызываю методы проверки модели / сервиса из пользовательского интерфейса, чтобы предоставить обратную связь с пользователем, а затем вызываю метод Redeem, который будет запущенте же самые внутренние проверки?

Пример вызова службы для выполнения операции погашения из пользовательского интерфейса:

string redeemCode = RedeemCodeTextBox.Text;

GiftCertificateService gcService = new GiftCertificateService();
GiftCertificate gc = new GiftCertificate(); //do this to call validation methods (should be through service somehow?)

if (!gc.IsValid(redeemCode))
{
    //give error back to user
}

if (gcService.GetRemainingBalance(redeemCode) < amount)
{
    //give error back to user
}

//if no errors
gcService.Redeem(code,amount);

Пример создания нового подарочного сертификата изПользовательский интерфейс:

GiftCertificateService gcService = new GiftCertificateService();
GiftCertificate gc = new GiftCertificate();

if (!gc.IsValidExpDate(inputExpDate))
{
    //give error to user..
}

//if no errors...
gc.Code = gcService.GetNewCode();
gc.Amount = 10M;
gc.ExpirationDate = inputExpDate;
gcService.SaveNewGC(gc);
//method updates the gc with the new id...

Что-то не так в том, как создаются GC и как валидации распределяются между сущностью / службой.Пользователь / потребитель не должен беспокоиться о том, какие проверки в каком месте ... совет?

Ответы [ 3 ]

42 голосов
/ 26 мая 2011

Взгляните на S # arp Architeture . Это как лучшая архитектура для построения приложений ASP.NET MVC. Общий шаблон архитектуры должен иметь 1 хранилище на объект, который отвечает только за доступ к данным, и 1 службу на хранилище, которое отвечает только за бизнес-логику и связь между контроллерами и службами.

Чтобы ответить на ваши вопросы на основе S # arp Architeture:

В дополнение к уровню обслуживания, должен ли также быть отдельный уровень бизнес-логики или это роль уровня обслуживания?

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

Должен ли быть один уровень обслуживания для каждого хранилища?

Да - должен быть один сервис на репозиторий (не 1 сервисный уровень на репозиторий, но я предполагаю, что вы это имели в виду).

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

Хранилища и службы могут возвращать одну сущность, коллекцию сущностей или объекты передачи данных (DTO) по мере необходимости. Контроллеры передадут эти значения статическому методу конструктора в модели, который возвратит экземпляр модели.

ex Использование DTO:

GiftCertificateModel.CreateGiftCertificate(int GiftCerticiateId, string Code, decimal Amount, DateTime ExpirationDate)

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

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

Могут ли модель, хранилище и пользовательский интерфейс выполнять вызовы на уровне обслуживания, или это только для пользовательского интерфейса?

Контроллеры и другие службы должны быть единственными, выполняющими вызовы на уровне служб. Сервисы должны быть единственными, которые делают звонки в репозитории.

Предполагается ли, что на уровне сервиса все статические методы?

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

Каков типичный способ вызова уровня обслуживания из пользовательского интерфейса?

Некоторые примеры контроллеров, вызывающих сервисный уровень:

giftCertificateService.GetEntity(giftCertificateId); (which in turn is just a call to the giftCertificateRepository.GetEntity(giftCertificateId)

giftCertificateService.Redeem(giftCertificate);

Какие проверки должны быть на модели по сравнению с уровнем обслуживания?

Уже ответили выше.

UPDATE

Поскольку вы используете WebForms, может быть немного сложнее понять некоторые концепции, но все, что я упомянул, применимо, поскольку то, что я описываю, является общей парадигмой MVC. ADO.NET для доступа к данным не имеет значения, поскольку доступ к данным отделен через репозитории.

Добавляю ли я к сервису те же (и, возможно, больше) свойства, что и для моей модели (количество, код и т. Д.), Или я предлагаю только методы, которые принимают объекты GiftCertificate и прямые параметры?

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

Создаю ли я экземпляр по умолчанию сущности GiftCertificate при вызове конструктора Service или просто создаю новые по мере необходимости (например, - для методов проверки в службе, которые должны вызывать методы проверки в сущности? Кроме того, тот же вопрос о создании экземпляра репозитория по умолчанию ...?

Контроллеры и службы должны иметь закрытые поля для служб и хранилищ соответственно. Вам не следует создавать экземпляры для каждого действия / метода.

Я знаю, что я раскрываю функциональность репо через сервис, я также выставляю методы из сущности (например, - IsValidCode и т. Д.)?

Не совсем уверен, что вы имеете в виду здесь. Если сервисы возвращают сущности, то эти методы для сущностей уже доступны. Если они возвращают DTO, это означает, что вы заинтересованы только в определенной информации.

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

Пользовательский интерфейс может просто создать новый объект GiftCertificate напрямую, без прохождения через службу (например, для вызова методов проверки параметров из сущности). Если нет, то как это обеспечить?

На уровне пользовательского интерфейса, когда я хочу создать новый подарочный сертификат, я вызываю проверки модели / сервиса (например, IsValidExpirationDate и т. Д.) Непосредственно из уровня пользовательского интерфейса ИЛИ сначала гидратирую объект, а затем передаю чтобы его можно было проверить, а затем вернуть какой-либо сводный результат проверки обратно в пользовательский интерфейс?

Для этих 2 вопросов давайте рассмотрим сценарий:

Пользователь вводит информацию для создания нового сертификата и отправляет. Существует проверка на уровне поля, поэтому, если текстовое поле имеет нулевое значение или сумма в долларах отрицательна, возникает ошибка проверки. Предполагая, что все поля действительны, контроллер вызовет службу gcService.Save(gc).

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

Наконец, служба звонит gcRepository.Save(gc).

3 голосов
/ 26 мая 2011
  1. Вам не нужно создавать хранилище для каждой сущности, см. Здесь больше ,

    Обычно каждый определяет хранилище для агрегировать в домене. То есть: мы не иметь хранилище для каждой сущности! Если мы посмотрим на простую запись заказа Система Приказ сущности может быть корень совокупности заказов. Таким образом, мы будет иметь хранилище заказов.

  2. Должен ли быть один сервис для каждого хранилища? -> Не всегда, так как вы можете использовать несколько репозиториев в одном сервисе.

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

  4. Обрабатывайте ввод / диапазон и т. Д. Вид проверки на уровне пользовательского интерфейса (вы можете использовать javascript или любую другую библиотеку) и позволяйте Сервисам обрабатывать только бизнес-аспекты. Вы можете получить преимущества Атрибутов, которые делают то же самое.

  5. UI-> Service-> Repository, если репозиторий вызывает службу, чем IMO, должно быть что-то не так.


Вы меняете код,

  1. Разделить модель и хранилище.

    public class GiftCertificateModel
    {
    }
    public class GiftCertificateRepository
    {
       //Remove Model related code from here, and just put ONLY database specific code here, (no business logic also). Common methods would be Get, GetById, Insert, Update etc. 
    
        Since essence of Repository is to have common CRUD logic at one place soyou don't have to write entity specific code. 
        You will create entity specific repository in rare cases, also by deriving base repository.
    
    }
    
    public class GiftCertificateService()
    {
        //Create Model instance here
        // Use repository to fill the model (Mapper)
    
    }
    
0 голосов
/ 26 мая 2011

Вы можете создать сервис под названием GiftCertificateService.

Таким образом, вы будете координировать любое задание, которое не относится к ответственности GiftCertificateModel, в его службу. (Не путать с сервисом WCF).

Служба будет контролировать все задачи, поэтому ваш пользовательский интерфейс (или любой другой вызывающий абонент) будет использовать методы, определенные в службе.

Затем служба будет вызывать методы модели, использовать репозиторий, создавать транзакции и т. Д.

Например (на основе предоставленного вами образца кода):

public class GiftCertificateService 
{
   public void CreateCertificate() 
   {
      //Do whatever needs to create a certificate.
      GiftCertificateRepository gcRepo = new GiftCertificateRepository();
      GiftCertificateModel gc = gcRepo.CreateNew();
      gc.Amount = 10.00M;
      gc.ExpirationDate = DateTime.Today.AddMonths(12);
      gc.Notes = "Test GC";
      gcRepo.Save(gc);
   }
}

Пользовательский интерфейс вызовет метод CreateCertificate (передача аргументов и т. Д.), И метод также может что-то вернуть.

ПРИМЕЧАНИЕ. Если вы хотите, чтобы класс действовал на пользовательском интерфейсе, тогда создайте класс контроллера (если вы делаете MVC) или класс презентатора (если вы делаете MVVM и не хотите помещать все внутри ViewModel ) и использовать GiftCertificateService из этого класса.

...