Replies: 1 comment
-
This should be updated and added to a dev doc in the docs folder in the source code. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I would like to start here a collection of preliminary development conventions/best practices.
All open for discussion of course, but I think it would be good to have an overview of the current state.
As all is in heavy development changes will happen and new things will get appeneded.
JavaFX
MVC pattern
We use the standard MVC pattern.
The Controller is responsible for behaviour and determines the hierarchical structure of sub MVC groups or components.
It creates the View and Model and pass JavaFX containers of child components/views to the view.
View receives the model and the controller reference. It use bindings or read access to its model and calls methods on the controller for interactions using the
on[Action]
convention, e.g.onSelectMarket
. It never sets fields in the model though it can use bidirectional bindings.Model holds state but does not do any logic.
There are util classed for TabViews with its own MVC base classes and support for natigation.
Components
We break up complexity of larger views by using components to encapsulate one feature (e.g. selecting a market).
A component consists of its own MVC classes as inner static classes. All access to those MVC classes is private. We use the short names
Model
,View
,Controller
inside the component.The component class expose interfaces and receives dependencies in its constructor. It creates its controller and usually expose the root container of the view which is used by other views for display.
We don't use Getter/Setter methods or ReadOnlyProperties inside components. Its the responsibility of the developer to use the model fields correctly (e.g. never set a model field from the view).
Lifecyle
MCV classes have
onViewAttached
andonViewDetached
methods which get called once the view container is attached to the scene. First the view is called, then the controller, then the model.Bindings, listeners, subscriptions should be set in onViewAttached and removed in onViewDetached.
It is still an open discussion if we should follow a strict pattern here or be more tolerant and do not handle unbind, removals.
In most cases there will be no memory leaks as the related objects get removed all together and/or as the JavaFX framework is using weak references. But there might be some sublte problems and there are some known issues, so it is still undecided if we should go the safe but more boilerplate way or not. At least bindings to service classes must be removed as the services would hold a reference to the controllers if not.
See https://tomasmikula.github.io/blog/2015/02/10/the-trouble-with-weak-listeners.html for more discussion.
UI Utils
To avoid boilerplate code and to get a unified style/layout we use Bisq specific wrappers for most common JavaFx controls or containers.
For instance BisqTableView or BisqDataGrid. Those offer various util methods.
BisqDataGrid offers
add[Control]
methods to add other components to the datagrid and applies the layout likeaddTextField
oraddButton
.BisqTableColumn offers a build interface for optionally defining properties and behaviour of the columns cell renderers.
The Popup class is similar as in Bisq 1 (copied over and adopted) as well the Notifications.
Navigation
Main menu items are defined in the
bisq.desktop.NavigationTarget
class. NavigationTarget has an optional parent (root has no parent) and the path is dynamically created from the hierarchy.The
bisq.desktop.Navigation
class offers anavigateTo
method to the NavigationTarget with an optional data object to be passed to the target controller. If data is used the target controller should offer a inner class to provide clearly defined types.A controller which is handling navigation of sub views extends from
NavigationController
and passes the NavigationTarget which represents its responsibility to the super class (e.g. ContentController passes NavigationTarget.CONTENT).It implements the
createController
method where it creates the child controllers (those who have the controllers NavigationTarget as parent). The model extends NavigationModel and handles the navigation by setting the view property on which the view listens for changes to apply the change of a child view.Domain
Services
Any domain logic and state need to be handled in the domain services. Each module has a high-level service class as its main API. Lower level services should not be accessed from other domains (not applied yet sufficiently...).
We use a custom
bisq.desktop.common.observable.FXBindings
class to glue JavaFx properties or ObservableCollections to domain specificbisq.common.observable.Observable
fields orbisq.common.observable.ObservableSet
collections. FXBindings handles the mapping to the JavaFx Application thread and for collections there is a mapper function to map the domain object to a UI class (usually some list item).Examples:
I use
pin
as convention for the handle to unsubscribe to avoid confusion withSubscriber
used in EasyBind.Asyn handling
For asynchronous code we use CompleteableFutures.
Threading
We use executors passed to CompleteableFutures for async execution.
The domain service should provide a thread pool or executor used for listener notifications to avoid that a client listener doing heavy work impacts the domain service's worker thread.
For time based executions we use the
bisq.common.timer.Scheduler
class.In the JavaFx context we use
bisq.desktop.common.threading.UIScheduler
orbisq.desktop.common.threading.UIThread
for wrappingPlatform.runLater
calls.Persistence
For persisting data there are 2 interfaces:
PersistableStore
for the class which should get persisted andPersistenceClient
for the service class handling the persistence and owning the PersistableStore field.Persisted data gets read at application startup before any service initialisation. At the moment we use Java serialisation as it does not add effort during development, but we will use ProtoBuffer later.
Every domain has its own persisted file.
Lombok
Lombok is used for avoiding boilerplate getter/setters and logger definitions.
UI Frameworks/Libraries
We try to reduce dependencies as much its feasible to reduce supply chain attack risks.
We use currently following libraries in the desktop module:
org.fxmisc.easybind:easybind
- we should fork it at some point.The
jfoenix
,fontawesomefx
,controlsfx
libraries are preliminary and we should find a proper replacement for them (maybe Gluon). We should not use references to those inside the UI code, but only in the generic component classes so once we replace them we have less effort.Domain Frameworks/Libraries
For other domains we use:
Any small-scale library should be forked and maintained by Bisq for better control.
I would prefer to not use the rx lib as it has a huge footprint and lot of overlap with existing Java native functionalities (Streams, CompleteableFutures).
As mentioned initially its all in development and stated conventions might not be applied everywhere as things developed and changes over time.
Beta Was this translation helpful? Give feedback.
All reactions