Architecture & Design Guidelines - ChrispyPeaches/FocusFriends GitHub Wiki

Architecture Overview

Solution Dependency Overview

Screenshot 2024-02-10 234627

Application-Wide

Method Definition

  • Defining a method within the application requires setup beyond creating a new file.

Method Components

All methods need:

  1. A Query or Command
  2. A Validator for the Query or Command, and
  3. A Handler

Query Definition

  • !!NOTE!! Queries are used whenever the target method reads from the database, but does not write to the database.
Screen Shot 2024-01-11 at 7 26 33 PM
  • When defining the Query, ensure that the newly created query class inherits from the type "IRequest<ModelThatIsReturnedFromMethod>"

Command Definition

  • !!NOTE!! Commands are used whenever the target method writes to the database.
Screen Shot 2024-01-11 at 7 26 10 PM
  • When defining the Command, if no data is returned from the function, ensure that the newly created Command class inherits from the type "IRequest<Unit>"

Validator Definition

  • !!NOTE!! Both Queries and Commands should have Validators with rules defined for them.
Screen Shot 2024-01-11 at 7 27 18 PM
  • When defining the Validator ensure that the newly created Validator class inherits from the type "AbstractValidator<QueryOrCommandType>"

Handler Definition

  • Handlers are the methods targeted by the mediator. The mediator uses the Command or Query fed to its Send method to find the requested handler and execute the appropriate code.
  • Before creating the Handler, wrap the Handler class in a class whose name defines the purpose of the Handler.
  • When defining Handlers, ensure the Handler class inherits from "IRequestHandler<CommandOrQuery, ModelThatIsReturned>"

Example of a Query Handler: Screen Shot 2024-01-11 at 7 28 46 PM

  • Note that the Handler is wrapped within the GetUser class, and that the Handler class itself inherits from "IRequestHandler<GetUserQuery, UserModel>". This means that the handler takes in a GetUserQuery, and will return a UserModel.

Example of a Command Handler: Screen Shot 2024-01-11 at 7 28 14 PM

  • Note that the Handler is wrapped within the CreateUser class, and that the Handler class itself inherits from "IRequestHandler<CreateUserCommand, Unit>". This means that the handler takes in a CreateUserCommand, and will return nothing after execution.

Method Execution

Code Execution From API Controller

  • When calling a method defined in the manner above, all that is necessary is to call "await _mediator.Send(CommandOrQuery)" from a controller that has access to the mediator.
  • It is best practice to keep controller code minimal, and do as much processing as possible within easily testable units of code.

Example of API controller code:

Screen Shot 2024-01-11 at 8 57 50 PM

File Structure

  • Methods utilized by the MediatoR that do transactions with the database are stored in FocusAPI/Methods/<ModelType>. Ensure that newly created methods are located within a subfolder named after the datatype of focus for the method.
  • Commands, Queries, and Validators are defined within the FocusCore project, and share the same document structure as the methods.

FocusApp

Project & Folder Structure

The FocusApp is broken into 3 projects to allow for entity framework migrations

  • FocusApp.Client holds the Maui app
  • FocusApp.Shared holds the database models and context to be shared by FocusApp.Client and FocusApp.Tools
  • FocusApp.Tools exists solely as a target startup project for applying migrations to the FocusApp database context

image

Client Folder Structure

image

  • Helpers holds any services, classes, or functions.
    • For example, a helper class that handles sending emails or a helper function that formats a date.
  • Resources holds static resources such as images, fonts, or icons.
  • Pages holds all content pages.
    • Shop holds all views in the Shop tab.
    • Social holds all views in the Social tab.
  • AppShell.cs holds the navigation and tab bar for the application.
    • Tab navigation is handled by SimpleToolKit.SimpleShell

FocusApp.Shared Folder Structure

  • Data - Holds the database context
  • Models - Holds the database models

Simple Shell Breakdown

  • Simple Shell is a package provided by SimpleToolKit. It is built off of .NET Maui's standard Shell but provides containers to support additional customization and flexibility with the tab bar and page navigation.
  • The majority of tab and navigation logic is contained in AppShell.cs
  • Specific documentation and samples for SimpleToolKit.SimpleShell can be found here: https://github.com/RadekVyM/SimpleToolkit/tree/main/docs/SimpleToolkit.SimpleShell
  • To call a page using depenedency injection, a ShellContent's DataTemplate needs to be instantiated using the page's Type. For example:
    new ShellContent()
    {
        Title = "TimerPage",
        ContentTemplate = new DataTemplate(typeof(TimerPage)),
        Route = "TimerPage"
    }
    

A simplified version of the visual structure is displayed below. However, our application lacks the "app bar" in the diagram

image

The tab bar is persistent across all pages (although this can be changed) while pages are displayed in a navigation host which acts as a container for the current page. Navigation is performed by providing the desired route to a GoToAsync shell command

Hot Reload

Hot reload functionality is not enabled by default for C# Markup Maui apps, but there are ways to enable it. The method used in the FocusApp project creates two events, a ClearCache event and an UpdateApplication event, both of which are triggered by a hot reload. This is powered by Maui's MetadataUpdateHandlerAttribute.

  • AppShell.cs needs to be reworked for the UpdateApplication event and reload the UI when the event is triggered

Dependency Injection

Pages

The FocusApp's pages are automatically registered as Transients as long as they inherit the BasePage class

internal class TimerPage : BasePage

This pattern was inspired by this guide on dependency injection. The ViewModel transient registration was replaced with Page transient registration since the FocusApp essentially combines ViewModels and Pages.

Check under the FocusApp > Simple Shell Breakdown section of this document for more information on how to call a page using dependency injection.

⚠️ **GitHub.com Fallback** ⚠️