Страницы

Поиск по вопросам

вторник, 10 декабря 2019 г.

Как правильно “разделять” приложение на слои?

#c_sharp #архитектура #проектирование #инспекция_кода


В своих приложениях я использую подход разделение функционала на слои, слои я стараюсь
группировать на основании функциональности


Data Access Object
Data Access Layer
Business Logic Layer
GUI


Для работы с бд я использую entity-framework. Довольно часто я стал ловить себя на
мысли что я не понимаю/не уверен на каком из слоев должна быть реализована та или иная
функциональность.

Например: необходимо получить список объектов из бд, пусть класс будет называться
Request, тогда я делаю так:

В DAL у меня есть generic репозиторий() c реализацией CRUD и торчащим наружу свойством
protected virtual IQueryable Entities{...}

В BLL я обращаюсь к свойству Entities вытягиваю из бд необходимые мне данные, материализую(ToList(),Single()),
преобразую в DTO объекты которые отдаю в GUI.

В целом меня такой подход устраивал всем, базовые методы(CRUD) реализованы в одном
классе реализующим generic репозиторий, методы для работы с конкретными сущностями
реализованы в слое BLL.

Но тут наступил переломный момент: мне понадобилось использовать хранимые процедуры/функции.

Для поддержания чистоты приложения доступ к хранимкам хочу(мне так кажется правильней)
сделать в слое DAL я это представляю как то так :

Сделать IRequestRepository в который в качестве зависимости передать generic репозиторий
плюс реализовать необходимые методы/функции, например такие как обращение и получения
результата хранимой функции, так же мне будет необходимо выставить наружу в generic
репозитории DbContext что бы можно было обращаться к хранимкам(_context.Database.SqlQuery(...)),
дальше же все остается так как и было.

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



Пример реализации:

public interface IRepository where T: class
{

    T GetById(object id);
    void Insert(T entity);
    void Insert(IEnumerable entities);
    void Update(T entity);
    void Update(IEnumerable entities);
    void Delete(T entity);
    void Delete(IEnumerable entities);
    IQueryable Table { get; }
    DbContext Context { get; }
}

public interface IRequestRepository
{
    Request GetById(object id);

    void Insert(Request request);
    void Insert(IEnumerable requests);
    void Update(Request request);
    void Update(IEnumerable requests);
    void Delete(Request request);
    void Delete(IEnumerable requests);
}

public class RequestRepository: IRequestRepository
{
    private readonly IRepository _requestRepository;

    public RequestRepository(IRepository requestRepository)
    {
        this._requestRepository = requestRepository;
    }

    #region Методы обертки над методами из IRepository
    Request GetById(object id) {...}
    void Insert(Request request) {...}
    void Insert(IEnumerable requests) {...}
    void Update(Request request) {...}
    void Update(IEnumerable requests) {...}
    void Delete(Request request) {...}
    void Delete(IEnumerable requests) {...}
    #endregion

    #region Методы обертки над хранимыми функциями/процедурами
    IEnumerable GetListOfActiveRequests()
    {
        var _requestList = _requestRepository.Context.Database
            .SqlQuery("Select * from dbo.GetListOfActiveRequests()");
        //...
    }
    #endregion

    //Прочие методы

}


Как все таки лучше/правильней поступить в описанном мной случае?



UPD:

исходя из ответа @PavelMayorov, если я правильно понял структура классов у меня вырисовывается
следующим образом

public interface IRequestRepository
{
    #region CRUD методы
    Request GetById(object id);
    void Insert(Request request);
    void Insert(IEnumerable requests);
    void Update(Request request);
    void Update(IEnumerable requests);
    void Delete(Request request);
    void Delete(IEnumerable requests);
    #endregion  
}

public interface IRequestHistoryRepository
{
    #region CRUD методы
    #endregion
}

public class RequestRepository: IRequestRepository
{
    private readonly DbContext _context;

    public RequestRepository(DbContext context)
    {
        this._context=context;
    }

    // Реализация необходимых методов
    // для работы с "сущностью"
}

public class RequestHistoryRepository: IRequestHistoryRepository
{
    private readonly DbContext _context;

    public RequestHistoryRepository(DbContext context)
    {
        this._context=context;
    }

    // Реализация необходимых методов
    // для работы с "сущностью"
}


вот только в этом случае будет дублирование в каждом таком репозитории кода CRUD 

try
{
    if (request == null)
        throw new ArgumentNullException("entity");

    //Создание/Чтение/Редактирование/Удаление
}
catch (DbEntityValidationException dbEx)
{
    var msg = string.Empty;

    foreach (var validationErrors in dbEx.EntityValidationErrors)
        foreach (var validationError in validationErrors.ValidationErrors)
            msg += string.Format("Property: {0} Error: {1}", validationError.PropertyName,
validationError.ErrorMessage) + Environment.NewLine;

    var fail = new Exception(msg, dbEx);
    //Debug.WriteLine(fail.Message, fail);
    throw fail;
}

    


Ответы

Ответ 1



Репозиторий со свойством IQueryable Entities - это безусловно лишняя сущность - EF уже сама по себе предоставляет неплохую реализацию репозитория (DbSet). Особенно лишними являются generic-репозитории. Вообще говоря, в простых программах слоем DAL можно считать саму библиотеку EF; делать поверх новый слой - странное занятие. Если ваша программа достаточно простая - то просто выкиньте из нее все репозитории и увидите как все стало проще :) Основная проблема слоев DAL в типичных программах - потеря инкапсуляции, схема хранения напрямую связана с внешним интерфейсом из-за использования типов модели хранения в интерфейсах. Если вам действительно нужен отдельный слой DAL - предлагаю вам выкинуть из его интерфейсов все упоминания моделей EF. Хранение данных должно быть полностью скрыто в слое DAL, никаких классов-сущностей или IQueryable<> наружу торчать не должно. Код уровня бизнес-логики не должен знать, простой ли запрос идет к базе - или же вызывается хранимая процедура. Что же до повторяющегося кода - есть стандартные способы борьбы с ним: методы-хелперы, базовые классы, AOP.

Ответ 2



Не существует идеального готового архитектурного решения на все случаи жизни. Мне неизвестны программисты, которые с самого начала своей карьеры, реализовывали бы хорошие и удобные слоения приложений. Поскольку речь идет об объектно-ориентированном языке программирования C#, то я бы предложил автору вопроса хотя бы в первом приближении ознакомиться с соответствующими приемами и шаблонами проектирования и разработки и попытаться взять их на вооружение. Классический учебник на эту тему в русском переводе называется "Приемы объектно-ориентированного проектирования. Паттерны проектирования", авторы Э.Гамма, Р.Хелм, Р.Джонсон, Д.Влиссидес. На самом деле на эту же тему и именно о C# есть более читабельная книга. Авторы Р.Мартин и М.Мартин, а называется она "Принципы, паттерны и методики гибкой разработки на языке C#". Сразу скажу что прочтение вышеозначенной литературы не сделает никого мгновенно готовым архитектором.

Комментариев нет:

Отправить комментарий