Насколько ограничены обязанности репозитория? - PullRequest
3 голосов
/ 11 марта 2012

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

Например, мое приложение работает с филиалами и клиентами банка. Теперь, если мой пользователь «открывает» ветвь для каждого рабочего сеанса, и каждый новый клиент назначается на эту ветвь, я чувствую склонность вводить синглтон AppContext, который я написал, для отслеживания атрибутов окружающего приложения, таких как текущая открытая ветвь, в мой ClientRepository, и пусть этот объект назначит правильный BranchID для новой записи клиента.

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

(1) Шаблон репозитория

Ответы [ 2 ]

1 голос
/ 16 декабря 2012

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

Мы верим, что в этом совершенном мире, если у вас есть определенный охват, вы создаете для него сервис. Как ранее упоминал Микаэль, «ClientsRepository» будет предоставлять SCRUD (s-search) для клиентов, только мы называем его ClientsService (как в Service, который сообщает вам все о клиентах). Здесь у вас есть угроза, что когда неосведомленный разработчик или тот, кто находится под чрезвычайным давлением или сроками, увидит код, подобный этому:

public ModifyClientResponse ModifyClient(ModifyClientRequest request){

    ClientEntity clientEntity=new ClientEntity();
    ModifyClientResponse clientResponse = new ModifyClientResponse();

    ClientMapper mapper=new ClientMapper();
    mapper.Map(request.Client, clientEntity);

    clientEntity.Update();

    ClientDTO responseClientDto=new ClientDTO();
    mapper.Map(clientEntity, responseClient);

    clientResponse.Client=responseClientDto;

    return clientResponse
}

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

public ModifyClientResponse ModifyClient(ModifyClientRequest request){

    ClientEntity clientEntity=new ClientEntity();
    ModifyClientResponse clientResponse = new ModifyClientResponse();

    ClientMapper mapper=new ClientMapper();
    mapper.Map(request.Client, clientEntity);

    clientEntity.Update();

    if(clientEntity.Address.DataModification==DataModification.Modified){
    // all the silly business logics about address being changed
    }

    ClientDTO responseClientDto=new ClientDTO();
    mapper.Map(clientEntity, responseClient);

    clientResponse.Client=responseClientDto;

    return clientResponse
}

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

Тем не менее, я хотел бы вернуться к тому, что это должно происходить в «идеальном мире»: приведенный выше код у нас обрабатывается путем запуска сообщения с клиентским DTO, чтобы каждый мог его обработать. Таким образом, сервис Invoice может подключиться к «Client Modify» и, используя обработчик, выполнить адекватную обработку. Таким образом мы держим наших клиентов изолированными - они могут находиться в другом источнике данных / сервере / планете / независимо от того, что нужно.

Мы также стараемся сделать это еще более чистым, без пользовательских обработчиков сообщений, но с рабочими процессами. У нас есть встроенный механизм рабочего процесса (почти что-то вроде основы рабочего процесса Windows или jboss), который может подписываться и запускать рабочие процессы в зависимости от некоторых событий.

В настоящее время мы изучаем создание механизма правил ECA (http://en.wikipedia.org/wiki/Event_condition_action),, который сделал бы это еще более гибким. Но достаточно о нас:)

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

Вкратце: я согласен, хранилища должны быть кристально чистыми, ничего не должно быть, кроме данных.

1 голос
/ 11 декабря 2012

Я лично согласен с тем, что хранилище должно быть как можно более "чистым", как вы описали в первом абзаце.

Исходя из опыта, я "обманул", также написав код бизнес-логики в функциях CRUD, что часто требовало, чтобы другие хранилища были свойствами.

Работая в ASP.net, я воспользовался обобщениями и обработкой событий.

public class Repository<T, DC>
        where T : class
        where DC : DataContext, new()
{
    public delegate void RecordInsertedHandler(object s, BasicEventArgs<T> e);
    public event RecordInsertedHandler RecordInserted;

    public delegate void RecordDeletedHandler(object s, BasicEventArgs<T> e);
    public event RecordDeletedHandler RecordDeleted;

    public delegate void RecordObtainedHandler(object s, BasicEventArgs<T> e);
    public event RecordObtainedHandler RecordObtained;

    public delegate void RecordUpdatedHandler(object s, BasicEventArgs<T> e);
    public event RecordUpdatedHandler RecordUpdated;

    protected DC dc;

    public Repository(string connectionString="")
    {
        dc = ((connectionString == null) || (connectionString == "")) ? new DC() :   DynamicTypes.Instantiate<DC>(connectionString);  // See code below for DynamicTypes:

    }

    // There are similar functions for other events not shown.
    protected void OnRecordInserted(BasicEventArgs<T> obj)
    {
        if (RecordInserted != null)
        {
            RecordInserted(this, obj);
        }
    }

    // Only the Insert is shown here.
    private void Insert(T obj)
    {
        dc.GetTable<T>().InsertOnSubmit(obj);
        dc.SubmitChanges();

        OnRecordInserted(new BasicEventArgs<T>(obj));
    }
}

Основная идея заключается в том, что вы должны запускать / вызывать эти события в правильных местах ваших обычных функций CRUD. Я предполагаю, что ваш репозиторий будет членом страницы ASP.net или формы Windows. Эти контейнеры будут «слушателями событий», поскольку они могут манипулировать пользовательским интерфейсом.

Динамические типы:

public static class DynamicTypes
{        
    public static T Instantiate<T>(params object[] args)
    {
        return (T)Activator.CreateInstance(typeof(T), args);
    }
}

BasicEventArgs:

public class BasicEventArgs<T> : EventArgs
{
    private T _Data;
    private string _Message;

    public BasicEventArgs(T data, string message="") : base()
    {
        _Data = data;
        _Message = message;
    }

    public T Data
    {
        get { return _Data; }
        set { _Data = value; }
    }

    public string Message
    {
        get { return _Message; }
        set { _Message = value; }
    }
}

Хранилища:

public class BranceshRepository : Repository<Branch, YourDataContext>
{
     public BranchesRepository(string connectionString="") : base(connectionString)
     {
     }
}

public class ClientsRepository : Repository<Client, YourDataContext>
{
     public ClientsRepository(string connectionString="") : base(connectionString)
     {
     }
}

Теперь, например, у нас есть страница ASP.net:

public partial class MyPage : Page
{
      protected ClientsRepository _ClientsRepository;
      protected BranchesRepository _BranchesRepository;

      protected void Page_Load(object s, EventArgs e);
      {
           _ClientsRepository = new ClientsRepository(...);
           _BranchesRepository = new BranchesRepository(...);

           _BranchesRepository.RecordInserted += new Repository<Branch,YourDataContext>.RecordInsertedHandler(OnBranchInserted);
           _ClientsRepository.RecordInserted += new RepositoryM<Client, YourDataContext>.RecordInsertedHandler(OnClientInserted);
      }

      protected void OnBranchInserted(object s, BasicEventArgs<Branch> e)
      {
             /* e.Data is your newly-inserted branch with the newly-generated Id
                from the database. You may save this branch to Session for
                later use when your user inserts a new client.
               */
      }

      protected void OnClientInserted(object s, BasicEventArgs<Client> e)
      {
              Branch currentBranch = (Branch)Session["Branch"];
              e.Data.BranchId = currentBranch.Id;
              _ClientsRepository.Update(e.Data);                
      }

      // Control event handlers not shown, like CreateClient_BT_Click, for example.
}

Таким образом, вам не нужно вводить синглтон в ваш ClientsRepository, и у вас есть полный доступ к вашему интерфейсу в этих обработчиках событий.

...