Сегодня многие фреймворки, такие как Prism или Autofac, позволяют разработчику организовать модульную структуру приложения. При этом часто бывает непонятно, для чего ещё нужны модули, кроме как для пресловутой "красоты архитектуры".
В рамках доклада я расскажу о том, какие существуют подходы к организации модульной структуры, в каких фреймворках они реализованы и для решения каких задач дает преимущество каждый подход.
IT-инфраструктура - Александр Соболь Dev2Dev v1.5 23.11.2014
Модульная структура. Цветцих Денис D2D Just.NET
1. Модульная структура –
быть или не быть?
Денис Цветцих
АстроСофт
http://www.astrosoft.ru/
D2D Just.NET
13 февраля 2016
dev2dev.ru
2. 2
Кто я?
• 7 лет .NET
• Разработка корпоративных приложений
• Паттерны, архитектура
• Power Tools
3. 3
Проекты для нескольких заказчиков
• Биллинговая система
• Внедрена 20-30 заказчикам с доработками
• MES-система
• Внедрена 150 заказчикам, для 10 есть доработки
В обоих случаях доработки для заказчиков
стали самостоятельными проектами
4. 4
Как все начиналось
Как-то так
Мы уже продали систему другому заказчику!
Но нужно чуть-чуть доработать. И быстро!
Или так
Мы напишем проект для заказчика
А потом сделаем из него продукт!
И заканчивается одинаково
Нужно поддерживать несколько версий проекта
Кому знакомо?
5. 5
Что делать – вроде бы понятно
• Разделить проект на части (модули)
• Для разных заказчиков
• повторно использовать общие модули
• реализовать специфичные модули
6. 6
Как это сделать – непонятно
• Как устроен проект, позволяющий
повторно использовать свои модули?
• Как делить функционал на модули?
• Как собрать проект из модулей?
8. 8
Проект для одного заказчика
UI
Logic
DataAccess
Нельзя повторно использовать части, так как
они зависят от нижних уровней
9. 9
Inversion of Control
• Модули верхнего уровня не должны
зависеть от модулей нижнего уровня. И
те, и другие должны зависеть от
абстракции.
• Абстракции не должны зависеть от
деталей. Детали должны зависеть от
абстракций.
12. 12
Модуль
Модуль – компонент программы, имеющий
• назначение
• контракт (реализуемые и используемые
сервисы)
Что такое модуль в вашей системе –
решаете сами
13. 13
Какие бывают модули
Пакет (Nuget)
одна или несколько сборок
может зависеть от других модулей
Плагин
одна или несколько сборок
не зависит от других модулей
15. 15
Горизонтальная декомпозиция (пакеты)
Shell (Интернет-магазин)
UI модуль
UI личного
кабинета
Logic модуль
UI корзины
Оформление
заказа
Изменение
настроек
Data Access модуль
Сохранение
настроек
Сохранение
заказа
16. 16
Когда использовать
• Один проект, много заказчиков
• Не нужна новая реализация всех сервисов
• Кастомные модули конфигурируют и
переопределяют часть базового функционала
18. 18
Когда использовать
• Система, состоящая из подсистем
• Подсистема – это плагин
• Линейка взаимодополняющих продуктов
• В оболочке один плагин – один продукт
• Если плагинов >= 3, тогда profit
• Проектировать плагины нужно сразу
20. 20
Composition Root
Composition root – единственная точка в
приложении, где определяется какие
реализации соответствуют каким
интерфейсам
Располагается в Shell рядом со входом:
• Global.asax
• App.xaml.cs
• Инфраструктура (Application, Bootstrapper)
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
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
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
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
Особенности
Чем плохо
• Непонятно, какие сервисы из какого модуля
• Нельзя инкапсулировать реализацию
сервисов
• Повторное использовние модуля – копипаст
регистрации сервисов в контейнере
Чем хорошо
• Модули не зависят от IoC контейнера
26. 26
Composition Root с модулями
• В каждом модуле – специальный класс,
регистрирующий сервисы этого модуля
• В Composition Root – регистрация
модулей, а не сервисов
34. 34
Особенности императивного варианта
Достоинства
• Показывает сервисы модуля
• Инкапсуляция реализации сервисов
• Просто перейти на другой IoC контейнер
• Быстрый
Недостатки
• Не подходит для плагинов (небезопасный)
• Порядок загрузки модулей может быть важен
35. 35
Декларативный
• В модуле определение сервисов при
помощи метаданных
• Атрибуты
• Реализация интерфейсов
• Composition Root анализирует
метаданные при помощи Reflection
38. 38
MEF: Composition Root
var configuration = new ContainerConfiguration()
.WithAssembly(typeof (IService).Assembly);
var container = configuration.CreateContainer();
39. 39
MEF: Composition Root
var configuration = new ContainerConfiguration()
.WithAssembly(typeof (IService).Assembly);
var container = configuration.CreateContainer();
40. 40
Особенности декларативного варианта
Достоинства
• Позволяет писать плагины
• Порядок загрузки модулей произвольный
Недостатки
• Нельзя инкапсулировать реализации сервисов
• Нельзя явно увидеть список сервисов модуля
• Сложнее перейти на другой IoC контейнер
• Загрузка может быть долгой
41. 41
Пример: интернет - магазин
Товары имеют вес и размеры (длина,
ширина, высота)
Корзина считает стоимость доставки товаров
Базовый сценарий: расчет исходя из объема
Кастомный сценарий: расчет исходя из веса
Система реализована по принципу IoC
Нужно реализовать кастомный сценарий
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
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
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
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
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
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
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
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
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));
}
}
53. 53
Common.CompositionRoot
var builder = new ContainerBuilder();
builder.RegisterModule<CommonLogicModule>();
var container = builder.Build();
var costCalculator = container.Resolve<ICostCalculator>();
// VolumeCostCalculator
54. 54
Common.CompositionRoot
var builder = new ContainerBuilder();
builder.RegisterModule<CommonLogicModule>();
var container = builder.Build();
var costCalculator = container.Resolve<ICostCalculator>();
// VolumeCostCalculator
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
Custom.CompositionRoot
var builder = new ContainerBuilder();
// Порядок регистрации модулей важен
builder.RegisterModule<CommonLogicModule>();
builder.RegisterModule<CustomLogicModule>();
var container = builder.Build();
var costCalculator = container.Resolve<ICostCalculator>();
// WeightCostCalculator
61. 61
Для поддержки нескольких заказчиков
• Система должна соответствовать IoC
• Маленький и средний проект
• Модуль – слой (горизонтальная структура)
• Загрузка императивная
• Большой проект
• Модуль – плагин (вертикальная структура)
• Загрузка декларативная
• Выгода, если плагинов не менее 3
62. 62
Что делать дальше
• Сделайте проект по IoC, пригодится
• Посмотреть модули Prism, Autofac, MEF
• Нужны ли вам модули? Какие?
• Реализуйте модули!
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/
65. 65
Если система не соответствует IoC
public class Service
{
public static Service Instance { get; }
}
66. 66
Решение 1: переделать на IoC
• Классы используют интерфейсы
• В модуле – класс, регистрирующий
сервисы в IoC контейнере
• В Composition Root – работа с модулями, а
не сервисами
67. 67
Решение 2: ServiceLocator
• Добавить интерфейс для сервиса
• В CompositionRoot базовой реализации
кладем в ServiceLocator базовую
реализацию
• В кастомном CompositionRoot кладем в
ServiceLocator кастомную реализацию
68. 68
Решение 2: ServiceLocator
Вместо статического свойства
Service.Instance
Используем реализацию из сервис-локатора
ServiceLocator.Current.GetInstance<IService>();
Не обязательно работать в MS и писать студию или работать в JetBrains и писать Resharper, тоже расширяемый плагинами.
3 – свой опыт, Гласс факты и заблуждения, Басс архитектура ПО на практике