Модульная структура

Preview:

Citation preview

Модульная структура – быть или не быть?

Денис ЦветцихАстроСофт

http://www.astrosoft.ru/

D2D Just.NET13 февраля 2016dev2dev.ru

2

Кто я?• 7 лет .NET• Разработка корпоративных

приложений• Паттерны, архитектура• Power Tools

3

Проекты для нескольких заказчиков

• Биллинговая система• Внедрена 20-30 заказчикам с

доработками• MES-система• Внедрена 150 заказчикам, для 10 есть

доработки

В обоих случаях доработки для заказчиков стали самостоятельными проектами

4

Как все начиналосьКак-то так

Мы уже продали систему другому заказчику!Но нужно чуть-чуть доработать. И быстро!

Или такМы напишем проект для заказчикаА потом сделаем из него продукт!

И заканчивается одинаковоНужно поддерживать несколько версий проекта

Кому знакомо?

5

Что делать – вроде бы понятно

• Разделить проект на части (модули)

• Для разных заказчиков • повторно использовать общие

модули• реализовать специфичные модули

6

Как это сделать – непонятно

• Как устроен проект, позволяющий повторно использовать свои модули?

• Как делить функционал на модули?

• Как собрать проект из модулей?

КАК КОНСТРУИРОВАТЬ ПРОЕКТ

8

Проект для одного заказчика

UI

Logic

DataAccess

Нельзя повторно использовать части, так как они зависят от нижних уровней

9

Inversion of Control• Модули верхнего уровня не

должны зависеть от модулей нижнего уровня. И те, и другие должны зависеть от абстракции.

• Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

10

IoC проектUI

Logic

DataAccess interfaces

Можно повторно использовать части системы

Logic interfaces

DataAccess

ДЕКОМПОЗИЦИЯ

12

МодульМодуль – компонент программы,

имеющий•назначение•контракт (реализуемые и

используемые сервисы)

Что такое модуль в вашей системе – решаете сами

13

Какие бывают модулиПакет (Nuget)

одна или несколько сборокможет зависеть от других модулей

Плагинодна или несколько сборокне зависит от других модулей

14

Модульное приложениеСостоит из:• Оболочка (Shell)• Инфраструктура• Загрузка модулей

• Модули

15

Горизонтальная декомпозиция (пакеты)Shell (Интернет-магазин)

UI модульUI личного кабинета

Logic модуль

UI корзины

Оформление заказа

Изменение настроек

Data Access модуль

Сохранение настроек

Сохранение заказа

16

Когда использовать• Один проект, много заказчиков • Не нужна новая реализация всех

сервисов• Кастомные модули конфигурируют и

переопределяют часть базового функционала

17

Вертикальная декомпозиция (плагины)

Shell (Visual Studio)

Модуль (плагин)

UI services

Logic services

DataAccess services

Resharper (плагин)

UI формы

Рефакторинги

Сохранение настроек

18

Когда использовать• Система, состоящая из подсистем• Подсистема – это плагин

• Линейка взаимодополняющих продуктов• В оболочке один плагин – один

продукт

• Если плагинов >= 3, тогда profit • Проектировать плагины нужно

сразу

ЗАГРУЗКА

20

Composition RootComposition root – единственная

точка в приложении, где определяется какие реализации соответствуют каким интерфейсам

Располагается в Shell рядом со входом:• Global.asax• App.xaml.cs• Инфраструктура (Application,

Bootstrapper)

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

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

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

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

ОсобенностиЧем плохо• Непонятно, какие сервисы из какого

модуля• Нельзя инкапсулировать реализацию

сервисов• Повторное использовние модуля –

копипаст регистрации сервисов в контейнере

Чем хорошо• Модули не зависят от IoC контейнера

26

Composition Root с модулями

• В каждом модуле – специальный класс, регистрирующий сервисы этого модуля

• В Composition Root – регистрация модулей, а не сервисов

27

