@Sterk
Программист

Как организовать архитектуру при использование C#, WPF, MVVM?

Здравствуйте. Простите за войну и мир. Не знаю как коротко об архитектуре спрашивать.
У меня часто возникают проблемы с непониманием как можно\нужно выстраивать взаимодействие между различными частями приложения. Допустим у нас есть:
1. BackgroundWorker - какой либо фоновый сервис(прибор, парсящий бот и тд).
2. StorageService - слой хранения данных(sql, nosql, serialization).
3. Ui - wpf, mvvm.
Теперь опишу как я обычно это делаю и о чем я думаю при этом.
1. BackgroundWorker - создаю отдельную classlibrary, куда помещаю все что связано с этим воркером. Создаю независимые модели данных(POCO, никаких gui'шных IPropertyChanged и тд), добавляю события с использованием этих моделей. То есть api построено по принципу вызова метода для управления, события для информирования в обратную сторону. Строю его максимально независимым, что бы в случае чего, его можно было использовать в различных внешних условиях(console, gui, service).
2. StorageService - так же как и в предыдущем случае строю максимально независимое решение. Все модели описывают строго слой данных, никаких левых свойств\методов, опять таки POCO. Обычно использую Unite of Work/Repository для крупного решения либо один класс(DbService) для работы со всеми сущностями для маленького решения. Не люблю ActiveRecord.
3. Ui - вот тут у меня начинаются проблемы. Так как для отображения всего этого приходится городить кучу, как мне кажется, костылей. Для примера опишу последнюю разработку для дома. Писал бота для взятия мед. талончика. Во всех трех слоях появились свои
class Job //Общий класс описывающий задание
{ 
  Guid PersonId {get;set;}
  bool Enabled {get;set;}
  List<Period> Periods{get;}
//Другие поля
} 
class Period //Информация когда брать талончик
{
//Другие поля
}
class Person //Человек для кого берем
{
Guid Id{get;set;}
//Другие поля
}

То есть у нас 3*3 структур данных, похожих на 90% в своей группе. Для переноса из одного слоя в другой мы каждый раз выполняем конвертация. Я это делаю во ViewModels. Это ладно мелочи, хотя и многословные довольно. Что увеличивает объем и ухудшает читаемость. Помогает вынос логики конвертации в сервис либо использование AutoMapper.
Последнее время я использую TabControl где каждая панель это отдельная ViewModel и View. У нас есть JobsViewModel и PeopleViewModel. Смотрим(мысленно) на JobsViewModel в ней хранятся задачи(ObservableCollection) которые выводятся в ListView, тут же есть ссылка на бота(BackgroundWorker). В каждой задачи хранятся периоды (ObservableCollection) и id человека.
А дальше возникает куча дилемм кто что и как должен делать.
1ая проблема - дублирование, отслеживание и связанность.
При добавление(удалении) в задач параллельно мы дублируем это для бота. Если я не хочу делать кнопку "Сохранить", а просто на лету изменять все данные в БД. То к дублированию вызовов добавляется еще и БД. Плюс подписка на propertychanged событие каждой JobViewModel, а она в свою очередь мне как то должна сигнализировать о изменениях в каждой PeriodViewModel. В общем нужно будет очень много связей и костылей. Либо использовать IEventAggregator и на каждый чих делать вызов. Это по крайней мере уменьшит связанность. Но следить за всей это кашей(множество точек изменения) все равно придется. Для более сложных структур это станет гораздо сложнее. Код станет более "магическим".
2ая проблема - добавление новых зависимостей на каждый чих.
Я использую красивый ToggleSwitch для переключения статуса исполнения задачи(свойство Enabled). Что бы передать это боту(BackgroundWorker), мне нужно либо каждой JobViewModel иметь ссылку на него(что увеличивает связанность), либо JobsViewModel должна подписываться на propertychanged событие для всех JobViewModel при добавлении и отписываться при удалении, это мне показалось более правильным решением.
3тья проблема - прокидывание данных из одной ViewModel в другую.
В PeopleViewModel формируется ObservableCollection. Это коллекция нужна в JobViewModel для вывода в комбобокс и выбора PersonId. Тут я вижу 4 пути:
а. Прокидываю целиком PeopleViewModel в каждую JobViewModel. (Связанность)
б. Создаю враппер по типу
PeopleService {public ObservableCollection<Person>{get;set;}}
и делаю инъекцию его в PeopleViewModel и каждую JobViewModel. Либо прокинуть ObservableCollection из PeopleViewModel только в JobsViewModel а от туда подсовывать каждой JobViewModel (Связанность но меньшая)
в. Через тот же IEventAggregator реализовываю дублирования добавления\удаления во все ObservableCollection во всех ViewModels. (Для десяти задач десять ObservableCollection + 1 в PeopleViewModel)
г. При переключение вкладки JobsViewModel обновлять у всех JobViewModel ObservableCollection из БД. Полная развязка от PeopleViewModel. (минусы как в пункте в, либо зависимость на коллекцию ObservableCollection в родительской JobsViewModel. Должно обязательно быть сохранение на лету в PeopleViewModel, ну и третий минус что если все расположено в одном окне(без табов) то не понятно по какому событию обновлять)

Для малого проекта с этим всем можно уживаться. Но я не понимаю как жить с таким если будут десятки и сотни моделей\вью моделей. Все эти связи\эвенты и тд. Как решают подобные задачи senior software engineer\architect?
  • Вопрос задан
  • 2176 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы