- MaXX Interactive Desktop Architecture
- Layered Architecture Details
- Software Patterns & Design Strategies
- MaXX GPES
- MaXX fileservice
- MidView Conceptual & Internal (SLIDES)
- IconCatalog and FM Renderer Conceptual (SLIDES)
- 5DWM Internals (SLIDES)
- Icon Design
- Best Practices
MaXX Interactive Desktop Architecture
Goals and Requirements
- Provide a secure modern and robust computational environment for smart applications and services.
- Support both local and distributed deployment.
- Be modular, expandable and flexible while supporting a loosely couple design.
- Do more with less. Meaning be smart in how resources are used and keep it in mind.
- Be as fast and efficient as possible. It is a balancing act and it always depends on the use-case.
- Support CPU architecture specific optimizations.
- Support Hardware Acceleration (GPU decode/encode & rendering).
- Support high performance asynchronous messaging for inter-application communications.
- Can run on different OS and CPU architectures (Intel/AMD, ARM and RISC-V).
The MaXXdesktop's global architecture is based on the following five(5) principles in order to ensure the above goals and requirements are met.
- Follows the SOLID Principles
- Follow the Clean Architecture
- Support a Multi-layers architecture style for better/clearer separation with clear boundaries, responsibilities and dependency.
- Support a Message-Driven architecture for both inter-process communication and asynchronous event-driven execution.
- Support Shared Memory to prevent unnecessary copy/duplication of information when doing local (same machine) inter-process computations or when handling/displaying images over a local X11 session.
The SOLID principles were first conceptualized by Robert C. Martin in his 2000 paper, Design Principles and Design Patterns. These concepts were later built upon by Michael Feathers, who introduced us to the SOLID acronym. And in the last 20 years, these 5 principles have revolutionized the world of object-oriented programming, changing the way that we write software.
The SOLID design principles encourage developers to create more maintainable, understandable, and flexible software by shifting their focus on the functionality rather than the low-level details. Consequently, as an application grow in size, adeveloper can reduce its complexity and save his sanity.
The following 5 concepts make up our SOLID principles:
- Single Responsibility
- Liskov Substitution (not so much used anymore)
- Interface Segregation
- Dependency Inversion
While some of these words may sound daunting, they can be easily understood with some simple code examples. In the following sections, we'll take a deep dive into what each of these principles means, along with a quick Java example to illustrate each one.
Clean Architecture is a way of developing software, such that just by looking at the source code of a program, you should be able to tell what the program does. The programming language, hardware and the software libraries used to achieve the objective of the program should become irrelevant. The aim of Clean Architecture is to make the following sentence possible: ‘Hey, the arrangement of directories tells me this is a shopping cart app. I don’t know which programming language or software library is used. I need to go into the directories and find out.’
The architecture is inspired from multi-layered Enterprise class system where each layer defines precise responsibility and its application/service (a.k.a. component) exposes functionalities or behaviours. At the heart of the architecture are clear communications channels between components and their respective layers and well defined data-contracts exposed by each of them.
Overall, this design strategy will improve robustness of the Desktop experience, provide solid foundations to build upon, allow a clean separation of concern, modularity and overall re-usability. Another way to see MaXX's architecture design is to look at micro-services architecture, but from a Desktop application perspective where each layer of the architecture is composed of specialized micro-services performing specific tasks.
Layered Architecture by responsibility
The MaXXdesktop architecture is divided into three (3) responsibility layers from which MaXX's application and service can be built. Below are the layers in question with a short description for each:
- User Experience/Presentation - layer performing visual-oriented tasks like displaying User Interfaces and/or capturing user's input. Components and/or applications on that layers are communicating with the Desktop Support layer for computation and resources access via proxy like services.
- Desktop Support - layer provides desktop computation support while providing an abstraction-layer to various Back-end Services, where most of the actions are taking place. This layer exposed some of those computations as functionally aggregator (composition and proxy design pattern) where a specific Desktop Support functionally is realized by utilizing one or more Back-end Service and an orchestration service that can coordinate the execution of other services/components.
- Back-end Services - layer is where most of the actual work is performed by low-level services/components. This layer can only communicate with components/services from the Desktop Support layer. Among the functionalities exposed by this layer are: hardware and application monitoring from MaXXmonitor, file-system accesses and configuration management via MaXXsettings.
The diagram below illustrates the current MaXXdesktop architecture
MaXX Links (Inter-Layer Communication)
The MaXXdesktop Architecture is planning tree(3) means of inter-layer communication mechanisms ensuring security and maintaining separation of responsibility.
CLI or Command Line Interface - allows a command-line driven client/server like interaction where the Client CLI initiate a CLI session with the Server CLI counterpart and sends one or many CLI commands. The communication is obviously synchronous and based on a request/response semantic that is redirected through the POSIX standard I/Os of the CLI Client application. Nothing prevents the Server CLI to emit an event during the processing of a CLI command. The CLI mechanism is great to provide a fire-and-forget interaction semantic.
HTTP or REST Interface - allows a Web/REST driven client/server like interaction where the HTTP Client sends a request to a HTTP Service. The communication is again synchronous and based on a request/response semantic using HTTP/S and predefined data types. Nothing prevents the HTTP Service to emit an event during the processing of the request. This mechanism is great to provide a a full features API style integration.
Messaging - allows synchronous, asynchronous or event-driven interactions where a producer create a message and send it to a destination via a communication channel. One or many consumers are at the receiving end of that channels allowing either point-to-point or publish-subscribe delivery mechanism. The payload of a message can either contain real data (passing by value) or provide a secured shared-memory location where the data can be retrieved (passing by reference).
A message is an item of data that is sent to a specific destination. An event is a signal emitted by a component upon reaching a given state. An event can be transported via a message, not the way around
The diagram below illustrates the inter-layer communication mechanisms.
The Message-Driven architecture provides low dependency, no tight coupling and robust communication between components and services. MaXXlinks Framework will provide all the necessary features and abstractions to supports modern multi-protocols synchronous/asynchronous messaging communications. Things such as: load balancing, tasks distribution, high availability, decoupling client-server with simple consumer/producer semantic are all possible (and in many cases rather simple to implement) through Messaging.
Messaging also introduce the ability to support a polyglot code base where components integration are performed via high performance messaging channels while supporting both local and distributed environment. It does not really matter any more in which programming language the component/service is written, as long as it supports Messaging ØMQ and complies to predefined data-contracts.
Through MaXXlinks, any C/C++/Python or Java application can be integrated into the MaXXdesktop environment or new modern features can be added to an existing application very easily.
The Messaging approach as been proven to yield excellent values by allowing ongoing improvement of components, total decoupling between producers and consumers and bring robust asynchronous and event-driven capability. As long as data-contracts are respected, changes documented+communicated, with versioning and backward compatibility supported, this is one of the best way to do inter-application communication.
The MaXX Interactive team bring more than 2 decades of real world expertise in High Performance Messaging Systems. That must count for something :)
We utilize this 'passing by reference' strategy quite heavily in GPES (General Purpose Execution Service) as a very efficient way to consume computation results without the net-impact. In some cases, all the work/computation, memory allocation and display are all performed by the GPU.
Layered Architecture Details
Layered Architecture by responsibility
As we previously saw, the MaXXdesktop layered architecture is divided into three (3) responsibility layers from which a MaXX aware application or service can be built.
Here are the layers in question with a list of components (applications/services).
User Experience & Presentation
This layer is performing visual-oriented tasks like displaying User Interfaces and/or capturing user's input. Components and/or applications on that layer are communicating with the Desktop Support layer for computation and resources access via MaXXlinks, a high performance messaging library for inter-process communication.
User Experience & Presentation Components
|5Dwm||Enhanced Motif Window Manager.|
|Toolchest||Desktop Application Menus and Launcher.|
|IconCatalog||Visual and Interactive Application catalogue using vector based icons.|
|File Manager - fm||Visual and Interactive File Manager using vector based icons.|
This is layer provides desktop computation support while also providing an abstraction-layer to various Back-end Services, where most of the actions is taking place.
The exposed computations falls into the following category:
- functionally aggregator (composition and proxy design pattern) where a specific Desktop Support functionally is realized by combining one or more Back-end Service.
- orchestration service that can coordinate the execution of other services/components/tasks.
- session management keeps track of all application's layout and state, can create and retrieve snapshots of specific moments.
- application launcher with real-time insights of current computation loads, application settings and preferences.
Desktop Support Components
|MaXXlauncher||Smart Application and Service Launcher.|
|MaXXscope||Smart Application and Service Orchestration for multi-core systems with CPU cores affinity & partitioning in mind.|
|MaXXsession||User Desktop Session Manager with snapshot capability.|
|MaXXgpes||General Purpose Execution Service - Centralize and Unified Task Execution Environment.|
This layer is where low-level system work is performed. For better security, this layer can only communicate with components/services from the Desktop Support layer. Among the functionalities exposed by this layer are: hardware and application monitoring (MaXXmonitor), file-system accesses(MaXXfileservice) and configuration management (MaXXsettings).
Back-end Services Components
|MaXXsettings||System Settings and User Preferences Management Service.|
|MaXXmonitor||Centralized Hardware and Application monitoring with metric aggregation.|
|MaXXfileservice||High Performance and Multi-threaded File System Service.|
updated - 2021-06-23
Work in progress...
Software Patterns & Design Strategies
This document will describe some of the software design patterns that are used throughout the MaXXdesktop Architecture, how they provide reliable solutions to common requiring problems and most importantly, shed some light on some selected strategies for building different types of desktop applications.
As per the Architecture document, we now know that MaXXdesktop was designed using a multi-layered and message-driven architecture which follows the SOLID principles and Clean Architecture. Each layer of the architecture hosts applications or services with specific characteristics, behaviours and responsibilities.
We see here a great opportunity for defining reusable and common design strategies for building high performance visual applications, dependable desktop support and back-end services. By promoting proper use of design patterns we can bring up the code quality, maintain a strong, robust and yet flexible architecture, increase predictability and lowers risks associated to changes. All this translates into better user experience and a software system that can evolve over time without suffering from a middle-age crisis every two-three years...
A few words on Clean Architecture first. Our intent is to follow what makes sense for the MaXXdesktop and to provide concrete scenario in order to avoid ambiguity.
So good news folks, this document will try to address do just that, and maybe pick your curiosity and learn a different way to write code.
This section will go over some of the design patterns used in MaXXdesktop and how we put them together to build reusable components.
Throughout the document, we will use the following arrows to illustrate the relationship type between objects. For example, A⟶B, reads A is using B as a dependency type relationship. The arrow marks the direction of the dependency.
The legend below will help the reader to better understand the intent and relationship between objects.
The Observer pattern is one of the twenty-three well-known "Gang of Four" design patterns describing how to solve recurring design challenges in order to design flexible and reusable object-oriented software. An Observer simply observe objects, named the Observable (or subject in generic term), by maintains a list of those and notifies them automatically of any state changes. This behaviour is usually implemented from an Interface rather than inherited. It is mainly used for implementing local or distributed event handling systems, in "event driven" software.
Diagram illustrates the Observer/Observable in context with ModelView and View.
The Observable pattern facilitates event-driven behaviours where Observers stands ready to react from an Observable state change in a non-blocking and asynchronously way. The Observable emits a state change notification to all its subscribed Observers. Upon reception of such notification, the Observer can react accordingly to that state change. This behaviour is usually inherited instead of been implemented from an Interface.
The View design pattern is one of the most popular one and easy to understand. But sadly the View pattern as been poorly used throughout the years, and on an epic scale. The View is simply a device specific delivery mechanism that displays the content of a ModelView, captures user's input, and send those inputs to a Controller. The View must implement the Observer pattern in order to receive data modification notifications from the ModelView.
The ViewModel pattern is defined as a simple Observable values container used by the View. The ModelView should not contain business logic and the data transformation responsibility is passed to the Presenter. The ModelView only contains simple values like Strings, flags and others which are already transformed values ready to be displayed by the View. The ModelView must extend the Observable pattern in order to emit data modification notifications to its View. Those notifications are triggered when the Presenter sets the ModelView data.
Diagram illustrates the use of the ModelView, View, Presenter and Controller Patterns.
The responsibility that characterized the Presenter pattern is to reformat a ResponseModel object received from an Interactor into a ModelView. Upon reception, the Presenter transforms the received ResponseModel into a viewable representation as a ViewModel.
The Controller pattern definition in our architecture, is an object that handles inputs from a View, converts them into RequestModel and send them to the Interactor via a Boundary. The inputs are usually events generated from the View. In term of responsibility and features, that it. Nothing else.
The ResponseModel pattern is an object that represents the output that is sent to the user of the system, usually in response to the input or due to other triggers such as a scheduled time or an UI event happening. The ResponseModel is a DTO (Data Transfer Object) containing values such as text and numbers.
The RequestModel pattern is an object that represents the input data from a View. The RequestModel can be describe as a generic representation of the input data required for a computation or function call, which usually contains simple data types like numbers and text. The RequestModel is also a DTO.
Diagram illustrates the interactions between Interactor, Boundary, Presenter and Controller Patterns.
Our Architecture is designed in layers and follows the Clean Architecture and SOLID Principles, so that peripherals, services, computational resources, and data provider components such as MaXX Settings can be swapped as requirements change. For this reason, the core components of the Desktop applications, i.e. Entities and Interactors, never talk directly to those components. Rather, Interfaces called Boundaries are made so that calls are made across them. One side of the Boundary makes calls and expects a form of response that is agreed upon. The other side of the Boundary receives the calls and returns those responses. Both sides usually do not know who is on the other side. They just act upon the requests and responses. Such design also allows components to be tested individually by using mock / fake components across the boundaries.
Let’s talk about boundary types.
This is the Boundary between the input system and the Interactor. The input system does not deal directly with the Interactor. Instead an Interface is offered along with a set of method calls that receives a request. These calls promise that the input will reach the Interactor properly and that the use case will be executed.
This is the Boundary between the output system and the Interactor. This makes sure that the Interactor does not know how the response will be shown to the user. The Interactor passes along the data inside the response. But formatting the response is upon the output system on the other side of the Boundary.
Interactor is a design pattern that could be describe as a specialization of the Command pattern with the specific responsibility of fulfilling a specific use-case. The Interactor receives a RequestModel from its Input Boundary Interface, then sets things in motion like an orchestra director, coordinate the execution of a use case. There must be one Interactor per use case in a properly designed system/application. It is not uncommon to see the use of the Service pattern in conjunction with the Interactor. This allows an even better separation of responsibilities.
The Entity pattern focus is on the domain data, its validation rules and business logic that creates an output in response to an input. After receiving input from the user, the Interactor uses different Entities in the system to achieve the output that is to be sent to the user. The Entity, like the Interactor is using a Boundary interface to access its data source through a Gateway. Remember that the Interactor itself should NEVER directly contain the logic that transforms input into output.
The Interactor or Entity will often need to interact with an external system, or a Desktop Support or Back-end service, for that purpose we encourage using the Gateway pattern. A Gateway has to cross boundaries as well, but toward another system or service, and it is fair to call then Boundary. However it would add ambiguity to the intent, therefore, Gateway it is. Gateway is a specialization of Boundary which acts as a Reverse-Proxy and instead of hard-wiring the code specific to an external service access inside an Entity, a Boundary Interface is made. The Entity calls the methods over a Gateway's boundary interface and the components on the other side will use the specific service. This approach makes the code very modular and plug-and-play.
Diagram illustrates the interactions between Entity and an external service via a Gateway.
Here are some more complete examples on how we put together well defined patterns and build reusable design strategies for the MaXXdesktop that are robust, easy to test, and flexible. Below, we demonstrates one strategy per actual architectural layer and some re-usability scenarios.
Here are the assumptions from which the strategies are put together.
Inter Application Communication
One very apparent advantage of proper architecture and design pattern selection is that we can see right away some similarities in the strategies. A first use case is the "inter-application communication" where the same recipe is use over and over. This is good! Now we can focus on building that strategy right with re-usability and flexibility in mind for both how UX and Desktop Support applications are communicating to the outside world. The only variant in both use cases is the "consumer" in front of the Gateway.
We could even push one step further and adapt that strategy for Back-end Services data access mechanism by replacing the MaXXlinks component with either a database like access for filesystem, MaXXmonitor metrics and MaXXsettings CLI. Write once, use everywhere :)
A second use case is with the "Boundaries" where the same recipe is used repetitively, with only one small variant for UX. This reusable strategy will serve as a massive improvement over the old MVC pattern (which we do not use) and as INGRES for both Desktop Support and Back-end Services. Suddenly the entire Desktop code base is considerably reduces in complexity and size thanks to reusable strategies and a clean architecture.
User Experience (UX)
From what we now know, most of the UX applications (except the window manager and a few very specialized use cases) are much simpler to build. For example, the User Preferences Panels will be reusing over 50% of their EGRESS code, which leaves us with the View, ModelView, Presenter, Interactor an Entry.
Here's how we see one good reusable strategy for visual application on the UX layer.
The Desktop Support services are even simpler to build. Al of our attention should be directed to the Interactors which are use cases execution orchestration. The INGRESS components are quite similar with small variant in the messages routing and EGRESS are identical.
Here's how a good reusable strategy for Desktop Support service looks like.