Back in 2007 I was a happy Object Pascal programmer. I had heard about Visual Studio 2005, but never even thought taking a look at it, I was amazed with the all brand new Borland Delphi 2006. A few month later a good friend of mine came to me with a pre-release copy of a book written by his father and some other experts, it was about Visual Studio 2008. That day I realized that something big was coming. 10 years later Visual Studio has become, not just a great tool, but a whole and consolidated ecosystem.
Since Microsoft decided to acquire Xamarin in 2016 and Microsoft’s “sudden” love for Open Source and GNU/Linux, C# .Net has grown in popularity, backed by .Net Core, one of the greatest features of .Net, in my opinion. Based on PYPL (Popularity of Programming Language) which index is created by analyzing how often language tutorials are searched on Google, we can see that C#’s popularity rises.
After several years programing with Visual Studio I decided to dive into MonoDevelop looking for a C# cross-platform alternative with no success for me. I finally turned to Java. Months ago I met .Net Core. It definitely called my attention.
Objective
The goal of this post is to show you my approach to a SOLID and cross-platform simple application using .Net Core and Reactive Programming. This app retrieves a list of countries and renders it to the user.
Structure
This approach counts with 3 functional components or layers, composed by at least 6 modules where 2 of them are platform specific.
- Platform View (Presentation layer)
- UI Controller (Presentation layer)
- Di (Domain layer)
- Core (Domain layer)
- Data handler (Data layer)
- Platform data-access (Data layer)
The purpose is the separation of the business rules and the platform’s frameworks and features, allowing it to run and test regardless the environment since it’s pure .Net Core code. Business set of modules have no idea in what platform it’s being executed, also knows nothing about the views or the data access frameworks. Core module is in the highest level of abstraction, holds the use-cases logic and knows nothing about the rest of the application. Meanwhile, platform’s specific views (windows, activities, forms, web pages, console, etc) and data access are in the lowest level of abstraction having direct access to the system’s input-output. Views and data access are connected to the business through its respective interfaces. Business modules are bound by a dependency injection module called Di in business layer. Rest of modules are bound by the platform specific injector.
Platform Views
Visual components of the presentation layer are placed here, such as:
- Application Main class – Although this class may stand on its own module, for this example the main class and dependency injection are located in here.
- Injector – Module’s injector. Extends the base injector from dependency injection module.
- Animations – Component animations schema, sequences, etc., if any.
- Design schemas – Design xml, xaml, html, css, etc.
- View controllers – Classes that control the views and hold the presenters (Ui Controller module) instances.
Here is where the data is rendered and animations take place. Views must implement UI Controller module’s views interfaces and the least UI logic is allowed.
UI Controller
View presenters, presentation layer models, model mappers and view interfaces are placed here (MVP pattern or any other related pattern you prefer). Its job is to control the user interface and forward data requests to the Domain layer through its use cases.
- Views Interfaces – Presenters will use this interfaces to interact with view controllers in the Platform Views module.
- Model – Entities to be used in this layer
- Model Mapper – Will transform entities from Core module back.
- Presenters – Interact directly with application’s use cases located in the Domain layer.
Core
Domain layer’s models, use cases and the whole application’s business logic and data flow is placed here. In this case, the Core is the director and brain of the application. It manipulates the data and takes it to its respective destination.
- Use cases – Application’s reasons to exist. This classes interconnect the app components and layers allowing the data to flow through them.
- Model – Entities to be used in this layer.
- Repository Interface – Gate to data. This interface is used by use cases to access data from data layer.
It worth to mention that a Domain Layer is not necessarily made of one module, it depends on your app needs and your design. This layer also owns the repositories interfaces definition.
Data handler
By implementing Core’s repository interfaces, Domain layer is allowed to access Data layer and request information. Data handler module gathers the data from its interfaces and returns it back to Domain. Its job is to provide the data regardless the source and cache it if needed.
- Repository implementation – Core’s repository interface implementation.
- Model Mapper – Turns models from Data Handler to Core’s.
- Model – Entities to be used in this layer.
- DataSource/Store – DataSources are intended to retrieve data as DataStores are for setting data. The reason for this separation is that, depending on the source, data is retrieved and not necessarily set, and vice versa.
- Provider/Storer – Provides or stores the data through data interfaces.
- Network API Interface – Allows access to network APIs in data-access layer.
- Cache Interface – Allows access to cache frameworks in data-access layer.
- Disk storage API interface – Allows access to system’s storage APIs in data-access layer.
Platform data-access
This module has direct data access and it’s allowed to use any platform specific framework or system API. It implements Data handler’s interfaces.
Error handling
Since I’m using Rx.Net (Reactive programming) exceptions will flow from origin to the caller (mostly the UI) where the presenter will take care and generate a proper message for the user.
The code
Check the code here.
Conclusion
Writing good software is difficult. As you might already know, requirements always change and keeping your software clean after so many changes is a hard task. Keeping good practices, applying well known programming principles and a little bit of common sense, programming smells can be mitigated.