Страницы

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

понедельник, 15 октября 2018 г.

Onion архитектура asp.net mvc core приложения

Я сделал небольшое приложение с, как мне кажется, нормальной многослойной архитектурой. https://github.com/mirypoko/Astoms Подскажите пожалуйста, может я что-то делаю не так? Может есть какие-то более хорошие подходы.
Есть несколько вопросов:
Не слишком ли много бизнес логики в моих контроллерах? Имеет ли смысл делать статические репозитории и статический UnitOfWork, которые будут использовать ссылку на объект DbContext для работы? Или лучше чтобы они были объектами? Если ли смысл в методе Dispose класса UnitOfWork? Я сделал его по примерам из интернета, но Dispose у меня нигде не используется. Если его нужно использовать, то где и зачем? Как я понял, я использую антипаттерн Generic-repository. Чем плохим это может грозить в будущем? У приложений всегда должен быть только один контекст базы данных или сколько угодно? (имею в виду как будет грамотнее с точки зрения проектирования приложений)


Ответ

В данный момент существует множество подходов к созданию архитектуры MVC приложений (Domain Driven Design, 3d-layered architecture, onion structure, etc).
Мой любимый подход это 3х уровневая архитектура и вот почему:
Позволяет легко избавиться от зависиомтей используя Inversion of Control Каждый из уровней отвечает за свои задачи, что позволяет придерживаться принципа Single responsibility даже на уровне слоя.
Приложение ASP.NET MVC с этим подходом выглядело бы так:
Data layer - библиотека, которая организовывает связь с базой данных, в неё входят UoW, Repository pattern, EF если имеется и Data models Bussiness layer - библиотека отвечающая за сервисы, бизнес логику и DTO (data transfer objects которые связывают Data Models и View Models) Presentation layer - собственно сам MVC, сюда входят ViewModels, View, Controllers, конфигурация IoC и т.д.
Благодаря такой архитектуре изменения в одном уровне не повлекут за собой огромных изменений (а при правильном построении SOLID вообще не изменят другие слои).
Теперь по поводу реализации и вопросов:
Контроллеры должны быть по принципу as thin as possible и любая бизнес логика в них говорит о том что реализацию можно улучшить и всю логику можно и нужно выносить в сервисы. В вашей реализации присутствует обработка изображений, которую по хорошему следует вынести в отдельный сервис назвав его ImageManipulationService и убрать из контроллеров #helpers Любую статику очень сложно покрыть тестами в будущем, лучше всего придерживаться реализации этих паттернов как объектов - в будущем будет намного легче поддерживать приложение. Интерфейс IDisposable специально был разработан для высвобождения ресурсов, которые не будут уничтожены Garbage Collector`ом (в основном это касается unsafe кода), в реализации UoW считается хорошей практикой высвобождать DbContext перед уничтожением самого UoW, так что паттерн реализован хорошо. Использование Generic Repository довольно распространенная практика и это намного лучше чем писать под каждую сущность новый объект репозитория. Не вижу никаких проблем использовать этот паттерн. У приложения может быть несколько контекстов базы данных в случае если приложение использует несколько БД одновременно. На практике такое случается крайне редко и если это вопрос по поводу реализации общего интерфейса для DbContext то это скорее overengeenering чем хорошая практика.
PS. Под Generic Repository хорошо подходит реализация Generic UoW:
public interface IUnitOfWork : IDisposable { T GetRepository() where T : class; int Save(); }
public class UnitOfWork : IUnitOfWork { private Dictionary _repositories; private IDbContext _dbContext;
public UnitOfWork() : this(new AppContext()) { }
public UnitOfWork(IDbContext dbContext) { _dbContext = dbContext; _repositories = new Dictionary(); }
///

/// Search for repository in dictionary and if not exists creating new. /// /// Type of repository to create. /// Returns repository with DbContext provided by UoW. public T GetRepository() where T : class { if (!_repositories.ContainsKey(nameof(T))) { var repository = (T)Activator.CreateInstance(typeof(T), _dbContext); _repositories.Add(nameof(T), repository); }
return (T)_repositories[nameof(T)]; }
/// /// Saves all pending changes. /// /// The number of objects in an Added, Modified, or Deleted state public int Save() { return _dbContext.SaveChanges(); }
/// /// Disposes current object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
private void Dispose(bool disposing) { if (disposing) { if (_dbContext != null) { _dbContext.Dispose(); _dbContext = null; } } } }

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

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