This document discusses the hexagonal architecture pattern for Qt embedded HMIs. It begins with an introduction to ports-and-adapters architecture, then discusses using this pattern to create machine, business logic, and GUI components with different adapters for products, simulators, and tests. It explains how to create these components, connect them, and configure the overall application to be highly modular, testable and maintainable. Benefits of this hexagonal architecture include high testability, modularity, and maintainability, though it does add some complexity. Resources for further information are provided.
2. Good, Right and Successful Architectures
All architectures
Good architectures
Technically sound
Right architectures
Meeting stakeholder needs
Successful architectures
Delivering value
Hexagonal
Architecture
Start with de-facto standard
architecture and adapt it!
2021/07/08 (C) Burkhard Stubert 2
Ports-and-Adapters
Architecture
3. About the Importance of Architecture
2021/11/04 (C) Burkhard Stubert 3
2012
2021?
Architecture is like an iceberg. Only 1/10 is visible.
The Titanic disaster teaches us what happens
when we ignore the other 9/10.
Yours truly
6. Ports-And-Adapters Architecture: Pattern
• Port = Interface between BL
(inside) and Adapters (outside)
• Adapter = Uses or implements
Port
• 1 Port has N Adapters
• Port same for all adapters
• Code from inside must not leak
to outside and vice versa
• Inside: models, connections
• Outside: Qt SerialBus, SQL, IPC
2021/11/04 (C) Burkhard Stubert 6
Business Logic
(BL)
GuiPort
MachinePort
GUI Test
Machine Simulator Mock
CLI
7. Product Versions over Time
2021/11/04 (C) Burkhard Stubert 7
Business
Logic
GUI
Machine
CAN
Business
Logic
GUI
Machine
Ethernet
Business
Logic
GUI
Machine
(IPC)
Product v0 Product v1 Product v2
• OTA Updates
• Ethernet instead of CAN
• Remote diagnostic
and support
• Message processing
in separate process
8. Development and Test Versions
2021/11/04 (C) Burkhard Stubert 8
Business
Logic
GUI
Test
Machine
Mock
Development Testing GUI
in system context
Business
Logic
GUI
Machine
Simulator
Business
Logic
GUI
Mock
Machine
Mock
Testing Updates
in system context
12. Machine Port with Product Adapter (CAN)
2021/11/04 (C) Burkhard Stubert 12
In product_machine.h:
QCanBusDevice *m_canBus{QCanBus::instance()
->createDevice("socketcan", "can0")};
CanBusRouter m_router{m_canBus};
EngineTwin m_engine;
In product_machine.cpp:
ProductMachine::ProductMachine()
: Machine{}
{
QObject::connect(
&m_router, &CanBusRouter::newEngineQuantities,
&m_engine, &EngineTwin::updateQuantities);
In BusinessLogic:
Machine::engine()
Engine ECU
QCanBusDevice
CanBusRouter
EngineTwin
Business Logic
can0
Machine
SocketCAN frame
QCanBusFrame
Quantity
Quantity
Quantity
13. Machine Port with Simulator Adapter
2021/11/04 (C) Burkhard Stubert 13
In simulator_machine.h:
CanBusSimulator m_simulator;
MockCanBusDevice m_canBus;
CanBusRouter m_router{&m_canBus};
EngineTwin m_engine;
In simulator_machine.cpp:
SimulatorMachine::SimulatorMachine()
: Machine{}
{
QObject::connect(
&m_simulator, &CanBusSimulator::newIncomingFrames,
&m_canBus, &EngineTwin::appendIncomingFrames);
QObject::connect(
&m_router, &CanBusRouter::newEngineQuantities,
&m_engine, &EngineTwin::updateQuantities);
In BusinessLogic:
Machine::engine()
CanBusSimulator
MockCanBusDevice
CanBusRouter
EngineTwin
Business Logic
Machine
QCanBusFrame
QCanBusFrame
Quantity
Quantity
Quantity
14. Test Adapter
2021/11/04 (C) Burkhard Stubert 14
In Machine Test:
Machine::engine()
MockCanBusDevice
CanBusRouter
EngineTwin
Machine Test
Machine
QCanBusFrame
QCanBusFrame
Quantity
Quantity
Quantity
Product Adapter (MQTT)
In BusinessLogic:
Machine::engine()
Engine ECU
QNetworkInterface
MqttRouter
EngineTwin
Business Logic
eth0
Machine
MQTT frame
QMqttMessage
Quantity
Quantity
Quantity
15. Creating the Machine Component
2021/11/04 (C) Burkhard Stubert 15
In machine_creator.cpp:
Machine *createMachine(Machine::Configuration conf)
{
switch (conf) {
case Machine::Configuration::Product:
return new ProductMachine{};
case Machine::Configuration::Simulator:
return new SimulatorMachine{};
...
Use createMachine in main()
16. Running Adapter in Thread, Process, MCU
Business Logic
MachinePort
Qt CanBus
can0 can1
Business Logic
MachinePort
Qt CanBus
can0 can1
Thread
CAN0
Thread
CAN1
Qt Signal-Slot Conn.
Business Logic
MachinePort
CAN Bus
can0 can1
Thread
CAN0
Thread
CAN1
Qt Remote
Objects
Task
CAN0
Task
CAN1
RPMsg
Process
1
on
A53
Process
1
on
A53
Process
2
on
A53
Process
1
on
A53
2
Tasks
on
M4
2021/11/04 (C) Burkhard Stubert 16
20. GUI with Product Adapter (QML GUI)
2021/11/04 (C) Burkhard Stubert 20
In MainView.qml:
import EmUse.Models
Pane
{
property MainModel model: BusinessLogic.mainModel
Text
{
text: Number(model.engineSpeed.value).toFixed(0)
}
Text
{
text: model.engineSpeed.unit
}
MainView
Business Logic
Machine
EngineTwin
MainModel
21. GUI Port with Test Adapter
2021/11/04 (C) Burkhard Stubert 21
void TestMainView::init()
{
m_machine = std::make_shared<Machine>(
createMachine(Machine::Configuration::Mock));
m_businessLogic = new BusinessLogic{m_machine};
m_model = m_businessLogic->mainModel();
}
void TestMainView::testEngineSpeed()
{
Quantity rpm{930.0, "rpm"};
emit m_machine->engine()->engineSpeed(rpm);
QCOMPARE(m_model->engineSpeed()->quantity(), rpm);
}
TestMainView
Business Logic
Machine
EngineTwin
MainModel
Testing GUI in system context
• with actual BusinessLogic
• with mocks for Machine objects
Mocks reused from unit tests of
Machine and EngineTwin
25. Pros and Cons of Hexagonal Architecture
• High testability
• High modularity
• High modifiability
• High maintainability
2021/11/04 (C) Burkhard Stubert 25
Pros
• Additional complexity
Cons
Succesful architecture for Qt embedded HMIs:
Hexagonal Architecture
26. Resources
• Alistair Cockburn: Hexagonal Architecture. Original description of the
Hexagonal Architecture pattern.
• Juan Manuel Garrido de Paz: Ports and Adapters Pattern (Hexagonal
Architecture). Detailed explanation of the pattern.
• Burkhard Stubert: A Successful Architecture for Qt Embedded
Systems. My talk at QtDay Italy 2021.
• Burkhard Stubert: Source code for this talk.
2021/11/04 (C) Burkhard Stubert 26
Fairbanks: “Presumptive architectures are usually successful.”
Good = efficient
Right = effective
Successful = valuable
Lets transfer the hardware idea into software
The PC with its USB ports becomes the business logic with several APIs or ports
If we unfold the adapter-BL-adapter paths of the hexagon, we get multiple instances of 3-layer architecture
The rest of the application does not notice that different ways of communications are used with machine.
Remote Diagnostic/Support is just a remote GUI. Remote diagnostic could be mirrored on-board (in the GUI).
This single requirement drives the architecture.
Abstraction increases from left to right.
ProductMachine implements the Machine interface
CanBusRouter
- routes messages to correct ECU twins
- decodes/encodes CAN frames to/from Quantities
SimulatorMachine implements the Machine interface
Can be used for playing back recorded CAN traces
Use #ifdef’s to get non-product relevant code out the application binary.
Motivation: Avoid GUI freezes & stuttering, avoid buffer overflows
How and where CAN adapters run does not matter!
MachineAPI is always the same!
The dependency Machine is injected in the constructor.
Remove dependency on Machine, if we create ALL models in advance (none on the fly).
Performance penalty!
If we start reimplementing parts of MainView.qml in the test, we are violating the model-view pattern!!!
Create CLI in similar way!
High testability: “built-in” system and component tests based on unit tests
Everything else follows from this!!!
High modularity: Different teams develop different components
High modifiability: Change adapters without rest of system knowing.
Follows open-closed principle
High maintainability: Bugs are local to component.