Skip to content

Architecture

Poh Zhi-Ee edited this page Nov 20, 2018 · 15 revisions

This document serve to explain some stuff

Table of Contents

  1. Creating A Service
  2. IoC Container
  3. ViewModelLocator
  4. Logging
  5. Navigation Service
  6. Login Service
  7. Data Service

Creating A Service

This aims to illustrate the thought process when creating a service.

Single responsibility principle

  1. What is this service supposed to do
  2. From a user perspective, what are the functions that it should have
  3. When attempting to call stuff from another service (e.g. dialog), think whether it should be this service calling it, or the user of this service calling it.

Based on 1 and 2, it should be possible to generate an interface for this Service, and work from there.

Asynchronous and multithreading

  1. Should the caller be able to determine if the method runs in the background? Or should it always be in the background?

  2. Note that asynchronous methods cannot take parameters by reference

Testing

  • How do I make sure each method does what it says it is supposed to do?

    1. Think from the user perspective
    2. In the test cases put what is the expected input and what is the expected output
  • What happens if my service deals with UI?

    1. Try your best to separate things that deal with UI and things that does not
    2. Test the stuff that does not deal with UI

IoC Container

The IoC container used in this project is AutoFac, selected due to ease of use, as well as good documentation and multiple examples of people using it on their Xamarin project.

To use an IoC container:

  1. Register types
  2. Specify lifetime
  3. Resolve

ViewModelLocator

The ViewModelLocator is to allow design time evaluation of the ViewModel while still allowing constructor injection of the ViewModel.

Sample usage

XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="NottCS.Views.MenuPage"
             BindingContext="{Binding Source={StaticResource Locator}, Path=Menu}"
             Title="Menu">

ViewModelLocator class:

  1. Register the type with the container and specify the lifetime
builder.RegisterType<MenuViewModel>().InstancePerDependency();
  1. Expose the property to be binded
public MenuViewModel Menu => _container.Resolve<MenuViewModel>();

The constructor injection is done through the use of the IoC Container.


Logging

The logging is done through the use of NLog.

Main advantages:

  1. Can specify logging levels
  2. Easily include timestamp of log
  3. Easily format log string to include more information like callsite
  4. Easily change logging medium, i.e. Console, Text file, etc.

NLog setup found under App.xaml.cs under SetupNLog() function. (Updated as of 3 Nov 2018)

Sample usage:

private readonly ILogger<MainViewModel> _logger;
public MainViewModel(ILogger<MainViewModel> logger)
{
    _logger = logger;
    logger.LogInformation("MainViewModel created");
}

Navigation Service

Since this application has been updated to use a drawer style, there are 2 ways for navigation to happen. Page terminologies illustrated here

1. Replace the Detail page. This is needed when an item on the Drawer/Menu Page is selected.

Usage:

await _navigationService.SetDetailPageAsync(viewModelType, navigationParam);

2. Navigate from the current Detail page. This is needed when the Detail page itself needs to navigate further.

Usage:

await _navigationService.NavigateToAsync<ViewModelType>(navigationParam);

Login Service

The login service uses the MSAL library provided by Microsoft, which is called Microsoft.Identity.Client in NuGet. It is already version 2.3.1 at this time of writing (4 Nov 2018) yet it is still under prerelease.

There is a LoginService wrapper around the MSAL library, due to the complex logic needed to complete the login as well as providing custom logic, i.e. checking domain to be nottingham.edu.my.

Note that an exception will be thrown whenever there is a failure to login, it is up to the UI to handle such exceptions.

On successful login, a token will be returned, which should be used in the RestService for communication with the backend.

Data Service

Usage:

  1. Data handling
  2. Synchronisation

image Basic flow:

Data handling:

BLL: Business Logic Layer, i.e. ClubService, EventService, MemberService, etc.

  • Get:

    1. ViewModel calls stuff like GetClubList() from BLL
    2. BLL calls from local db since it is a get
  • Update:

    1. ViewModel calls stuff like UpdateClub(Club club) from BLL
    2. BLL checks for internet connectivity (throws if no internet, set application to offline mode)
    3. BLL updates both local db and backend since it is an update
    4. ViewModel calls GetClubList() again (note that this only pulls from local, for full remote update need to call sync service)

    Note that if Offline Mode is enabled the ViewModel should not be able to call Add/Update/Delete

  • Add: Same as update

  • Delete: Same as update

So basically there are 3 sets of data:

  1. In memory data used by ViewModels (ObservableCollection)
  2. Local database data used for caching (SQLite)
  3. Remote database accessed through endpoint (Django)

Synchronisation:

Plan:

  1. SyncService.Sync() Synchronise based on time, preferred way of calling, should be called on Login but before Navigation

  2. SyncService.FullSync()

    Performs full synchronisation, clears local database and pulls everything from remote to update. This should be called when a button somewhere is presseed, probably put the button in SettingsPage?

Currently the Sync() method is using the FullSync() implementation due to lack of backend support. Also need to take care of data race conditions due to multiple access of the Sqlite database (loading data while syncing).

Special case

Anything with an image.

Get:

  1. Backend - URL
  2. Sqlite - URL
  3. ViewModel - URL

Add/Update:

  1. Backend - URL
  2. Sqlite - URL
  3. ViewModel - local file path

So basically what happens during an Add/Update is:

  1. ViewModel to select image
  2. Path is passed to some ImageService
  3. ImageService converts the path into a byte[]
  4. The byte[] is passed into the REST Service and passed to backend
  5. The resulting URL is then stored into Sqlite
  6. The next time the image is displayed on ViewModel would be through the URL in Sqlite