Страницы

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

вторник, 16 октября 2018 г.

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

В своих приложениях я использую подход разделение функционала на слои, слои я стараюсь группировать на основании функциональности
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; }


Ответ

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

Основная проблема слоев DAL в типичных программах - потеря инкапсуляции, схема хранения напрямую связана с внешним интерфейсом из-за использования типов модели хранения в интерфейсах.
Если вам действительно нужен отдельный слой DAL - предлагаю вам выкинуть из его интерфейсов все упоминания моделей EF. Хранение данных должно быть полностью скрыто в слое DAL, никаких классов-сущностей или IQueryable<> наружу торчать не должно. Код уровня бизнес-логики не должен знать, простой ли запрос идет к базе - или же вызывается хранимая процедура.

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

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

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