Skip to content

Webapp System Architecture

FollowSteph edited this page Sep 29, 2019 · 4 revisions

Below is a screenshot of the current project structure. This page will focus on the core sections and folders and explain what they are and what they do from a high level perspective. Only the more interesting parts will be covered and as a result this is NOT an extensive or detailed document.

Please note that most of the hooks for the backend will be in either calls to the db packages, call to the services packages, updates to the eventbus, or through pushes through the StatusUpdater interface. All backend code that is used in the GUI (the views) should only be through these 4 possible hooks (all described below).

code2

bus

Backend: The eventbus can be used by the backend to push notifications of updates to the front end. All you need to do on the backend is publish and event to the bus and any front end components that are interested will pick them up automatically.

This is where all the eventbus code framework is located. The event bus passes PathmindBusEvent of which we currently have PolicyUpdateBusEvent and ProjectUpdateBusEvent. To keep things simpler we're only passing a single type of busevent for now and so we use the PathmindBusEvent.BusEventType to detect the type of bus event through filters. In most cases we don't have to cast to the specific bus event type as we only need the data id once we know the type.

Similarly most of the time the GUI components are looking for the same types of events with the same filter conditions therefore there is a utils package to help detect interesting since it's often the same code. Currently we have only have PolicyBusEventUtils but this is expected to expand. The filtering is more involved then just the right type of event, for example for the policy it also detects if it's for the same experiment and so on.

data

For a more detailed explanation please refer to the Data Model page.

Every data model object extends the Data class which consists of the id and name attribute. After that if a data model object is archivable (project, model, and experiment) then they will also extend the ArchivableData class. In other words Experiment extends ArchivableData and ArchivableData extends Data.

Each data model object contains a 1 to 1 mapping of the database columns. Beyond that most data model objects will also contain any data they need for the screens (views) so please be aware that if it's not on the screen then it may not be available in the data model. We could include all fields but for now I've kept them as minimal as possible. That being said all data model classes currently contain all the parent objects along with their name and id. So for example the Experiment class contains it's parent Model class (with name and id) as well as it's parent Project class (with name and id). Anything beyond the name and id in the parent objects are NOT guaranteed, again it's only what is needed on the screens beyond that.

Under the data package there is a utils sub-package which includes all the helper methods specific for each data model class that needs to be used in multiple places. Right now most of it is to generate fake data but for example RunUtils has a getElapsedTime method to be consistent since it's more than just reading a couple of values.

db

For a more detailed explanation please refer to the Data Model page.

Backend: The db package is separated into two sub-packages called dao and repositories. The repositories package is meant to hold simple and single SQL queries and calls. Anything that is transactional, involves more than one query, etc. should be located in the dao package. Right now in most cases the dao is a pass-through to the repositories layer (DAO extends Repository) but this is expected to change as more backend database code is implemented.

IMPORTANT: The front end should only ever use the DAO package.

exception

Right now it's very miminal since we're still learning the details and so only contains the InvalidDataException which is used when someone tries to enter a view with an invalid ID in the URL. The PathMindException is the parent exception of all custom exceptions.

security

Right now the security is managed through some simple custom code using the SecurityUtils class as I didn't know what we were planning to use to manage users, if we had roles, etc.

In essence we use the VaadinSession to store the PathmindUser once they are logged in and nullify the value on logout. Then to determine if a user is logged in we look to see if they are in the session. If they aren't we throw an AuthenticationException which brings them to the login page. This means that anywhere we need to get the user's id we just do SecurityUtils.getUserId() and if it's null it throws the AuthenticationException.

To prevent anyone from randomly typing in a URL and trying to bypass the login the main class PathMindDefaultView overwrites the beforeEnter method and does a check to see if the user is logged in and if not automatically forwards them to the login page. Since all views extends the PathMindDefaultView (except the LoginView) this is automatically done on every single view without the developer having to remember to do any login checks. Just a quick heads up the PathMindDefaultView does a lot more than just security checks so every single view (again other than the LoginView) needs to extend the PathMindDefaultView.

services

Backend: This package is meant to be where the backend service hooks are located. In other words outside of events through the event bus, progress bar updates though StatusUpdater, or calls to the database layer, all other backend hooks should come through the services package. So for example a training run should be hooked through the services package.

ui

All the GUI code is located here. The more interesting sections are discussed below.

ui.binders

In cases where the same form elements are used in multiple places then common binders are located here to avoid having to replicate all the mapping, validation, etc. logic.

ui.components

This is the package where common components are located.

ui.components.status.StatusUpdater

