Architecture & Design Guidelines - ChrispyPeaches/FocusFriends GitHub Wiki
- Defining a method within the application requires setup beyond creating a new file.
All methods need:
- A Query or Command
- A Validator for the Query or Command, and
- A Handler
- !!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](https://private-user-images.githubusercontent.com/63073663/296099146-fddc8552-c125-45e4-b57e-a84ae0fb713f.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjUxNzIxMTIsIm5iZiI6MTcyNTE3MTgxMiwicGF0aCI6Ii82MzA3MzY2My8yOTYwOTkxNDYtZmRkYzg1NTItYzEyNS00NWU0LWI1N2UtYTg0YWUwZmI3MTNmLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MDElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTAxVDA2MjMzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI2YjM2YjQzMzMzODdhMWE3ZWI2OTVkYTZhZTkxNTg0M2YzMzdkMDhiYmRhNGM2NWQ2YWY4MzNhNDg0NzU2MGImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.ZljrDEEeRG4ix4E2pfYe5IUxoof776hnX-O-LHd20Fk)
- When defining the Query, ensure that the newly created query class inherits from the type "IRequest<ModelThatIsReturnedFromMethod>"
- !!NOTE!! Commands are used whenever the target method writes to the database.
![Screen Shot 2024-01-11 at 7 26 10 PM](https://private-user-images.githubusercontent.com/63073663/296098991-a6c5dd44-1fd2-4c6e-8a14-7c253a8a7b87.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjUxNzIxMTIsIm5iZiI6MTcyNTE3MTgxMiwicGF0aCI6Ii82MzA3MzY2My8yOTYwOTg5OTEtYTZjNWRkNDQtMWZkMi00YzZlLThhMTQtN2MyNTNhOGE3Yjg3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MDElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTAxVDA2MjMzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTU1MmIxODAyOGI5MTRmMzBiNjAxMzliN2ZhYzMxM2ZiM2RkNzgwNTk3MDgyZWZkMWRjZWYzOGIxOTg0YzNjMWMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.H9AID6V2J0amrkcAzb0E7SSpxJ8IYzho0BQRg12jEQY)
- 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>"
- !!NOTE!! Both Queries and Commands should have Validators with rules defined for them.
![Screen Shot 2024-01-11 at 7 27 18 PM](https://private-user-images.githubusercontent.com/63073663/296100435-370ccf4d-8612-487c-a010-9368197cd8db.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjUxNzIxMTIsIm5iZiI6MTcyNTE3MTgxMiwicGF0aCI6Ii82MzA3MzY2My8yOTYxMDA0MzUtMzcwY2NmNGQtODYxMi00ODdjLWEwMTAtOTM2ODE5N2NkOGRiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MDElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTAxVDA2MjMzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE3YjJmODkxNDk5MDdmMDRhMGFmNGI0N2QzY2UwMzBmOGUyN2FkMjY3ZjUwY2I2ZDIwNTM1YTc2NmI2NWFjMTMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.v2pZePSNrGTnJpk1H9G9a3IGuOogmObC_6VlRJ3ngiQ)
- When defining the Validator ensure that the newly created Validator class inherits from the type "AbstractValidator<QueryOrCommandType>"
- 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:
- 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:
- 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.
- 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.
![Screen Shot 2024-01-11 at 8 57 50 PM](https://private-user-images.githubusercontent.com/63073663/296114267-b836c572-1e5a-4f65-9290-f6d6bd095456.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjUxNzIxMTIsIm5iZiI6MTcyNTE3MTgxMiwicGF0aCI6Ii82MzA3MzY2My8yOTYxMTQyNjctYjgzNmM1NzItMWU1YS00ZjY1LTkyOTAtZjZkNmJkMDk1NDU2LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MDElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTAxVDA2MjMzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTBjYzI4ZjEzZTljN2U1MTg1YjAxMGVkYTI5ZDc4ZTZmODBmZTU4NDU1Y2I3NzMyYmM1MzEyM2U3ODg5ZWY1NTAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.zFmoHFAVDlB7JTZC7GuQmvsqxRBu9Vp6aK7wWkAayYA)
- 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.
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
andFocusApp.Tools
- FocusApp.Tools exists solely as a target startup project for applying migrations to the FocusApp database context
-
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
- Tab navigation is handled by
- Data - Holds the database context
- Models - Holds the database models
- 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
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 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 theUpdateApplication
event and reload the UI when the event is triggered
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.