Варианты загрузки модулей

• Императивный• Декларативный

28

Императивный• Composition Root отдает модулю IoC

контейнер• Модуль регистрирует в контейнере

свои сервисы

29

Autofac: модульpublic class AutofacModule : Module{ protected override void Load(ContainerBuilder builder) { // Регистрируем сервисы builder.RegisterType<Service>().As<IService>(); }}

30

Autofac: модульpublic class AutofacModule : Module{ protected override void Load(ContainerBuilder builder) { // Регистрируем сервисы builder.RegisterType<Service>().As<IService>(); }}

31

Autofac: Composition Root

private IContainer Container;

protected override void OnStartup(StartupEventArgs e){ var builder = new ContainerBuilder();

builder.RegisterModule<AutofacModule>();

Container = builder.Build();

}

32

Autofac: Composition Root

private IContainer Container;

protected override void OnStartup(StartupEventArgs e){ var builder = new ContainerBuilder();

builder.RegisterModule<AutofacModule>();

Container = builder.Build();

}

33

Autofac: Composition Root

private IContainer Container;

protected override void OnStartup(StartupEventArgs e){ var builder = new ContainerBuilder();

builder.RegisterModule<AutofacModule>();

Container = builder.Build();

}

34

Особенности императивного варианта

Достоинства• Показывает сервисы модуля• Инкапсуляция реализации сервисов• Просто перейти на другой IoC контейнер• Быстрый

Недостатки• Не подходит для плагинов (небезопасный)• Порядок загрузки модулей может быть

важен

35

Декларативный• В модуле определение сервисов

при помощи метаданных• Атрибуты• Реализация интерфейсов

• Composition Root анализирует метаданные при помощи Reflection

36

MEF: модульpublic interface IService{}

[Export(typeof(IService))]public class Service : IService{

}

37

MEF: модульpublic interface IService{}

[Export(typeof(IService))]public class Service : IService{

}

38

MEF: Composition Rootvar configuration = new ContainerConfiguration() .WithAssembly(typeof (IService).Assembly);

var container = configuration.CreateContainer();

39

MEF: Composition Rootvar configuration = new ContainerConfiguration() .WithAssembly(typeof (IService).Assembly);

var container = configuration.CreateContainer();

40

Особенности декларативного варианта

Достоинства• Позволяет писать плагины• Порядок загрузки модулей произвольный

Недостатки• Нельзя инкапсулировать реализации

сервисов • Нельзя явно увидеть список сервисов модуля• Сложнее перейти на другой IoC контейнер • Загрузка может быть долгой

41

Пример: интернет - магазин

Товары имеют вес и размеры (длина, ширина, высота)

Корзина считает стоимость доставки товаров

Базовый сценарий: расчет исходя из объемаКастомный сценарий: расчет исходя из веса

Система реализована по принципу IoC Нужно реализовать кастомный сценарий

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

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

Common.Logic - CostCalculatorpublic interface ICostCalculator{ int GetDeliveryCost(Product product);}

internal class VolumeCostCalculator : ICostCalculator{ public int GetDeliveryCost(Product p) { return p.Height * p.Width * p.Length; }}

45

Common.Logic - CostCalculatorpublic interface ICostCalculator{ int GetDeliveryCost(Product product);}

internal class VolumeCostCalculator : ICostCalculator{ public int GetDeliveryCost(Product p) { return p.Height * p.Width * p.Length; }}

46

Common.Logic - CostCalculatorpublic interface ICostCalculator{ int GetDeliveryCost(Product product);}

internal class VolumeCostCalculator : ICostCalculator{ public int GetDeliveryCost(Product p) { return p.Height * p.Width * p.Length; }}

47

Common.Logic - CostCalculatorpublic interface ICostCalculator{ int GetDeliveryCost(Product product);}

internal class VolumeCostCalculator : ICostCalculator{ public int GetDeliveryCost(Product p) { return p.Height * p.Width * p.Length; }}

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

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

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

CommonLogicModulepublic class CommonLogicModule : Module{ protected override void Load(ContainerBuilder

builder) { builder .RegisterType<VolumeCostCalculator>() .As<ICostCalculator>();

builder .RegisterType<ShopingCart>() .As<IShopingCart>(); }}

52

CommonLogicModulepublic class CommonLogicModule : Module{ protected override void Load(ContainerBuilder

builder) { builder .RegisterType<VolumeCostCalculator>() .As<ICostCalculator>();

builder .RegisterType<ShopingCart>() .As<IShopingCart>(); }}

53

Common.CompositionRoot

var builder = new ContainerBuilder();

builder.RegisterModule<CommonLogicModule>();

var container = builder.Build();

var costCalculator = container.Resolve<ICostCalculator>();

// VolumeCostCalculator

54

Common.CompositionRoot

var builder = new ContainerBuilder();

builder.RegisterModule<CommonLogicModule>();

var container = builder.Build();

var costCalculator = container.Resolve<ICostCalculator>();

// VolumeCostCalculator

55

Custom.Logic - WeightCostCalculator

internal class WeightCostCalculator : ICostCalculator

{ public int GetDeliveryCost(Product p) { return p.Weight; }}

56

Custom.Logic - WeightCostCalculator

internal class WeightCostCalculator : ICostCalculator

{ public int GetDeliveryCost(Product p) { return p.Weight; }}

57

Custom.Logic - WeightCostCalculator

internal class WeightCostCalculator : ICostCalculator

{ public int GetDeliveryCost(Product p) { return p.Weight; }}

58

CustomLogicModulepublic class CustomLogicModule : Module{ protected override void Load(ContainerBuilder

builder) { builder .RegisterType<WeightCostCalculator>() .As<ICostCalculator>(); }}

59

Custom.CompositionRootvar builder = new ContainerBuilder();

// Порядок регистрации модулей важенbuilder.RegisterModule<CommonLogicModule>();builder.RegisterModule<CustomLogicModule>();

var container = builder.Build();

var costCalculator = container.Resolve<ICostCalculator>();

// WeightCostCalculator

60

Custom.CompositionRootvar builder = new ContainerBuilder();

// Порядок регистрации модулей важенbuilder.RegisterModule<CommonLogicModule>();builder.RegisterModule<CustomLogicModule>();

var container = builder.Build();

var costCalculator = container.Resolve<ICostCalculator>();

// WeightCostCalculator

61

Для поддержки нескольких заказчиков

• Система должна соответствовать IoC

• Маленький и средний проект• Модуль – слой (горизонтальная

структура)• Загрузка императивная

• Большой проект• Модуль – плагин (вертикальная

структура)• Загрузка декларативная• Выгода, если плагинов не менее 3

62

Что делать дальше• Сделайте проект по IoC,

пригодится • Посмотреть модули Prism, Autofac,

MEF• Нужны ли вам модули? Какие?• Реализуйте модули!

64

Спасибо за вниманиеДенис Цветцих

АстроСофтden.tsvettsih@yandex.ru

65

Если система не соответствует IoC

public class Service{ public static Service Instance { get; }}

66

Решение 1: переделать на IoC

• Классы используют интерфейсы• В модуле – класс, регистрирующий

сервисы в IoC контейнере• В Composition Root – работа с

модулями, а не сервисами

67

Решение 2: ServiceLocator

• Добавить интерфейс для сервиса• В CompositionRoot базовой

реализации кладем в ServiceLocator базовую реализацию

• В кастомном CompositionRoot кладем в ServiceLocator кастомную реализацию

68

Решение 2: ServiceLocator

Вместо статического свойстваService.Instance

Используем реализацию из сервис-локатора

ServiceLocator.Current.GetInstance<IService>();

69

Спасибо за вниманиеДенис Цветцих

АстроСофтden.tsvettsih@yandex.ru

Recommended