Photo by Art Lasovsky on Unsplash
The Coexistence of SOLID Principles, Clean Architecture and MVVM - Part 2
Deeper code examples
Brief
If anyone is interested in how this thread started out, please move over to here for a better introduction. There are links over there on SOLID Principles and Clean Architecture. For now, lets get into a little bit of code.
High Level
So the utility that I will be showing you is a smaller part of a whole project. The main part to showcase here is the ability to take this utility, and share the business and data layers among other applications in the project to create business value. Business value is important because it really determines the set of code or assets that are important to generate capital. The shared library that we have here will ultimately be general enough so that it's longevity won't be with a static set of UI libraries or of the sort.
The UI layer
Typically with any application we have the following construct. We have started View Model that the application can work with. Don't worry about the details here other than we are giving a general overview of how the View Model is being used.
public partial class frmMain : Form
{
MainViewModel _ViewModel = null;
public frmMain()
{
InitializeComponent();
_ViewModel = new MainViewModel(Settings.roDataGroupConnectionString, Settings.rwDataGroupConnectionString);
_ViewModel.PropertyChanged += _ViewModel_PropertyChanged;
...
This application really starts with the foreign data to translate it to the main model, and in this case the model that the application should adhere to. Here is some code that one of the foreign data utilities does exactly that. The model for the main UI in this case is called DataGroupContainer, but really can be a list of them.
private void tsmiMapGenerator_Click(object sender, EventArgs e)
{
using (DataGroupImportUtility.Import.Sources.MapGeneratorDB.frmMapGeneratorDB frm = new Import.Sources.MapGeneratorDB.frmMapGeneratorDB()) {
if (frm.ShowDialog() == DialogResult.OK) {
_ViewModel.MainContainer = new System.Collections.ObjectModel.ObservableCollection<DataGroupContainer>(frm.ContainerModel);
}
}
}
After foreign data adheres to the model contract, it immediately works with the View Model. This allows the application (WinForms in this case) to do the following among the form. We have a simple but healthy MVVM Concept here by binding the view model data back to some sort of UI list (ListView control).
private void _ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName) {
case "MainContainer":
lstPrimaries.DataSource = new BindingSource { DataSource = _ViewModel.MainContainer };
break;
}
}
Pretty easy so far here, right? Good. Let's move on inside the MVVM. Now with a WebApi, dependency injection is typically taken care of in the following form to define the type of instance that you are using, for example:
.AddScoped<ISomeSortOfService, SomeSortOfService>()
With MVVM and this utility, we don't have that, so we must do it ourselves. What I am about to show you is that the MVVM portion of this project can be interchangeable to a WebApi Controller. Here you can see that there is an instance of this "IDataGroupService". This happens to be a service instance that will eventually work with the data and business layer. This allows the MVVM model to be more or less of a passthrough object, but depending on what is being requested, it doesn't have to be.
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
readonly IDataGroupService _DataGroupService = null;
private ObservableCollection<DataGroupContainer> _MainContainer;
public ObservableCollection<DataGroupContainer> MainContainer {
get { return _MainContainer; }
set { _MainContainer = value; NotifyPropertyChanged("MainContainer"); }
}
public MainViewModel(string roDGConnection, string rwDGConnection)
{
_DataGroupService = new MainDataGroupService(null, new DbContext(roDGConnection, rwDGConnection));
}
...
The service looks very similar to a normal clean architecture service..
public partial class MainDataGroupService : IDataGroupService
{
readonly IEventProvider _EventProvider = null;
readonly IDbContext _DbContext = null;
public MainDataGroupService(IEventProvider e, IDbContext db)
{
this._EventProvider = e;
this._DbContext = db;
}
If you are familiar with the clean architecture concepts found out here on github, this folder structure should look familiar to you.
Conclusion
Layering of abstractions can sometimes be cumbersome, but when it comes to larger scale applications, these abstractions allow the codebase to become much more manageable from a flexibility and a development team standpoint. Here I am showcasing not just from the team aspect but from a technical longevity standpoint.
Refactoring can be mundane depending on how much is required, but as you can see that when taken the appropriate amount of time, there is good return on this time and the code is much cleaner.
Ahmed Tarek's article here has a really good quote...
" building knowledge about a system is a process. "
Final thoughts
Eventually I will be posting some examples on migration of existing applications that don't have any of the concepts or principles involved. We are looking at thousands of lines of code at a time and only a short amount of time to spend. Where are the principles surrounding that? Where to start? We're gonna dive in on some of those and take a break from this project a bit.
Please follow me on hashnode and twitter! I would love to hear some feedback regarding my articles and your own experiences.