Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Модульная структура. Цветцих Денис D2D Just.NET

444 views

Published on

Сегодня многие фреймворки, такие как Prism или Autofac, позволяют разработчику организовать модульную структуру приложения. При этом часто бывает непонятно, для чего ещё нужны модули, кроме как для пресловутой "красоты архитектуры".
В рамках доклада я расскажу о том, какие существуют подходы к организации модульной структуры, в каких фреймворках они реализованы и для решения каких задач дает преимущество каждый подход.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Модульная структура. Цветцих Денис D2D Just.NET

  1. 1. Модульная структура – быть или не быть? Денис Цветцих АстроСофт http://www.astrosoft.ru/ D2D Just.NET 13 февраля 2016 dev2dev.ru
  2. 2. 2 Кто я? • 7 лет .NET • Разработка корпоративных приложений • Паттерны, архитектура • Power Tools
  3. 3. 3 Проекты для нескольких заказчиков • Биллинговая система • Внедрена 20-30 заказчикам с доработками • MES-система • Внедрена 150 заказчикам, для 10 есть доработки В обоих случаях доработки для заказчиков стали самостоятельными проектами
  4. 4. 4 Как все начиналось Как-то так Мы уже продали систему другому заказчику! Но нужно чуть-чуть доработать. И быстро! Или так Мы напишем проект для заказчика А потом сделаем из него продукт! И заканчивается одинаково Нужно поддерживать несколько версий проекта Кому знакомо?
  5. 5. 5 Что делать – вроде бы понятно • Разделить проект на части (модули) • Для разных заказчиков • повторно использовать общие модули • реализовать специфичные модули
  6. 6. 6 Как это сделать – непонятно • Как устроен проект, позволяющий повторно использовать свои модули? • Как делить функционал на модули? • Как собрать проект из модулей?
  7. 7. КАК КОНСТРУИРОВАТЬ ПРОЕКТ
  8. 8. 8 Проект для одного заказчика UI Logic DataAccess Нельзя повторно использовать части, так как они зависят от нижних уровней
  9. 9. 9 Inversion of Control • Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те, и другие должны зависеть от абстракции. • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
  10. 10. 10 IoC проект UI Logic DataAccess interfaces Можно повторно использовать части системы Logic interfaces DataAccess
  11. 11. ДЕКОМПОЗИЦИЯ
  12. 12. 12 Модуль Модуль – компонент программы, имеющий • назначение • контракт (реализуемые и используемые сервисы) Что такое модуль в вашей системе – решаете сами 
  13. 13. 13 Какие бывают модули Пакет (Nuget) одна или несколько сборок может зависеть от других модулей Плагин одна или несколько сборок не зависит от других модулей
  14. 14. 14 Модульное приложение Состоит из: • Оболочка (Shell) • Инфраструктура • Загрузка модулей • Модули
  15. 15. 15 Горизонтальная декомпозиция (пакеты) Shell (Интернет-магазин) UI модуль UI личного кабинета Logic модуль UI корзины Оформление заказа Изменение настроек Data Access модуль Сохранение настроек Сохранение заказа
  16. 16. 16 Когда использовать • Один проект, много заказчиков • Не нужна новая реализация всех сервисов • Кастомные модули конфигурируют и переопределяют часть базового функционала
  17. 17. 17 Вертикальная декомпозиция (плагины) Shell (Visual Studio) Модуль (плагин) UI services Logic services DataAccess services Resharper (плагин) UI формы Рефакторинги Сохранение настроек
  18. 18. 18 Когда использовать • Система, состоящая из подсистем • Подсистема – это плагин • Линейка взаимодополняющих продуктов • В оболочке один плагин – один продукт • Если плагинов >= 3, тогда profit  • Проектировать плагины нужно сразу 
  19. 19. ЗАГРУЗКА
  20. 20. 20 Composition Root Composition root – единственная точка в приложении, где определяется какие реализации соответствуют каким интерфейсам Располагается в Shell рядом со входом: • Global.asax • App.xaml.cs • Инфраструктура (Application, Bootstrapper)
  21. 21. 21 Composition Root без модулей protected override void OnStartup(StartupEventArgs e) { var builder = new ContainerBuilder(); // Data Access service builder.RegisterType<Repository>().As<IRepository>(); // Business Logic service builder.RegisterType<LogicService>().As<ILogicService>(); // User Interface service builder.RegisterType<NavigationService>().As<INavigationService>(); var container = builder.Build(); }
  22. 22. 22 Composition Root без модулей protected override void OnStartup(StartupEventArgs e) { var builder = new ContainerBuilder(); // Data Access service builder.RegisterType<Repository>().As<IRepository>(); // Business Logic service builder.RegisterType<LogicService>().As<ILogicService>(); // User Interface service builder.RegisterType<NavigationService>().As<INavigationService>(); var container = builder.Build(); }
  23. 23. 23 Composition Root без модулей protected override void OnStartup(StartupEventArgs e) { var builder = new ContainerBuilder(); // Data Access service builder.RegisterType<Repository>().As<IRepository>(); // Business Logic service builder.RegisterType<LogicService>().As<ILogicService>(); // User Interface service builder.RegisterType<NavigationService>().As<INavigationService>(); var container = builder.Build(); }
  24. 24. 24 Composition Root без модулей protected override void OnStartup(StartupEventArgs e) { var builder = new ContainerBuilder(); // Data Access service builder.RegisterType<Repository>().As<IRepository>(); // Business Logic service builder.RegisterType<LogicService>().As<ILogicService>(); // User Interface service builder.RegisterType<NavigationService>().As<INavigationService>(); var container = builder.Build(); }
  25. 25. 25 Особенности Чем плохо • Непонятно, какие сервисы из какого модуля • Нельзя инкапсулировать реализацию сервисов • Повторное использовние модуля – копипаст регистрации сервисов в контейнере Чем хорошо • Модули не зависят от IoC контейнера
  26. 26. 26 Composition Root с модулями • В каждом модуле – специальный класс, регистрирующий сервисы этого модуля • В Composition Root – регистрация модулей, а не сервисов
  27. 27. 27 Варианты загрузки модулей • Императивный • Декларативный
  28. 28. 28 Императивный • Composition Root отдает модулю IoC контейнер • Модуль регистрирует в контейнере свои сервисы
  29. 29. 29 Autofac: модуль public class AutofacModule : Module { protected override void Load(ContainerBuilder builder) { // Регистрируем сервисы builder.RegisterType<Service>().As<IService>(); } }
  30. 30. 30 Autofac: модуль public class AutofacModule : Module { protected override void Load(ContainerBuilder builder) { // Регистрируем сервисы builder.RegisterType<Service>().As<IService>(); } }
  31. 31. 31 Autofac: Composition Root private IContainer Container; protected override void OnStartup(StartupEventArgs e) { var builder = new ContainerBuilder(); builder.RegisterModule<AutofacModule>(); Container = builder.Build(); }
  32. 32. 32 Autofac: Composition Root private IContainer Container; protected override void OnStartup(StartupEventArgs e) { var builder = new ContainerBuilder(); builder.RegisterModule<AutofacModule>(); Container = builder.Build(); }
  33. 33. 33 Autofac: Composition Root private IContainer Container; protected override void OnStartup(StartupEventArgs e) { var builder = new ContainerBuilder(); builder.RegisterModule<AutofacModule>(); Container = builder.Build(); }
  34. 34. 34 Особенности императивного варианта Достоинства • Показывает сервисы модуля • Инкапсуляция реализации сервисов • Просто перейти на другой IoC контейнер • Быстрый Недостатки • Не подходит для плагинов (небезопасный) • Порядок загрузки модулей может быть важен
  35. 35. 35 Декларативный • В модуле определение сервисов при помощи метаданных • Атрибуты • Реализация интерфейсов • Composition Root анализирует метаданные при помощи Reflection
  36. 36. 36 MEF: модуль public interface IService { } [Export(typeof(IService))] public class Service : IService { }
  37. 37. 37 MEF: модуль public interface IService { } [Export(typeof(IService))] public class Service : IService { }
  38. 38. 38 MEF: Composition Root var configuration = new ContainerConfiguration() .WithAssembly(typeof (IService).Assembly); var container = configuration.CreateContainer();
  39. 39. 39 MEF: Composition Root var configuration = new ContainerConfiguration() .WithAssembly(typeof (IService).Assembly); var container = configuration.CreateContainer();
  40. 40. 40 Особенности декларативного варианта Достоинства • Позволяет писать плагины • Порядок загрузки модулей произвольный Недостатки • Нельзя инкапсулировать реализации сервисов • Нельзя явно увидеть список сервисов модуля • Сложнее перейти на другой IoC контейнер • Загрузка может быть долгой
  41. 41. 41 Пример: интернет - магазин Товары имеют вес и размеры (длина, ширина, высота) Корзина считает стоимость доставки товаров Базовый сценарий: расчет исходя из объема Кастомный сценарий: расчет исходя из веса Система реализована по принципу IoC  Нужно реализовать кастомный сценарий
  42. 42. 42 Common.Domain public class Product { public int Id { get; set; } public int Height { get; set; } public int Width { get; set; } public int Length { get; set; } public int Weight { get; set; } }
  43. 43. 43 Common.Domain public class Product { public int Id { get; set; } public int Height { get; set; } public int Width { get; set; } public int Length { get; set; } public int Weight { get; set; } }
  44. 44. 44 Common.Logic - CostCalculator public interface ICostCalculator { int GetDeliveryCost(Product product); } internal class VolumeCostCalculator : ICostCalculator { public int GetDeliveryCost(Product p) { return p.Height * p.Width * p.Length; } }
  45. 45. 45 Common.Logic - CostCalculator public interface ICostCalculator { int GetDeliveryCost(Product product); } internal class VolumeCostCalculator : ICostCalculator { public int GetDeliveryCost(Product p) { return p.Height * p.Width * p.Length; } }
  46. 46. 46 Common.Logic - CostCalculator public interface ICostCalculator { int GetDeliveryCost(Product product); } internal class VolumeCostCalculator : ICostCalculator { public int GetDeliveryCost(Product p) { return p.Height * p.Width * p.Length; } }
  47. 47. 47 Common.Logic - CostCalculator public interface ICostCalculator { int GetDeliveryCost(Product product); } internal class VolumeCostCalculator : ICostCalculator { public int GetDeliveryCost(Product p) { return p.Height * p.Width * p.Length; } }
  48. 48. 48 Common.Logic - ShopingCart internal class ShopingCart : IShopingCart { private ICostCalculator _calculator; private List<Product> _products = new List<Product>(); public ShopingCart(ICostCalculator calculator) { _calculator = costCalculator; } public int GetDeliveryCost() { return _products .Sum(p => _calculator.GetDeliveryCost(p)); } }
  49. 49. 49 Common.Logic - ShopingCart internal class ShopingCart : IShopingCart { private ICostCalculator _calculator; private List<Product> _products = new List<Product>(); public ShopingCart(ICostCalculator calculator) { _calculator = costCalculator; } public int GetDeliveryCost() { return _products .Sum(p => _calculator.GetDeliveryCost(p)); } }
  50. 50. 50 Common.Logic - ShopingCart internal class ShopingCart : IShopingCart { private ICostCalculator _calculator; private List<Product> _products = new List<Product>(); public ShopingCart(ICostCalculator calculator) { _calculator = costCalculator; } public int GetDeliveryCost() { return _products .Sum(p => _calculator.GetDeliveryCost(p)); } }
  51. 51. 51 CommonLogicModule public class CommonLogicModule : Module { protected override void Load(ContainerBuilder builder) { builder .RegisterType<VolumeCostCalculator>() .As<ICostCalculator>(); builder .RegisterType<ShopingCart>() .As<IShopingCart>(); } }
  52. 52. 52 CommonLogicModule public class CommonLogicModule : Module { protected override void Load(ContainerBuilder builder) { builder .RegisterType<VolumeCostCalculator>() .As<ICostCalculator>(); builder .RegisterType<ShopingCart>() .As<IShopingCart>(); } }
  53. 53. 53 Common.CompositionRoot var builder = new ContainerBuilder(); builder.RegisterModule<CommonLogicModule>(); var container = builder.Build(); var costCalculator = container.Resolve<ICostCalculator>(); // VolumeCostCalculator
  54. 54. 54 Common.CompositionRoot var builder = new ContainerBuilder(); builder.RegisterModule<CommonLogicModule>(); var container = builder.Build(); var costCalculator = container.Resolve<ICostCalculator>(); // VolumeCostCalculator
  55. 55. 55 Custom.Logic - WeightCostCalculator internal class WeightCostCalculator : ICostCalculator { public int GetDeliveryCost(Product p) { return p.Weight; } }
  56. 56. 56 Custom.Logic - WeightCostCalculator internal class WeightCostCalculator : ICostCalculator { public int GetDeliveryCost(Product p) { return p.Weight; } }
  57. 57. 57 Custom.Logic - WeightCostCalculator internal class WeightCostCalculator : ICostCalculator { public int GetDeliveryCost(Product p) { return p.Weight; } }
  58. 58. 58 CustomLogicModule public class CustomLogicModule : Module { protected override void Load(ContainerBuilder builder) { builder .RegisterType<WeightCostCalculator>() .As<ICostCalculator>(); } }
  59. 59. 59 Custom.CompositionRoot var builder = new ContainerBuilder(); // Порядок регистрации модулей важен builder.RegisterModule<CommonLogicModule>(); builder.RegisterModule<CustomLogicModule>(); var container = builder.Build(); var costCalculator = container.Resolve<ICostCalculator>(); // WeightCostCalculator
  60. 60. 60 Custom.CompositionRoot var builder = new ContainerBuilder(); // Порядок регистрации модулей важен builder.RegisterModule<CommonLogicModule>(); builder.RegisterModule<CustomLogicModule>(); var container = builder.Build(); var costCalculator = container.Resolve<ICostCalculator>(); // WeightCostCalculator
  61. 61. 61 Для поддержки нескольких заказчиков • Система должна соответствовать IoC • Маленький и средний проект • Модуль – слой (горизонтальная структура) • Загрузка императивная • Большой проект • Модуль – плагин (вертикальная структура) • Загрузка декларативная • Выгода, если плагинов не менее 3
  62. 62. 62 Что делать дальше • Сделайте проект по IoC, пригодится  • Посмотреть модули Prism, Autofac, MEF • Нужны ли вам модули? Какие? • Реализуйте модули!
  63. 63. 63 Материалы по модулям Пример к докладу на гитхабе https://github.com/denis-tsv/Modules Prism https://msdn.microsoft.com/en-us/library/gg406140.aspx http://habrahabr.ru/post/176863/ Autofac http://docs.autofac.org/en/latest/configuration/modules.html MEF https://msdn.microsoft.com/ru-ru/library/dd460648(v=vs.110).aspx Dependency Injection in .NET http://www.ozon.ru/context/detail/id/35161137/ http://smarly.net/dependency-injection-in-net/
  64. 64. 64 Спасибо за внимание Денис Цветцих den.tsvettsih@yandex.ru
  65. 65. 65 Если система не соответствует IoC public class Service { public static Service Instance { get; } }
  66. 66. 66 Решение 1: переделать на IoC • Классы используют интерфейсы • В модуле – класс, регистрирующий сервисы в IoC контейнере • В Composition Root – работа с модулями, а не сервисами
  67. 67. 67 Решение 2: ServiceLocator • Добавить интерфейс для сервиса • В CompositionRoot базовой реализации кладем в ServiceLocator базовую реализацию • В кастомном CompositionRoot кладем в ServiceLocator кастомную реализацию
  68. 68. 68 Решение 2: ServiceLocator Вместо статического свойства Service.Instance Используем реализацию из сервис-локатора ServiceLocator.Current.GetInstance<IService>();
  69. 69. 69 Спасибо за внимание Денис Цветцих den.tsvettsih@yandex.ru

×