Architecture - ninazeina/SXP GitHub Wiki
Folders & files
src/main/java
: the backendsrc/main/js
: the frontendsrc/main/ressources
: Common ressource files for project (configuration, images...)src/tests/
: The test files, typically JUnit, mirroring the project treelibs
: Java libraries not retrieved by Gradlebuild
: Compiled backend.src/main/js/SXP-XXX
: Compiled frontend.
More:
.db-XXX
: Temporary, generated at run-time. The database. Several when testing multi-user single-machine..peer-XXX
: Temporary, generated at run-time, stores information about a peer..project
: Your own eclipse files.git
: Your own local git repo..gitignore
: So git ingnore certain files.Gradle
: A distribution of Gradle, the builderbuild.gradle
: configuration file for Gradle, dependencies..gradle
: Generated by gradle.COPYING.txt
: LGPL License
Modules
- client : an html5-based GUI
- rest : a server, listening to requests and passing them to the controller
- controller : the main, instantiates everything, processes requests
- model : the database, the entities, their format, the managers for them
- crypt : signing, encrypting, hashing
- network : P2P
- protocols : for establishing contracts
- resilience : for backing up data, possibly over the network
Respect the modularity
Each module (except for the JS client) is a package containing several subpackages:
- api : high-level, public access to the package services
- factory: high-level, public access to the objects
- impl : a particular implementation of the api
- [base] : what is common to all implementations of the api
- [utils] : auxiliary classes
Façade Design Pattern
The module services must only ever be accessed via its api and factory. This is the façade Design Pattern, which must be respected at all times.
Factory Design Pattern
It is often the case that a module X needs to instantiate a class C from a module Y.
Example: the module controller may need a Hasher, whose interface is described crypt/api
But module Y implements the class in Y/impl/C, certainly not in Y/api/C.
Example: the implementation class is crypt/impl/hasch/SHA256Hasher
Therefore we have a problem, how can X instantiate C whilst never transpassing into Y/impl? The solution is that X must call some create() method provided in Y/factory instead. This create method will be made to return a class C.
Example: the class crypt/factory/HasherFactory provides createDefault() and create("SHA256")
The module classes must only ever be accessed via factory. The Factory Design Pattern, must be used whenever needed to maintain modularity.
View-Controller
The client is an html5-based GUI. The rest module is very generic and easy to use, see CommandLine. It passes on the requests to the controller.
The Core
The controller has the main, which instantiates everything, and processes/dispatches requests. In the simplest form it would just pass them on to the model. But here the controller/managers allow for further processing which may have to do with crypt (encryption/signing), network (P2P), resilience of the data. Contract signing, done by protocols, is still experimental.
Managers and the Decorator Design Pattern
In most applications, the view signals the controller to modify the model, which signals the controller to refresh the view. Quite often, the model is a database, which is handled via a manager. Here also, the model package provides a basic manager. This basic manager is synchronous: modify the database takes immediate effect, searching the database leads to immediate results.
An asynchronous manager no longer assumes that: writing is but sending on a network, searching may eventually produce a result, but we will be notified when this happens. To put things on a equal footing, we first adapt the basic manager that it becomes an asynchronous manager. We then decorate its methods, by piling up ManagerDecorator on top of the asynchronous manager, in order to describe extra behaviour, such as the fact that entities should be encrypted, advertized over the network, backed up over the network, etc.