Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.138.200.66] |
|
Сообщ.
#1
,
|
|
|
Сегодня мы попробуем вместе с Вами заложить надежный архитектурный фундамент под приложение, разрабатываемое с использованием технологии ASP.NET MVC.
Конечно же, предложенный ниже подход не является истинно правильным в последней инстанции, тем не менее, многое из того, что будет описано, является приличной отправной точкой для старта собственных изысканий и модификаций и может быть использовано в реальных приложениях, которые пишите Вы сами. В рамках статейного примера, мне приходится рассматривать предельно простую бизнес-модель приложения, но уверяю Вас, что для рассмотрения архитектурных моментов это нам не помешает. Начнем? Приложения, которые мы пишем, оперируют данными и слой доступа к данным необходимый минимум из того, что нам необходимо реализовать при работе с ними. Встает вопрос: Как его правильно реализовать? Существует огромное число умных книжек, которые описывают огромное число паттернов и возможных подходов к решению. Мой личный опыт подсказывает, что требования зачастую индивидуальны и единого универсального решения не существует. Как всегда нужно рассматривать огромное число факторов и пытаться найти золотую середину. Стоит рассматривать вопросы производительности, безопасности, качества получаемого решения, удобства сопровождения, рентабельности и многие другие индивидуальные факторы. Все это влияет как на выбор технологии для реализации, так и на выбор соответствующей архитектуры. Мне кажется, нам следует оттолкнуться от чего более конкретного. Предположим, что перед нами стоит задача реализовать приложение для заказчика средней руки в максимально короткие сроки с перспективой сопровождения нами продукта в будущем. Пусть это будет гипотетическая Front-End система генерации и доставки рекламного контента для вино-водочной компании X. Это приложение нацелено на активную работу с данными, предполагает наличие подсистемы отчетности и отдельного приложения в виде административного интерфейса по управлению этими самыми данными, с другой стороны приложение не предполагает сколь либо сложной бизнес логики присущей многим финансовым системам. Наличие активной работы с данными и отсутствие сколь либо сложной логики наталкивает на мысль использовать тонкую модель работы с данными, где данные, по сути, выступают доменом приложения. Многие апологеты толстой модели уже взвыли, к этому моменту, по крайней мере, я надеюсь на это, - не люблю крайности. Реальность такова, что Microsoft ориентируется именно на тонкие модели при разработке приложений любой сложности, а потому оставим споры философам и продолжим ход своих рассуждений. Могу лишь заметить, что сам придержусь центристских позиций в этом вопросе. Подсистема отчетности, наверняка потребует приличного числа обращений к БД, более того ряд запросов наверняка будут иметь солидную сложность вовлекая в выборки большое число таблиц. Требования к производительности для данной подсистемы являются критичными. Что же мы можем предложить? Сжатые сроки, а это нормально для бизнеса, подталкивают нас смотреть на объектно-реляционные преобразователи, которые де-факто уже являются стандартом при разработке для малых и средних систем приложений, но подсистема отчетности требует производительности не характерной для ОРМ. Есть ли выход? Есть. Мы будем использовать промежуточный абстрактный слой между ОРМ и нашим доменом, который скроет от нас работу с ОРМ и предоставит общий программный интерфейс для доступа к данным. В случае необходимости, мы сможем переписать (или написать изначально) нужные места без применения ОРМ библиотеки используя чистый ADO.NET и хранимые процедуры, которые смогут обеспечить приемлемый уровень производительности в приложении. Параллельно мы сохраним единую модель доступа к любым данным в приложении, через абстрактный слой. Это в перспективе может позволить нам практически безболезненно изменять реализацию слоя доступа к данным, а также позволит иметь единый слой контроля обращения к данным как с использованием ОРМ, так и без нее, что может быть использовано в различных сценариях имеющих отношение к безопасности. Я предлагаю определить простой интерфейс подобный нижеприведенному: public interface IRepository<T> where T : class { T Load(int id); IList<T> ListAll(); IList<T> Find(Func<T, bool> whereClause); IList<T> Load(int pageNumber, int pageSize, out int total); IList<T> Load(Func<T, bool> whereClause, int pageNumber, int pageSize, out int total); void Insert(T obj); void Save(T obj); void Update(T obj); void Delete(T obj); } Этот интерфейс только стартовая точка, в действительности он приведен просто для демонстрации общей концепции, но суть должна быть ясна уже сейчас, если клиент будет использовать похожий интерфейс для доступа к данным он будет оставаться в неведении о том, что происходит за «фасадом», а следовательно код клиента не будет завязан на конкретную технологию работы с данными. Тут стоит обратить внимание, что реализация подобного интерфейса не есть классической реализацией паттерна Repository, однако преследует схожие с ним цели. Часто многие .NET разработчики определяют более универсальные методы, по указанию запросов используя методы вроде: IQueryable<T> Query<Y>(Expression<Func<T, bool>> where); Или же используют различные реализации шаблона Specification это отличные решения, но я по своей природе консерватор и считаю, что более прямолинейный и однозначный интерфейс ведет к написанию более простого и ясного кода впоследствии. Так же иногда в репозитории объявляются методы, нацеленные исключительно на выборку данных, а CRUD операции помещаются в некий глобальный контекст, который ассоциируется с сеансом работы с БД. Такой подход неплохо смотрится в двухзвенных приложениях, хотя и имеет порядочное число критиков, однако смею вас заверить, что для многих приложений часто бывает просто удобно поступать именно так, особенно это касается упомянутых уже двухзвенных приложений. С большой долей вероятности, в обсуждаемом приложении нам придется реализовывать IRepository<T> в двух вариациях для ORM и для прямых обращений к базе данных, а потому мне кажется логичным использовать абстрактные базовые классы. Они помогут инкапсулировать общие моменты реализации конкретных репоситориев. Для примера, но позже, я приведу реализацию такого класса, который работает с Linq to Sql, как наиболее простое решение. Однако перед этим я позволю себе пофилософствовать о некоторых особенностях реализации такого класса. Как я упоминал, мне бы хотелось скрыть от клиента детали реализации слоя обращения к данным. В частности, мне бы не хотелось, что бы клиент взаимодействовал с контекстом данных напрямую, было бы логично предположить, что контекст может быть агрегирован в такой класс, но мой опыт подсказывает, что это не лучшее решении. Как известно контекст в Linq to Sql связан с физическим соединением с базой данных, а следовательно было бы полезно иметь контекст внешним по отношению к репозиторию особенно это бывает очевидно тогда когда нам необходимо работать с несколькими репозиториями в рамках единой транзакции. Другой аргумент это не желание создавать зависимости между репозиторием и контекстом. Более того локальный контекст лично мне кажется не уместным т.к. конкретный контекст после построения модели будет наследником стандартного DataContext, а значит, что реализация абстрактного класса будет зависеть от модели построенной по конкретной базе данных, что кажется нонсенсом. Я просто уверен, что контекст должен быть внешним, но при этом постоянное написание кода в стиле: using (var context = new BlogDataContext()) { var messageProvider = new MessagesProvider(context); int total = 0; IList<Message> messages = messageProvider.Load(0, 20, out total); } Удручает и убивает во мне программиста … обещаю к концу статьи мы избавимся от использования подобного кода в ASP.NET MVC приложениях. Тут может быть несколько путей развития событий. Я остановлюсь лишь на паре из них. Первая использовать паттерн Registry в чистом виде, а именно реализовать синглетный класс предоставляющий клиенту статические свойства, возвращающие конкретные репозитории которые инициализируются контекстом передаваемым в конструктор или настраиваемым по конфигурации. При этом сами репозитории конечно могут (обязаны) не являться статическими полями. Ниже приведена сильно упрощенная реализация такого решения, что бы разговор был более предметным: public class DataRepository { protected DataContext context = …; protected static DataRepository _repository = new DataRepository(); protected static DataRepository Instance { get { return _repository; } } protected IRepository<Message> messageProvider = new MessagesProvider(Instance.context); protected IRepository<User> userProvider = new MessagesProvider(Instance.context); public static IRepository<Message> MessageProvider { get { return Instance.messageProvider; } } public IRepository<User> UserProvider { get { return Instance.userProvider; } } … } За использование подобного подхода говорит удобство использования класса клиентом. IList<Message> messages = DataRepository.MessageProvider.ListAll(); User current = DataRepository.UserProvider.LoadById(...); Клиентский код максимально просто писать и код оперирующий данными максимально унифицирован – «мечта клиента». Более того, можно легко реализовать фиктивную версию реестра, что позволит писать Unit тесты не обращающиеся на прямую к данным. Легко организовать контроль транзакций в приложении, для этого необходимо в DataRepository добавить методы BeginTransaction, Commit, Rollback, а также обеспечить возможность использования единственного контекста в рамках работы каждого из репозиториев. Тут же стоит обратить внимание на то, что время жизни объекта DataRepository обычно совпадает с временем сеанса работы клиента с ним. Следовательно нам будет необходимо обеспечить методы по корректному освобождению ресурсов, что в результате так или иначе заставит нас реализовывать IDisposable и использовать using или операции на подобии Open(), Close(). У данного подхода есть и пара более существенных недостатков, которыми нельзя пренебрегать. Класс по своей сути является глобальной переменной и с этим ничего нельзя сделать, второй недостаток это наличие зависимостей в классе, который частично может быть решен посредством фабрики или локатора. Третьим недостатком, на мой взгляд, является постоянная необходимость расширять репозитории по мере разрастания проекта, что может стать дополнительной головной болью. Размышление о фабриках и локаторах было уместным, оно же позволит нам найти альтернативное решение (или же использовать схожий подход, но несколько в другом ключе). Подход к тому же максимально удобный для ASP.NET MVC приложения. Кто не догадался речь пойдет об использовании Dependency Injection и Inversion of control. С помощью библиотек-контейнеров на подобие StructureMap или Spring удобно разрешать зависимости, более того, многие из этих библиотек имеют удобные средства для управления жизненным циклом объектов, ну и в довесок код написанный с их использованием является слабосвязанным, а следовательно легко поддается тестированию. (Вариант с модификацией реестра с их использованием выходит за рамки рассматриваемых мной концепции, но основываясь на примере ниже, Вы без труда реализуете подобное и для реестра.) Я отдаю предпочтение библиотеке Unity. Unity, с моей точки зрения, солидная библиотека, обладающая всем необходимым и практически лишенная слабых мест. Если же приводить причины, почему ею не стоит пользоваться, то, наверное, единственная это та, что нет некоторых вещей из «коробки» доступных в паре других библиотек. Что служит оправданием для мужчин слабых духом И так вернемся к предмету. Я хочу «впрыснуть» контекст в провайдер посредством Unity, более того я собираюсь переложить на ее плечи и всю ответственность за контролем времени жизни этого самого контекста, что позволит программисту сложить с себя обязанность освобождать ресурсы явно. Все это будет делаться автоматически. Звучит многообещающе. Вернемся же к базовому репозиторию (и когда мы о нем говорили в последний раз?) ниже приведена возможная реализация для Linq to Sql: public abstract class L2SRepository<T> : IRepository<T> where T : class { protected DataContext _context; public L2SRepository(DataContext context) { _context = context; } #region IDataRepository<T> Members public abstract T Load(int id); public virtual IList<T> ListAll() { return _context.GetTable<T>().ToList(); } public virtual IList<T> Find(Func<T, bool> whereClause) { return _context.GetTable<T>().Where(whereClause).ToList(); } public virtual IList<T> Load(int pageNumber, int pageSize, out int total) { var result = _context.GetTable<T>().Skip((pageNumber * pageSize) - 1).Take(pageSize).ToList(); total = result.Count(); return result; } public virtual IList<T> Load(Func<T, bool> whereClause, int pageNumber, int pageSize, out int total) { var result = _context.GetTable<T>().Where(whereClause).Skip((pageNumber * pageSize) - 1).Take(pageSize).ToList(); total = result.Count(); return result; } public virtual void Save(T obj) { _context.GetTable<T>().InsertOnSubmit(obj); } public virtual void Update(T obj) { _context.GetTable<T>().Attach(obj, true); } public virtual void Insert(T obj) { _context.GetTable<T>().InsertOnSubmit(obj); } public virtual void Delete(T obj) { _context.GetTable<T>().DeleteOnSubmit(obj); } protected DataContext Context { get { return _context; } } #endregion } Зависимость впрыскивается в класс, через конструктор. Методы умышленно сделаны виртуальными, я предполагаю (я просто уверен в этом ), что конкретные реализации зачастую будут иметь необходимость изменять реализацию работы методов базового класса. Реализация «в лоб» некторые методы, полагаются на особенности реализации контроля обнаружения конфликтов, к примеру Update отработает исключительно для сущностей замапленых на таблицы с timestamp или version, но для демонстрации основной идеи больше и не нужно. Дополнительно также стоит заметить, что реализация не затрагивает некоторых моментов, которые очевидно будут востребованы в нашем приложении, но я считаю себя просто обязанным об этом упомянуть. Часто вам потребуется фетчинг данных и было бы неплохо иметь поддержку его настройки и использования его, а наследник мог бы переопределять стратегии фетчинга в своей реализации. Другим важным аспектом является управление разрешением конфликтов и базовые аспекты могут быть реализованы здесь по месту, с правом предоставления стратегии разрешения потомкам, что конечно не мешает реализовать стратегию «по умолчанию». Осталось показать тривиальную реализацию типичного репозитория: public class MessagesProvider : L2SRepository<Message> { public MessagesProvider(DataContext context) : base(context) { } public override Message Load(int id) { return Context.GetTable<Message>().Where(item => item.Id == id).SingleOrDefault(); } } В него же можно добавлять специфические методы по получению данных. Вот код программной регистрации объектов в контейнере: IUnityContainer container = new UnityContainer(); container.Configure<InjectedMembers>().ConfigureInjectionFor<BlogDataContext>(new InjectionConstructor(connectionString)); container.RegisterType<DataContext, BlogDataContext>(); container.RegisterType<IRepository<Message>, MessagesProvider>(); Я указываю контейнеру использовать конструктор принимающий connectionString при конструировании экземпляра контекста. Теперь репозиторием можно пользоваться так: IRepository<Message> provider = Container.Resolve<IRepository<Message>>(); Message message = provider.Load(1); На заметку: регистрацию можно осуществить и декларативно посредством конфигурационного файла. Отлично движемся дальше. На очереди ASP.NET MVC, а именно контроллеры. Я предлагаю написать собственную фабрику для инстанцирования контроллеров или воспользоваться бесчисленными примерами из сети интернет. public class UnityControllerFactory : DefaultControllerFactory { IUnityContainer container; public UnityControllerFactory(IUnityContainer container) { this.container = container; } protected override IController GetControllerInstance(Type controllerType) { try { if (!typeof(IController).IsAssignableFrom(controllerType)) throw new ArgumentException( "Type is not a controller”, "controllerType"); return container.Resolve(controllerType) as IController; } catch { return null; } } } Суть этой фабрики, заменить стандартную фабрику ASP.NET MVC, это позволит нам истанцировать контроллеры посредством Unity, что даст нам возможность впрыскивать в них нужные зависимости, например нужные репозитории Теперь контроллер в приложении может выглядеть вот так расчитывая на то, что вышепреведенная фабрика без труда инстанцирует котроллер: public class MessageController : Controller { IRepository<Message> _repository; public MessageController(IRepository<Message> repository) { _repository = repository; } … } А зависимость, будет разрешена контейнером на основе конфигурации. Вот пример настройки установки нужной фабрики в файле global.asax: ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(container)); Вероятно, в том сценарии в котором я собираюсь использовать фабрику, контейнер можно было бы устанавливать через соответствующие свойство в UnityControllerFactory. IUnityContainer container; public IUnityContainer Container { set { container = value; } } А саму фабрику объявить статической переменной в Application: private static UnityControllerFactory factory = new UnityControllerFactory(); protected void Application_Start() { RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(factory); } На десерт я выполню свое обещание и покажу, как можно переложить на плечи Unity заботу об освобождении ресурсов. Мы воспользуемся возможностью Unity создавать дочерние контейнеры. Суть идеи проста в начале запроса мы создаем дочерний контейнер, настраиваем его специальным образом, заставляя контейнер создавать единственный экземпляр объекта, а в конце реквеста будем уничтожать дочерний контейнер. Это позволит нам без малейших усилий реализовать логику в которой будет создаваться единственный контекст на HttpRequest, что на мой взгляд является идеальным решением для веб-приложений, как с точки зрения использования ресурсов, так и с точки зрения удобства использования. Для этого добавим в global.asax следующий код: private static IUnityContainer main = new UnityContainer(); [ThreadStatic] private static IUnityContainer perRequestContainer = null; protected void Application_BeginRequest() { perRequestContainer = main.CreateChildContainer(); perRequestContainer.Setup(ConnectionString); factory.Container = perRequestContainer; } protected void Application_EndRequest() { perRequestContainer.Dispose(); } protected string ConnectionString { get { return ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString; } } Секрет конфигурации дочернего контейнера кроется в следующей строке кода: perRequestContainer.Setup(ConnectionString); Как Вы наверное догадались Setup является не чем иным как утилитным extention методом написанным мной для облегчения программной конфигурации зависимостей в нашем приложении: public static class UnityRegistrator { public static void Setup(this IUnityContainer container, string connectionString) { container.Configure<InjectedMembers>().ConfigureInjectionFor<BlogDataContext>(new InjectionConstructor(connectionString)); container.RegisterType<DataContext, BlogDataContext>(new ContainerControlledLifetimeManager()) .RegisterType<IRepository<Message>, MessagesProvider>(new ContainerControlledLifetimeManager()); … } } ContainerControlledLifetimeManager является имеено тем Lifetime менеджером, который позволит получать единственный экземпляр объекта как в случае инстанцирования, так и в случае впрыскивания зависимости. Если говорить об альтернативных реализациях, то можно написать собственный Lifetime менеджер который будет сохранять объекты в HttpRequest или же воспользоваться библиотекой StructureMap, которая способность ассоциировать объекты с HttpRequest реализует путем тривиальной настройки. В этих вариантах не понадобятся дочерние контейнеры. Мне кажется мы добились основных целей. Наметили вариант развития приложения. Максимально упростили себе его тестирование и сопровождение. Код является слабосвязанным. Обеспечили возможность использовать прозрачно для клиента альтернативные реализации репоситориев оставляя себе открытой дверь для прямого взаимодействия с БД. Ну и самое главное, реализовали возможность иметь единственный контекст на реквест с автоматическим освобождением неуправляемых ресурсов. До новых встреч |
Сообщ.
#2
,
|
|
|
Хорошая статья, держи +1
Немного мыслей - конкретные реализации IRepository<T> если в терминологии Фаулера, это по сути Шлюзы таблицы данных. Твоя имплементация держать в репозитории предзаготовленные шлюзы довольно симпотичная, но есть как по мне несколько недостатков - синглтоны вечно живут в памяти и жрут ее плюс реализовывать шлюзы должен более-менее толковый разработчик с учетом разнопоточного и многопользовательского доступа. |
Сообщ.
#3
,
|
|
|
Цитата PIL @ Твоя имплементация держать в репозитории предзаготовленные шлюзы довольно симпотичная, но есть как по мне несколько недостатков - синглтоны вечно живут в памяти и жрут ее плюс реализовывать шлюзы должен более-менее толковый разработчик с учетом разнопоточного и многопользовательского доступа. Согласен. Но в вебе используя Unity, мы легко устраним такую проблему автоматически освобождая ресурсы. Реестр проживет не более одного реквеста. В обычных многопользовательских приложениях мы можем реализовать хранение реестра по стратегии Per thread, через TLS память. К тому же ниже предлагается другое решение. ориентированное исключительно на ASP.NET MVC. Если бы мне нужен был реестр шлюзов в ASP.NET MVC приложении я бы предпочел наверное иметь реестр где репозитории просто впрыскиваются через свойства зависимостей. Цитата PIL @ Хорошая статья, держи +1 Спасибо. |
Сообщ.
#4
,
|
|
|
juice, Истину глаголишь
Единственное что хотелось бы добавить, так это то что по-хорошему, контроллеры не должны ввобще ничего "знать" о репозиториях. В нашем проеке (тоже ASP.NET MVC) контроллеры работают со слоем служб, а службы уже непосредственно используют репозитории. Например: Репозиторий: public class SqlEmailRepository : SqlRepositoryBase, IEmailRepository { public SqlEmailRepository(SqlRepositoryBase rep){ db = rep.db; } /// <summary> /// Initializes a new instance of the <see cref="SqlEmailRepository"/> class. /// </summary> public SqlEmailRepository(){ db = new SomeOurDataContext(); } [ExceptionCallHandler(RepositoryPoliciesConfiguration.repositoryCommonPolicyName)] public void SaveMessage(MailMessage message) { if (message.ID <= 0){ db.MailMessages.InsertOnSubmit(message); } foreach (MailAttachment attachment in message.MailAttachments){ if (attachment.ID <= 0){ db.MailAttachments.InsertOnSubmit(attachment); } } SubmitChanges(); } ... и т.д. Служба: public class SomeOurEmailService : OurServiceBase, IEmailService { private IEmailRepository repository; public SomeOurEmailService(IEmailRepository repository){ this.repository = repository; } public SomeOurEmailService(){ this.repository = new SqlEmailRepository(); } // Этот метод вызывает контроллер [ExceptionCallHandler(ServicePoliciesConfiguration.eMailServiceFoldersExceptionPolicy)] public void SaveMailFolder(MailFolder folder){ if(folder == null){ throw new ArgumentNullException("EmailService.SaveMailFolder: Mail Folder must be not null"); } EasyMailHelper helper = new EasyMailHelper(folder.MailAccount); helper.CreateOrSelectMailbox(folder.FullName); // А вот обращение к репозиторию repository.SaveFolder(folder); } и т.д. Контроллер: public class EmailController : OurControllerBase { IEmailService service = PolicyInjection.Create<EmailService>(); ... // Например, посылка сообщения [Authorize] public ActionResult SendMessage(){ // Юзаем сервис... service.SendMailMessage(message, account, true); } Такой код позволяет совсем абстрагировать контроллер, а соответственно максимально отделить логику от представления, ну конечно же позволяет удобно тестировать логику, которая расположена в сервисах, подменяя репозиторий на мок-объект. Хотя, для примера наверное правильно что juice не стал вводить еще один уровень абстракции - дабы не усложнять Спасибо за статею! Бессонная ночь не прошла зря +1! |
Сообщ.
#5
,
|
|
|
Цитата Miha_Dnepr @ Такой код позволяет совсем абстрагировать контроллер, а соответственно максимально отделить логику от представления, ну конечно же позволяет удобно тестировать логику, которая расположена в сервисах, подменяя репозиторий на мок-объект. Хотя, для примера наверное правильно что juice не стал вводить еще один уровень абстракции - дабы не усложнять Миша, ты не совсем прав. В моем примере контроллеры ASP.NET MVC приложения, вообще получают зависимость через конструктор. В твоем примере вы обходитесь без DependencyIjection просто используя PolicyInjection как обычный локатор (InversionOfControl), посути перенося зависимость в него. Локаторы я могу писать и сам. Используя DependencyInjection, ты исбавляешься и от связи с локатором который делает код еще более приспособленным для тестирования. Сила контейнеров именно в умении самостоятельно разрешать зависимости впрыскивая их в объекты, а не в умении искать их по реестру. Сравни: public class EmailController : OurControllerBase { IEmailService service = PolicyInjection.Create<GConnectEmailService>(); } Налицо завимость от локатора, я считаю, что контейнеры нужно использовать по назначению, раз сказал A надо говорить и B. Мой контроллер ничего незнает о PolicyInjection или Unity, Unity сама в фабрике разрешит все зависимости инстанцировав контроллер. public class MessageController : Controller { IRepository<Message> _repository; public MessageController(IRepository<Message> repository) { _repository = repository; } … } Теперь, что касается слоя служб. В этом я придерживаюсь классического подхода, они нужны там где нужны и не более того и чем тоньше они тем лучше. В частности удобно их использовать для обозначения контекста доменов. Вводить дополнительный чисто инфраструктурный уровень в ориентированной на работу с данными двухуровневке я не стану. Нельзя сказать, что Ваш подход это плохо или неправильно, кажый архитектор решит сам нужно ли это ему. Просто это не мой выбор. |
Сообщ.
#6
,
|
|
|
Да, но в моем случае, тоже есть возможность получить зависимость через конструктор:
public SqlEmailRepository(SqlRepositoryBase rep){ db = rep.db; } Где "db" и есть дата-контекст, котроый мы получем в конструкторе. Таким образом, репозиторию все равно каким образом создан контекст - либо через юнити (как в твоем случае), либо напрямую. Теперь по поводу PolicyInjection - в моем примере, нужно было его просто удалить, на самом деле. PolicyInjection в моем случае используется для работоспособности методов типа: [ExceptionCallHandler(ServicePoliciesConfiguration.eMailServiceFoldersExceptionPolicy)] public void SomeServiceMethod(){...} Это делается для того что бы похендлить исключения. Политика отлова/логгирования задается в конфигурации, а затем при помощи PolicyInjection внедряется. Таким образом, мне вообще можно забыть про try/catch - все типы исключений и политика реагирования на них задаются в конфиге. К репозиториям и данным, PolicyInjection в моем случае вообще не имеет отношения. Единственное что пока остается для меня загадкой, так это совместная работа PolicyInjection и юнити контейнера. |
Сообщ.
#7
,
|
|
|
Цитата Miha_Dnepr @ Таким образом, репозиторию все равно каким образом создан контекст - либо через юнити (как в твоем случае), либо напрямую. Именно, это и демонстрирует не только репозитории в моем обзоре, но и сами контроллеры. Вообще жду больше от тебя инфы с "фронта", где ASP.NET MVC обкатывается в боевом проекте. |
Сообщ.
#8
,
|
|
|
Цитата juice @ Ну да, я же и не спорил. Посто нужно было выкорчевать полиси нжекшн, что бы вопросов не возникало. Ну, и по-поводу слоя служб: действительно, они нужны там где нужны допустим, в ситуации с мэйлом, это не просто работа с данными (я имею ввиду, например кеширование ссобщеня в БД) но и работа с e-mail сервером. В моем случае логика кеширования и реальной посылки сообщения, создания/удаления папок на imap сервере и т.д находится в сервисе, который это все разруливает. Логика там довольно непростая, т.к. исользуются доп. почтовые библиотеки, которые упрощают работу с imap/smtp - и мне совсем не хочется выносить это все в контроллер. Я стараюсь просто как можно четчте обозначить границы. Представь, если придется все это дело потом, например, на сильверлайт переность? Контроллеры придется "прибить", а сервисы останутся со всей логикой, уже обкатанной и оттестированной. Вот собственно истинная причина разграничения и введения дополнительного слоя. Естесственно, там где идет работа только с данными, там доп. служба не всегда нужна - можно обращаться к репозиториям из контроллера, но для общности архитектуры, я все равно выношу работу с репозиториями в службу. Дело в том, что у меня есть четкие границы, благодаря которым, я могу предоставить общую архитектуру для всего проекта. Например, я принял решение логировать исключения на уровне служб. контроллер лиишь получает эксепшн от уровня службы (заметь, лог уже произведен причем, не важно куда - в БД, мэйл или WMI - все задается в конфиге на уровне политик), локализует его, делает юзер-френдли и посылает в виде Json клиенту. Опять же, при необходимости, можно убрать контроллер в данном виде из архитектуры приложения и поменять его на что-угодно, хоть на вин. форму, и т.д.Именно, это и демонстрирует не только репозитории в моем обзоре, но и сами контроллеры. По-поводу инфы с фронта: к началу апреля уже будет что показать, сейчас дорабатываем UI - это полный капец, самая сложная часть получается (ну, помимо движка отчетов, генерилки Pdf и некоторых задач с мэйлом). Зато, я немерянно тащусь от того что получается. Работает быстро и красиво. Опять-же, ни одного серверного контрола, WebParts и иже с ними мы не используем. З.Ы. Поздравляю всех с днем рождения релиза MVC! (он правда был несколько дней назад) http://www.microsoft.com/downloads/details.aspx?FamilyID=53289097-73ce-43bf-b6a6-35e00103cb4b&displaylang=en |
Сообщ.
#9
,
|
|
|
Miha_Dnepr, ты абсолютно прав мой друг. В таком случае я тоже использую слой служб, но исключительно для делегирования работы домену. Так удобно работать в распределенных приложениях с использованием Application Server или в приложениях ориентрированных на службы. Вобщем мораль простая, нужно использовать именно то, что лучше всего подходит для каждого конкретного проекта. В приложении которое имеет несколько предметных контекстов, можно использовать либо модели напрямую либо использовать слой служб для доступа к доменам. В рассматриваемом выше примере сущности есть не чем иным как моделью ориентированной на сохраняемость, а потому я решил не использовать слой служб.
|
Сообщ.
#10
,
|
|
|
Цитата Miha_Dnepr @ Посто нужно было выкорчевать полиси нжекшн, что бы вопросов не возникало. По этому поводу если контейнер разрешает зависимости, то это не мешает использовать PolicyInjectiton Цитата Miha_Dnepr @ Это делается для того что бы похендлить исключения. Политика отлова/логгирования задается в конфигурации, а затем при помощи PolicyInjection внедряется. Таким образом, мне вообще можно забыть про try/catch - все типы исключений и политика реагирования на них задаются в конфиге. К репозиториям и данным, PolicyInjection в моем случае вообще не имеет отношения. Единственное что пока остается для меня загадкой, так это совместная работа PolicyInjection и юнити контейнера. Это не загадка Unity предаставляет подмножесто AOP - Interception, которое позволяет точно также обрабатывать сквозной код. Поддерживается три типа прокси классов и правила матчинга, как программные так и конфигурационные. Вобщем если переписать твой код в моем стиле и инстанцировать контроллеры в собственной фабрике посредством PolicyInjection, думаю все будет работать на ура. Я думаю, что напишу следующую статью именно об этом, - о работе механизма Interception в Unity. |
Сообщ.
#11
,
|
|
|
Цитата juice @ - было бы очень неплохо Я думаю, что напишу следующую статью именно об этом, - о работе механизма Interception в Unity. |
Сообщ.
#12
,
|
|
|
Хорошая статья+
juice, выложи по возможности готовые сорсы к статье, хотелось бы запустить и более подробно изучить |