Backend: The StatusUpdater consists of the ability to give a progress percentage, error messages, and notify when a task is successfully completed. Right now successfully completed is fileSuccessfullyVerified but this should be more generic and something like successfullyCompleted (I have not yet refactored it as I don't fully know all the new updated hooks). We should also include a failedCompleted method because we may not want to rely on whether or not there is an error as a way of terminating the task (we want to show all the errors as they occur and not stop the process when we encounter the first error for example).

ui.components.searchBox

Anywhere we need a searchbox we can just override SearchBox and all we need to do is supply it the grid as well as the isMatch method which then uses SearchUtils.contains(attribute) on each column to update the grid.

ui.components.archive.ArchivesTabPanel

When we need the ability to archive items in a grid we can just add the ArchivesTabPanel to the screen and it will add all the necessary code to the grid to be able to manage archiving. IMPORTANT: Save is currently NOT implemented as there is no database support for it but I suspect it will just be an extra Consumer in the constructor which will be a lambda on how to save the item to the database. Something along the lines of project -> projectDAO.archive(project)

ui.layouts

Every page other than the login uses the MainLayout as it's layout.

ui.utils

There's several important classes here that you need to know that will greatly simplify your life and coding.

  • ExceptionWrapperUtils needs to be used on any button click action that can result in an exception. The benefit is that it not only saves you from having to write all the try/catch code but it also means that the error handling is consistent through the application (log the error, go to the error view, etc.)
  • FormUtils needs to be used any time a form needs to be validated
  • GuiUtils should be used to setup simple components in a consistent manner such as getBoldLabel and so on
  • NotificationUtils should always be used for any popup notification so that we have consistent popup styles
  • PushUtils should be used for all push code as it will deal with errors and saves the developer from having to remember to wrap everything in an access call
  • WrapperUtils is probably the most important and most used utils class. Any time you need to setup a HorizontalLayout, VerticalLayout, or SplitPane you should always use this class as it makes the code a lot more readable AND it applies the same consistent formatting and styles throughout the application.

views

All views are located here. Each view has it's own package and within that package there can be sub-packages such as components, binders, and so on. However if something is used beyond it's view then it should be promoted to the shared components package. View classes should be minimal and very simple to follow. They should always be kept as clean and simple as possible and refer to outside classes. They should be as close to pseudocode as possible.

The more interesting views are discussed below:

views.PathMindDefaultView

All views (except for the LoginView) extends PathMindDefaultView. In addition to setting up the components and structure PathMindDefaultView also does the login checks and so on. Below we will only cover the main methods of interest that tend to be overriden more frequently:

  • loadData The loadData method is meant to load the data from the database and as such will throw an InvalidDataException if no data is found. The benefit of this is that PathMindDefaultView knows how to handle this exception and will consistently handle data errors. From the developers perspective your code is just DAO.getData and then if that fails you throw an exception. Generally the loadData method is very short and simple.

  • getTitlePanel The getTitlePanel just returns a ScreenTitlePanel (title and subtitle such as Project: projectName) but this is expected to be refactored shortly to match the new screen designs. Basically this is to setup the equivalent of an h1 tag but is more involved. Please keep in mind that this is different than getPageTitle which is used for the browser tab name.

  • getMainContent The getMainContent is as expected, where the main components for the screen are generated. In most cases this method will consistent of WrapperUtils. The goal of this method is to first contain setup methods (as needed) and then to add the components in a simple to read way. The use of WrapperUtils combined with good code formatting (indentations) should make the layout and flow of the screen simple to understand. There should be no code in that method that creates any components including buttons, etc. unless it can be embedded in a single line. This method should JUST create the components and should NOT load the component with data.

  • updateScreen The updateScreen is where the data model (that was retrieved with loadData is mapped into the components of the screen. In some cases this is done through the Binder and in other cases it's just populated. The goal here is that each screen component only needs 1 line to populate itself.

  • subscribeToEventBus The subscribeToEventBus is only used by some screens but it must be done last in the process as it can sometimes need certain things to be setup beforehand.

views.overall

Before getting into some specific views I just wanted to note that we use HasUrlParameter to pass around the data model id's as a way of letting the views know which data to load. It is however possible that we may need to pass more than one id in which case we do view/id1/id2 and use HasUrlParameter<String> and then split the parameter up. For now only ExperimentView needs this so the code is located in that class but should it become needed elsewhere then it will be refactored to be used in a consistent manner.

views.project.NewProjectView

This view is the most complex because it includes the new project wizard. Each panel of the wizard is located in the views.project.components.wizard package.

views.experiment.newExperiment

It needs to be refactored to keep it maintainable like the other views but since it's been in so much flux I've avoided doing it so far.

views.LoginView

Is special in that everything needs to be manually setup.

resources.db.changelog

This is where the JOOQ database setup code is located. The project Readme file has details on the conventions and standards.

Misc

Rather than use profiles in Maven the project is instead setup to use System Environment variables.

Clone this wiki locally