lecture: Software Architecture --- title: SmartHut.sm --- author: Claudio Maggioni --- # Getting started You will use [Markdown](https://www.markdownguide.org/cheat-sheetplan), [PlantUML](https://plantuml.com/), [architectural decision records](https://github.com/adr/madr), feature models and connector views to describe a software architecture model about your own project. This document will grow during the semester as you sketch and refine your software architecture model. When you are done with each task, please push so we can give you feedback about your work. We begin by selecting a suitable project domain. # Ex - Domain Selection {.instructions Submit the name and brief description (about 100 words) of your domain using the following vision statement template: ``` For [target customers] Who [need/opportunity/problem] The [name your project] Is [type of project] That [major features, core benefits, compelling reason to buy] Unlike [current reality or competitors] Our Project [summarize main advantages over status quo, unique selling point] ``` Please indicate if your choice is: * a project you have worked on in the past (by yourself or with a team) * a project you are going to work on this semester in another lecture (which one?) * a new project you plan to build in the future * some existing open source project you are interested to contribute to The chosen domain should be unique for each student. Please be ready to give a 2 minute presentation about it (you can use one slide but it's not necessary) Hint: to choose a meaningful project look at the rest of the modeling tasks which you are going to perform in the context of your domain. } Project Name: SmartHut.sm Project Type: Website / Web application / IoT Vision Statement:
For:
people owning smart devices or other smart home IoT equipment;
Who:
wish to manage such equipment from a unified web interface and API;
The:
SmartHut.sm application;
Is:
A website and REST API enabled engine;
That:
is able to direcly control smart devices, group them in rooms, configure and apply pre-made settings to groups, and trigger user-defined actions based on user-defined conditions;
Unlike:
Other smart home solutions aims to be free and open source software able to be hosted on commodity hardware owned by the user;
Our project:
Aims to perform most features implemented by competitors and more, allowing for ease of expandability in terms of features and supported devices.
Additional Information: This project is from the Spring semester 2020 edition of _Software Atelier 4: Software engineering project_. The original project does not support any actual smart home device, and instead mocks device state and readouts in its backend. This poses as a potential opportunity to expand the existing architecture of the project to include device support as well. More information on the project can be found [here](https://maggioni.xyz/portfolio/SmartHut.html). Source code of the existing project can be found [here](https://git.maggioni.xyz/SmartHut). # Ex - Architectural Decision Records {.instructions Software architecture is about making design decisions that will impact the quality of the software you plan to build. Let's practice how to describe an architectural decision. We will keep using ADRs to document architectural decisions in the rest of the model. Use the following template to capture one or more architectural design decisions in the context of your project domain Pass: 1 ADR Good: 2 ADR Exceed: >2 ADR } ![Architectural Decision Record 1](./decisions/0001-ui-is-web-app.madr) ![Architectural Decision Record 2](./decisions/0002-use-docker-for-distribution.madr) ![Architectural Decision Record 3](./decisions/0003-use-rdbms-as-databases.madr) # Ex - Quality Attribute Scenario {.instructions 1. Pick a scenario for a specific quality attribute. Describe it with natural language. 2. Refine the scenario using the following structure: ```puml @startuml skinparam componentStyle rectangle skinparam monochrome true skinparam shadowing false rectangle Environment { [Source] -> [System] : Stimulus [System] -> [Measure] : Response } @enduml ``` *Stimulus*: condition affecting the system *Source*: entity generating the stimulus *Environment*: context under which stimulus occurred (e.g., build, test, deployment, startup, normal operation, overload, failure, attack, change) *Response*: observable result of the stimulus *Measure*: benchmark or target value defining a successful response Pass: 3 scenarios Good: >3 scenarios Exceed: >6 scenarios using challenging qualities } ## Scenario \#1: Live *device refresh* parameter Quality: _Configurability_ Scenario: When deploying its own instance of the SmartHut software, the user shall be able to change the refresh timeout for checking device status without restarting the software. ```puml @startuml skinparam componentStyle rectangle skinparam monochrome true skinparam shadowing false rectangle "When user deploys instance of SmartHut" { rectangle "User (Admin of its own instance)" as Source rectangle "System not restarted" as Measure Source -> [System] : "Changes devies refresh timeout" [System] -> [Measure] : "Adapts to new configuration" } @enduml ``` ## Scenario \#2: Time to market for first MVP Quality: _Time to market_ Scenario: The first MVP aimed at gathering feedback from power users shall be shipped in less than 3 months. ```puml @startuml skinparam componentStyle rectangle skinparam monochrome true skinparam shadowing false rectangle "Development" { rectangle "SmartHut team" as Source rectangle "max 3 months" as Measure Source -> [System] : "Develops" [System] -> [Measure] : "first MVP ready" } @enduml ``` ## Scenario \#3: Authorized access to camera live stream Quality: _Authorization_ Scenario: Access to camera live streams shall be granted only to users explicity authorized by the user owning the camera. ```puml @startuml skinparam componentStyle rectangle skinparam monochrome true skinparam shadowing false rectangle "Normal operation" { rectangle "Alice" as Source rectangle "Access granted" as Measure Source -> [System] : "Shares camera live stream with Bob" [System] -> [Measure] : "Answers Bob's request to watch camera live stream" } @enduml ``` ```puml @startuml skinparam componentStyle rectangle skinparam monochrome true skinparam shadowing false rectangle "Normal operation" { rectangle "Alice" as BSource rectangle "Access denied" as BMeasure BSource -> [System] : "Shares camera live stream with Bob" [System] -> [BMeasure] : "Answers Eve's request to watch camera live stream" } @enduml ``` ## Scenario \#4: State change of devices is processed within two seconds Quality: _Performance (latency)_ Scenario: When a user changes the state of a smart device from the UI (e.g. switches on a light bulb), the change shall be applied to the smart device (e.g. light turns on) within 2 seconds ```puml @startuml skinparam componentStyle rectangle skinparam monochrome true skinparam shadowing false rectangle "Normal operation" { rectangle "User" as Source rectangle "max 2 seconds" as Measure Source -> [System] : "Requests device state change from UI" [System] -> [Measure] : "Smart device changes state" } @enduml ``` ## Scenario \#5: First time users are proficient at defining automations Quality: _Usability_ Scenario: During user testing, at least 75% of users, with no prior training, should be able to define a device automation workflow within 20 minutes. ```puml @startuml skinparam componentStyle rectangle skinparam monochrome true skinparam shadowing false rectangle "User testing" { rectangle "Untrained user" as Source rectangle "less than 20 minutes in 75% of cases" as Measure Source -> [System] : "Define device automation flow" [System] -> [Measure] : "Comply to user desire" } @enduml ``` ## Scenario \#6: Docker compatibility Quality: _Portability_ Scenario: Power users shall be able to install their own SmartHut instance if they have a functioning docker and docker-compose installation. ```puml @startuml skinparam componentStyle rectangle skinparam monochrome true skinparam shadowing false rectangle "Deployment (personal instance of power user)" { rectangle "User with docker install" as Source rectangle "Correctly and without additional tweaking" as Measure Source -> [System] : "Installs (via docker-compose specification)" [System] -> [Measure] : "Is initialized and starts" } @enduml ``` ## Scenario \#7: System is resilient w.r.t. unresponsive devices Quality: _Resilience_ Scenario: The SmartHut UI is able to notify if a smart device becomes unresponsive without crashing and within 1 minute from the onset of the malfunction. ```puml @startuml skinparam componentStyle rectangle skinparam monochrome true skinparam shadowing false rectangle "Normal operation" { rectangle "Smart device" as Source rectangle "Without crashing and within 1 minute" as Measure Source -> [System] : "Becomes unresponsive" [System] -> [Measure] : "Copes with failure and notifies user" } @enduml ``` # Ex - Quality Attribute Tradeoff {.instructions Pick a free combination of two qualities on the [map](https://usi365.sharepoint.com/:x:/s/MSDE2023-SoftwareArchitecture/EbK1lRTVOUZJhQoz0XdUBwIBd5vd5yQblOaOwYze4ovbuA?e=6aexs6) and write your name to claim it. Then write a short text giving an example for the tradeoff in this assignment. Pass: 1 unique trade-off Good: 2 trade-offs Exceed: >2 trade-offs } ## Feasibility vs. Configurability Developing an highly configurable product may be beneficial as the product may be easily reusable (e.g. via white labelling), but it may be more expensive or time consuming than building an ad hoc solution. Developing an product with less configuration options may be more feasible w.r.t. deliverability to the first client. However, since configurability is impacted, future requests from that customer or other customers wishing to use the same product may be more costly and/or time consuming. ## Ease of support vs. Portability An highily portable product is inherently more difficult to provide support for due to the possible differences between the supported platforms. In turn, a product with high ease of support may have official documentation procedures and troubleshooting domain knowledge bound to a particular platform only because customers typically use that platform or that platform is the only one officially supported "by contract". ## Performance vs. Modularity An highly modular system may have lower performance than an equivalent monolith implementation due to the cost that the interfaces between microservices pose (e.g., if microservices communicate via HTTP, a monolith may be more performant than the equivalent microservice architecture due to the absence of these calls). Conversely, an highly performant architecture may be optimized in such a way that may impact modularity (e.g. functionally distinct components and abstractions may be combined for the sake of performance, even if the resulting code is less modular and more difficult to maintain - consider C++ friend classes). # Ex - Feature Modeling {.instructions In the context of your chosen project domain, describe your domain using a feature model. The feature model should be correctly visualized using the following template: ![Example Feature Model Diagram](./examples/feature.puml) ![Example Feature Model Diagram](./examples/feature.fml) If possible, make use of all modeling constructs. Pass: Include at least 4 non-trivial features Good: Include at least 6 non-trivial features, which are all implemented by your project Exceed: Include more than 8 non-trivial features, indicate which are found in your project and which belong to one competitor } By "competitor" I choose to consider the existing application as it has been developed for the 2020 edition of SA4, comparing what has already been implemented to what needs to be implemented. Here is a list of features included in the existing application. All other features therefore need to / could be implemented or replace existing features. - User Interface - Web app - Backend - API - REST - WebSocket - Business Logic - Input-output propagation - Sensors - Scenes - Deployment scripts - Docker images - x66-64 linux - docker-compose script - v2.4 ![Feature Model Diagram](./features.fml1) # Ex - Context Diagram {.instructions Prepare a context diagram to define the design boundary for your project. Here is a PlantUML/C4 example to get started. ![Example Context Diagram](./examples/context.puml) Make sure to include all possible user personas and external dependencies you may need. Pass: 1 User and 1 Dependency Good: >1 User and >1 Dependency Exceed: >1 User and >1 Dependency, with both incoming and outgoing dependencies } ![Context Diagram](./context.puml) # Ex - Component Model: Top-Down {.instructions Within the context of your project domain, represent a model of your modular software architecture decomposed into components. The number of components in your logical view should be between 6 and 9: - At least one component should be further decomposed into sub components - At least one component should already exist. You should plan how to reuse it, by locating it in some software repository and including in your model the exact link to its specification and its price. - At least one component should be stateful. The logical view should represent provide/require dependencies that are consistent with the interactions represented in the process view. The process view should illustrate how the proposed decomposition is used to satisfy the main use case given by your domain model. You can add additional process views showing how other use cases can be satisfied by the same set of components. This assignment will focus on modularity-related decisions, we will worry about deployment and the container view later. Here is a PlantUML example logical view and process view. ```puml @startuml skinparam componentStyle rectangle !include title Example Logical View interface " " as MPI interface " " as SRI interface " " as CDI interface " " as PSI [Customer Database <$database{scale=0.33}>] as CDB [Music Player] as MP [User Interface] as UI [Payment Service] as PS [Songs Repository] as SR MP - MPI CDI - CDB SRI -- SR PSI -- PS MPI )- UI UI --( SRI UI -( CDI MP --( SRI CDB --( PSI skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` ```puml @startuml title Example Process View: Buy and Play the Song participant "User Interface" as UI participant "Music Player" as MP participant "Songs Repository" as SR participant "Customer Database" as CDB participant "Payment Service" as PS UI -> SR: Browse Songs UI -> CDB: Buy Song CDB -> PS: Charge Customer UI -> MP: Play Song MP -> SR: Get Music skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` Hint: How to connect sub-components to other external components? Use this pattern. ```puml @startuml component C { component S component S2 S -(0- S2 } interface I S - I component C2 I )- C2 skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` Pass: 6 components (1 decomposed), 1 use case/process view Good: 6 components (1 decomposed), 2 use case/process view Exceed: >6 components (>1 decomposed) and >2 use case/process view } ## Logical View The existing components in in this diagram are all the stateful components. All of them will be instances of the PostgreSQL 15 RDBMS ([sources here](https://github.com/postgres/postgres) and [specification here](https://www.postgresql.org/docs/15/index.html)), a _free as in beer_ and FOSS piece of software. ```puml @startuml skinparam componentStyle rectangle !include title Smarthut.sm Logical View interface " " as DF interface " " as DIF interface " " as TF interface " " as SCF interface " " as UIF [Smart device] as D [Smart sensor] as S [User Interface] as UI component "Users and Devices" as DEV { interface " " as DIDBF [Device Engine] as DI [User and Device DB <$database{scale=0.33}>] as DIDB DIDBF-DIDB DI -( DIDBF } component "Triggers and Automations" as TRIG { interface " " as TDBF [Trigger and Automation Engine] as T [Trigger and Automation DB <$database{scale=0.33}>] as TDB TDBF-TDB T -( TDBF } component "Scenes" as SCENE { interface " " as SCDBF [Scene Engine] as SC [Scene DB <$database{scale=0.33}>] as SCDB SCDBF-SCDB SC -( SCDBF } D--DF UI--UIF DIF--DI TF--T SCF--SC S --( DIF D --( DIF DI --( TF T --( SCF DIF )-- SC DF )-- DI UIF )-- DI UI --( DIF UI --( SCF UI --( TF skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` ## Process Views Use Case: A smart sensor 'S' reports a value which satisfies the condition defined in a trigger. Thanks to an automation specification, this in turn triggers the application of a scene which changes the state of smart device 'D'. ```puml @startuml title Process View: Sensor triggers trigger and scene application participant "Smart device 'D'" as D participant "Smart sensor 'S'" as S participant "User Interface" as UI participant "Device Engine" as DI participant "User and Device DB" as DIDB participant "Trigger and Automation Engine" as T participant "Trigger and Automation DB" as TDB participant "Scene Engine" as SC participant "Scene DB" as SCDB S -> DI: report new value DI -> DIDB: store new value DI -> UI: display new value DI -> T: report latest device state T -> TDB: query for relevant triggers T -> TDB: query for triggered automations T -> TDB: query for scenes included in automation T -> SC: request scene application SC -> SCDB: query for device states to apply SC -> DI: request application of new state to device 'D' DI -> D: apply new state D -> DI: report successful application DI -> DIDB: store new state DI -> UI: display new state skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` Use case: a user alters manually the state of device 'D' in the UI, and the device updates accordingly. Device 'D' is not part of any defined trigger. ```puml @startuml title Process View: UI updates state of device participant "Smart device 'D'" as D participant "Smart sensor 'S'" as S participant "User Interface" as UI participant "Device Engine" as DI participant "User and Device DB" as DIDB participant "Trigger and Automation Engine" as T participant "Trigger and Automation DB" as TDB participant "Scene Engine" as SC participant "Scene DB" as SCDB UI -> DI: request state update DI -> D: apply new state D -> DI: report successful application DI -> DIDB: store new state DI -> UI: display new state DI -> T: report latest device state skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` Use case: a user through the UI defines a new automation, defining a matching new trigger and a new scene for it. ```puml @startuml title Process View: UI defines new scene, trigger and automation participant "Smart device 'D'" as D participant "Smart sensor 'S'" as S participant "User Interface" as UI participant "Device Engine" as DI participant "User and Device DB" as DIDB participant "Trigger and Automation Engine" as T participant "Trigger and Automation DB" as TDB participant "Scene Engine" as SC participant "Scene DB" as SCDB UI -> DI: fetch device registrations UI -> T: fetch defined automations T -> TDB: query automations UI -> T: fetch defined triggers T -> TDB: query triggers UI -> SC: fetch defined scenes SC -> SCDB: query scenes UI -> SC: store new scene SC -> SCDB: store new scene UI -> T: store new trigger T -> TDB: insert new trigger UI -> T: store new automation T -> TDB: insert new automation skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` # Ex - Component Model: Bottom-Up {.instructions Within the context of your project domain, represent a model of your modular software architecture decomposed into components. To design this model you should attempt to buy and reuse as many components as possible. In addition to the logical and process views, you should give a precise list to all sources and prices of the components you have selected to be reused. Write an ADR to document your component selection process (indicating which alternatives were considered). Pass: Existing design with at least 1 reused components (1 Logical View, 1 Process View) Good: Existing design with at least 3 reused components (1 Logical View, 1 Process View, 1 ADR) Exceed: Redesign based on >3 reused components (1 Logical View, >1 Process View, >1 ADR) } ## Component selection process ADRs ![Architectural Decision Record 4](./decisions/0004-homekit-and-mqtt-protocols-smart-dev.madr) ![Architectural Decision Record 5](./decisions/0005-postgres-as-rdbms.madr) ## Chosen components | **Name** | **Version** | **Description** | **Price** | **Sources** | | -------- | ----------- | --------------- | --------- | - | | HAP-Java | 2.0.4 (2022-11-23) | Java implementation of the HomeKit Accessory Protocol | free (MIT license - non copyleft) | https://github.com/hap-java/HAP-Java/tree/hap-2.0.4 | | Eclipse Paho Java MQTT client | 1.2.5 SR (2020-07-15) | Java client for the MQTT protocol | free (EPL 2.0 license - non copyleft) | https://github.com/eclipse/paho.mqtt.java/tree/v1.2.5 | | PostgreSQL | 15.2 (2023-02-06) | Relational DBMS | free (PostgreSQL license - non copyleft) | https://github.com/postgres/postgres/tree/REL_15_2 | ## Logical View ```puml @startuml skinparam componentStyle rectangle !include title Smarthut.sm Logical View interface " " as DIF interface " " as TF interface " " as SCF component "IoT Devices" as IOT { interface " " as HDF interface " " as HSF interface " " as ZDF interface " " as ZSF [HomeKit smart device] as HD <> [HomeKit smart sensor] as HS <> [MQTT smart device] as ZD <> [MQTT smart sensor] as ZS <> HD--HDF HS--HSF ZD--ZDF ZS--ZSF } component "User Interface" as UIC { interface " " as WSCF [User Interface] as UI [WebSockets API (JavaScript)] <> as WSC WSCF)--UI WSC--WSCF } component "Users and Devices" as DEV { interface " " as DIDBF interface " " as HAPF interface " " as ZIGF interface " " as WSSF [HAP-java] as HAP <> [paho.mqtt.java] as ZIG <> [Device Engine] as DI note left of DI: Already implemented using Spring Boot [javax.websocket API] as WSS <> [User and Device DB <$database{scale=0.33}> (PostgreSQL)] <> as DIDB HDF )-- HAP HSF )-- HAP ZDF )-- ZIG ZSF )-- ZIG DI--(WSSF WSSF--WSS HAP--HAPF ZIG--ZIGF HAPF )-- DI ZIGF )-- DI DIDBF-DIDB DI -( DIDBF } component "Triggers and Automations" as TRIG { interface " " as TDBF [Trigger and Automation Engine] as T [Trigger and Automation DB <$database{scale=0.33}> (PostgreSQL)] <> as TDB TDBF-TDB T -( TDBF } component "Scenes" as SCENE { interface " " as SCDBF [Scene Engine] as SC [Scene DB <$database{scale=0.33}> (PostgreSQL)] <> as SCDB SCDBF-SCDB SC -( SCDBF } DI--DIF TF--T SCF--SC WSS <..> WSC: WebSocket protocol (RFC 6455) DI --( TF T --( SCF DIF )-- SC DIF )-- UI UI --( SCF UI --( TF skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` ## Process Views Use Case: An homekit smart sensor 'HS' and a MQTT smart sensor 'MS' report values which satisfy the condition defined in a trigger. Thanks to an automation specification, this in turn triggers the application of a scene which changes the state of homekit smart device 'D'. ```puml @startuml title Process View: Sensor triggers trigger and scene application box "IoT Devices" participant "HomeKit device 'D'" as HD participant "HomeKit sensor 'HS'" as HS participant "MQTT sensor 'MS'" as ZS end box box "User Interface" participant "UI" as UI participant "WebSockets API (JS)" as WSC end box box "Users and Devices" participant "javax.websocket" as WSS participant "HAP-java" as HAP participant "paho.mqtt.java" as ZIG participant "Engine" as DI participant "DB" as DIDB end box box "Triggers and Automations" participant "Engine" as T participant "DB" as TDB end box box "Scenes" participant "Engine" as SC participant "DB" as SCDB end box HS -> HAP: Homekit protocol\n(report new temperature value) HAP -> DI: TemperatureSensorAccessory.getCurrentTemperature() Future resolves\n(report new temperature value from 'HS') ZS -> ZIG: MQTT PUBLISH\n(report new temperature value) ZIG -> DI: subscriber.subscribe(...) observer called\n(report new temperature value from 'MS') DI -> DIDB: JDBC transaction (UPDATE)\n(store new states) DI -> WSS: s.getBasicRemote().sendText(...)\n(send new device states) WSS <- WSC: WebSocket protocol WSC -> UI: connection.onmessage called\n(new device states received, updates UI) DI -> T: report latest device states T -> TDB: prepared SELECT statement\n(query for relevant triggers) T -> TDB: prepared SELECT statement\n(query for triggered automations) T -> TDB: prepared SELECT statement\n(query for scenes included in automation) T -> SC: request scene application SC -> SCDB: prepared SELECT statement\n(query for device states to apply) SC -> DI: HTTP 1.1 PUT /devices/{id}/state\n(apply new state to device 'D') DI -> HAP: LightbulbAccessory.setLightbulbPowerState(...)\n(apply new state to device 'D') HAP -> HD: Homekit protocol\n(apply new state) HD -> HAP: Homekit protocol\n(report successful application) HAP -> DI: LightbulbAccessory.setLightbulbPowerState(...) Future completes\n(report successful application) DI -> DIDB: JDBC transaction (UPDATE)\n(store new state) DI -> WSS: s.getBasicRemote().sendText(...)\n(send new device state) WSS <- WSC: WebSocket protocol WSC -> UI: connection.onmessage called\n(new device state received, updates UI) skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` Use case: a user alters manually the state of device 'D' in the UI, and the device updates accordingly. Device 'D' is not part of any defined trigger. ```puml @startuml title Process View: UI updates state of device box "IoT Devices" participant "MQTT device 'D'" as ZD end box box "User Interface" participant "UI" as UI participant "WebSockets API (JS)" as WSC end box box "Users and Devices" participant "javax.websocket" as WSS participant "HAP-java" as HAP participant "paho.mqtt.java" as ZIG participant "Engine" as DI participant "DB" as DIDB end box box "Triggers and Automations" participant "Engine" as T participant "DB" as TDB end box box "Scenes" participant "Engine" as SC participant "DB" as SCDB end box UI -> DI: HTTP 1.1 PUT /devices/{id}/state\n(update state) DI -> ZIG: client.publish(\n TOPIC,\n new MqttMessage(...)\n);\n(request state update) ZIG -> ZD: MQTT PUBLISH\n(state update request) ZD -> ZIG: MQTT PUBLISH\n(new state) ZIG -> DI: subscriber.subscribe(...) observer called \n(successful application) DI -> DIDB: JDBC transaction (UPDATE)\n(store new state) DI -> WSS: s.getBasicRemote().sendText(...)\n(send new device state) WSS <--> WSC: WebSocket protocol WSC -> UI: connection.onmessage called\n(new device state received, updates UI) DI -> T: report latest device state skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` # Ex - Interface/API Specification {.instructions In this iteration, we will detail your previous model to specify the provided interface of all components based on their interactions found in your existing process views. 1. choose whether to use the top down or bottom up model. If you specify the interfaces of the bottom up model, your interface descriptions should match what the components you reuse already offer. 2. decide which interface elements are operations, properties, or events. Get started with one of these PlantUML templates, or you can come up with your own notation to describe the interfaces, as long as it includes all the necessary details. The first template describes separately the provided/required interfaces of each component. ![Separate Required/Provided Interfaces](./examples/interface1.puml) The second template annotates the logical view with the interface descriptions: less redundant, but needs the logical dependencies to be modeled to show which are the required interfaces. ![Shared Interfaces](./examples/interface2.puml) Pass: define interfaces of all outer-level components Good: Define interfaces of all outer-level components. Does your architecture publish a Web API? If not, extend it so that it does. Exceed: Also, document the Web API using the OpenAPI language. You can use the [OpenAPI-to-Tree](http://api-ace.inf.usi.ch/openapi-to-tree/) tool to visualize the structure of your OpenAPI description. } To build the interface diagram, I choose to use the top-down model as it provides a simple view with the integration between SmartHut and IoT devices. Interface directions are slightly revised between the devices and the "Users and Devices" components to better suit the operations / events / properties notation. As already discussed in class, the three "backend" components "Users and Devices", "Triggers and Automations" and "Scenes" each implement a RESTful Web API. The diagram includes each endpoint found in the OpenAPI specification of the project. A copy of the JSON OpenAPI V2 specification can be found in the file `openapi-spec.json` located in the directory where the specification is located. As the **OpenAPI-to-Tree** tool appears to be down at the moment (HTTP 502 when trying to access the page) I provide a PDF export of the Swagger frontend generated by the specification in the file `openapi-spec.pdf`located in the directory where the specification is located. ```puml @startuml skinparam componentStyle rectangle !include interface " " as IOTDF interface " " as IOTSF interface " " as DEVF interface " " as TRIGF interface " " as SCF interface " " as UICF [Smart Device] as IOTD [Smart Sensor] as IOTS [User Interface] as UIC [Users and Devices] as DEV [Triggers and Automations] as TRIG [Scenes] as SC IOTD--IOTDF IOTS--IOTSF DEVF--DEV TRIGF--TRIG SCF--SC UIC--UICF DEV--(TRIGF TRIG--(SCF DEVF)--UIC UIC--(TRIGF UIC--(SCF DEVF)--SC IOTDF)--DEV IOTSF)--DEV UICF)--DEV note left of IOTDF operation: .. setState(state) checkAlive() -- events: .. state_changed end note note right of IOTSF operation: .. checkAlive() -- events: .. state_changed end note note right of UICF operation: .. updateState(state) end note note left of DEVF operation: .. POST /auth/login GET /auth/profile POST /buttonDimmer PUT /buttonDimmer/dim GET /buttonDimmer/{id} DELETE /buttonDimmer/{id} POST /buttonDimmer/{id}/lights DELETE /buttonDimmer/{id}/lights POST /curtains PUT /curtains DELETE /curtains/{id} POST /curtains/{id}/state GET /device PUT /device POST /dimmableLight PUT /dimmableLight DELETE /dimmableLight/{id} POST /dimmableLight/{id}/state PUT /dimmableState DELETE /dimmableState/{id} GET /user GET /user/guests PUT /user/guests GET /user/hosts PUT /user/permissions POST /knobDimmer PUT /knobDimmer/dimTo GET /knobDimmer/{id} DELETE /knobDimmer/{id} POST /knobDimmer/{id}/lights DELETE /knobDimmer/{id}/lights POST /motionSensor DELETE /motionSensor/{id} PUT /motionSensor/{id}/detect POST /rangeCondition PUT /rangeCondition GET /rangeCondition/{automationId} DELETE /rangeCondition/{id} POST /rangeTrigger PUT /rangeTrigger GET /rangeTrigger/{automationId} DELETE /rangeTrigger/{id} GET /regularLight POST /regularLight PUT /regularLight GET /regularLight/{id} DELETE /regularLight/{id} POST /regularLight/{id}/state GET /room POST /room GET /room/{id} PUT /room/{id} DELETE /room/{id} GET /room/{roomId}/devices POST /securityCamera PUT /securityCamera DELETE /securityCamera/{id} POST /securityCamera/{id}/state POST /sensor DELETE /sensor/{id} PUT /sensor/{id}/simulation PUT /sensor/{id}/value POST /smartPlug PUT /smartPlug DELETE /smartPlug/{id} DELETE /smartPlug/{id}/meter POST /smartPlug/{id}/state POST /switch PUT /switch/operate GET /switch/{id} DELETE /switch/{id} POST /switch/{id}/lights DELETE /switch/{id}/lights PUT /switchableState DELETE /switchableState/{id} POST /thermostat PUT /thermostat DELETE /thermostat/{id} POST /thermostat/{id}/state POST /register GET /register/confirm-account POST /register/init-reset-password PUT /register/reset-password end note note left of TRIGF operation: .. GET /automation POST /automation PUT /automation PUT /automation/fast GET /automation/{id} DELETE /automation/{id} POST /booleanCondition PUT /booleanCondition GET /booleanCondition/{automationId} DELETE /booleanCondition/{id} POST /booleanTrigger PUT /booleanTrigger GET /booleanTrigger/{automationId} DELETE /booleanTrigger/{id} POST /thermostatCondition PUT /thermostatCondition GET /thermostatCondition/{automationId} DELETE /thermostatCondition/{id} end note note left of SCF operation: .. GET /scene POST /scene PUT /scene/{id} DELETE /scene/{id} POST /scene/{id}/apply POST /scene/{id}/copyFrom/{copyId} GET /scene/{sceneId}/states POST /scenePriority PUT /scenePriority GET /scenePriority/{automationId} DELETE /scenePriority/{id} end note skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` # Ex - Connector View {.instructions Extend your existing models introducing the connector view For every pair of connected components (logical view), pick the most suitable connector. Existing components can play the role of connector, or new connectors may need to be introduced. ![Example Connector View Diagram](./examples/connector-view.c5) Make sure that the interactions shown in the process views reflect the primitives of the selected connector Pass: model existing connectors based on previous model decisions Good: model existing connectors based on previous model decisions, write an ADR about the choice of one connector Exceed: introduce a new type of connector and update your existing process view (sequence diagram) to show the connector primitives in action } I choose to define the MQTT protocol as a special kind of connector to better highlight its resilient capabilities, like the support for 3 different levels of message delivery reliability and persistent sessions. In the diagram the connector is represented with an abuse of notation, namely by introducing a box labeled "MQTT". Flow of messages is bidirectional for devices and from device to engine only for sensors. Note that the stream between an HomeKit device and the device engine is bidirectional even if the diagram does not show it. ![Connector View Diagram](./connector-view.c5) ![Architectural Decision Record](./decisions/0006-web-connector-between-engines.madr) ### Process View Here is the updated process view to show the primitives of the MQTT connector. Use case: a user alters manually the state of device 'D' in the UI, and the device updates accordingly. Device 'D' is not part of any defined trigger. ```puml @startuml title Process View: UI updates state of device box "IoT Devices" participant "MQTT device 'D'" as ZD end box box "User Interface" participant "UI" as UI end box box "Users and Devices" participant "paho.mqtt.java" as ZIG participant "Engine" as DI end box box "Triggers and Automations" participant "Engine" as T end box DI -> ZIG: client.connect(...) ZIG -> ZD: CONNECT reliability=2 ZD -> ZIG: CONNACK ZD -> ZIG: SUBSCRIBE "state-cmd" ZIG -> ZD: MQTT ACK ZIG -> ZD: SUBSCRIBE "state-update" ZD -> ZIG: MQTT ACK UI -> DI: HTTP 1.1 PUT /devices/{id}/state\n(update state) DI -> ZIG: client.publish(\n TOPIC,\n new MqttMessage(...)\n);\n(request state update) ZIG -> ZD: MQTT PUBLISH "state-cmd"\n(state update request) ZIG -> ZD: MQTT PUBLISH "state-cmd"\n(resending, no ack received) ZD -> ZIG: MQTT ACK ZD -> ZIG: MQTT PUBLISH "state-update"\n(new state) ZIG -> ZD: MQTT ACK ZIG -> DI: subscriber.subscribe(...) observer called \n(successful application) DI -> UI: s.getBasicRemote().sendText(...)\n(send new device state) DI -> T: report latest device state ZD -> ZIG: MQTT PUBLISH\n(new state, repeated message, ignoring) skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` # Ex - Adapters and Coupling {.instructions 1. Highlight the connectors (or components) in your existing bottom-up design playing the role of adapter. (We suggest to use the bottom-up design since when dealing with externally sourced components, their interfaces can be a source of mismatches). 2. Which kind of mismatch** are they solving? 3. Introduce a wrapper in your architecture to hide one of the previously highlighted adapters 4. Where would standard interfaces play a role in your architecture? Which standards could be relevant in your domain? 5. Explain how one or more pairs of components are coupled according to different coupling facets 6. Provide more details on how each adapter solves the mismatches identified using pseudo-code or the actual code 7. How can you improve your architectural model to minimize coupling between components? (Include a revised logical/connector view with your solution) Pass: 1-5 (with one adapter) Good: 1-6 (with at least two adapters) Exceed: 1-7 (with at least two adapters) ** If you do not find any mismatch in your existing design we suggest to introduce one artificially. ## Hints * (1) Should we find cases where two components cannot communicate (and are doing it wrongly) and highlight they would need an adapter?, or cases where we have already a "component playing the role of adapter in the view" and highlight only the adapter? *Both are fine. We assumed that if you draw a dependency (or a connector) the interfaces match, but if you detect that the components that should communicate cannot communicate then of course introduce an adapter to solve the mismatch* * (2) Please show the details about the two interfaces which do not match (e.g., names of parameters, object structures) so that it becomes clear why an adapter is needed and what the adapter should do to bridge the mismatch * (5-6) These questions are about the implications on coupling based on the decisions you documented in the connector view. Whenever you have a connector you couple together the components and different connectors will have different forms of coupling For example, if you use calls everywhere, do you really need them everywhere? is there some pair of components where you could use a message queue instead? Regarding the coupling facets mentioned in question 5. You do not have to answer all questions related to "discovery", "session", "binding", "interaction", "timing", "interface" and "platform" (p.441, Coupling Facets). Just the ones that you think are relevant for your design and by answering them you can get ideas on how to do question 6. } 1. The smart device protocol libraries `HAP-Java` and `paho.mqtt.java` play the role of adapters in the diagram of my architecture. The two components are highlighted in the diagram like this. 2. The mismatch these adapters solve is between the state logic of the devices as handled by the SmartHut Users and Devices component and the way these states are represented and triggered in the MQTT and HomeKit protocol. The two protocol libraries act as a translation layer between the Java runtime of the Users and Devices component and the communication stack required by both protocols. 3. The `Homekit Device Wrapper` and `MQTT Device Wrapper` have been introduced in the diagram. 4. As SmartHut should handle by design different IoT protocols, no standard interfaces could play a role in the architecture. The wrappers (and in particular the _SmartHut to \[library\]_) adapters) I introduced aim to create a common ground between IoT protocols to handle the devices in a standardized fashion whitin SmartHut. 5. One important coupling facet decision between in the architecture is the discovery coupling between the web "engine" components. When deploying, I either aim to have a static configuration file where all Web Interface root endpoints are enumerated (one per component) to allow both a monolith and polylith installation of SmartHut according to the user's needs (an example of a project that allows this kind of decision at deployment is the Matrix protocol implementation [Dendrite](https://github.com/matrix-org/dendrite)). 6. Here I provide examples of how the adapters in the MQTT Wrapper and Homekit wrapper adapt the communication between the SmartHut Users and Devices engine component and the respective protocols. I envision a common interface specification for both protocols, enumerating the actions of each smart device. Here's an example for a smart light with only on/off capabilities: ```java interface Light { Future getStatus(); Future setStatus(boolean turnOn); } ``` Then, here's a high level implementation for the MQTT protocol, which is code to be included in the SmartHut to paho.mqtt.java adapter: ```java class MQTTLight extends Light { private final MqttClient client; private final String deviceTopic; public MQTTLight(@Inject MqttClient client, String deviceTopic) { this.client = client; this.deviceTopic = deviceTopic; } @Override public Future getStatus() { return client.subscribe(deviceTopic /* get status message */); } @Override public Future setStatus(boolean turnOn) { return client.publish(deviceTopic, new MqttMessage(turnOn)); } } ``` and here's a high level implementation for the Homekit protocol, to be included in the SmartHut to HAP-java adapter: ```java class HAPLight extends Light { private final LightBulbAccessory accessory; public HAPLight(LightBulbAccessory accessory) { this.accessory = accessory; } public static HAPLight discover(String deviceId) { /** here create the lightbulb accessory object from the given device id, call constructor */ } @Override public Future getStatus() { return accessory.getLightbulbPowerState(); } @Override public Future setStatus(boolean turnOn) { return accessory.setLightBulbPowerState(turnOn); } } ``` 7. Coupling between the engine components of Smarthut would be to introduce Zeroconf clients and servers in all engine components and allow auto-discovery in an initial setup phase, which would delay discovery coupling at runtime an allow for more flexible deployments. However, this would slightly complicate the implementation of the frontend as UDP/broadcast connections are harder to implement in a web-based interface. ```puml @startuml skinparam componentStyle rectangle !include title Smarthut.sm Logical View interface " " as DIF interface " " as TF interface " " as SCF interface " " as SHAF interface " " as SZAF component "Homekit Device Wrapper" as IOTHK { interface " " as HDF interface " " as HSF interface " " as HAPF [HomeKit smart device] as HD <> [HomeKit smart sensor] as HS <> [HAP-java] as HAP <> #CCCCCC [SmartHut to HAP-java adapter] as SHA HAP--HAPF HD--HDF HS--HSF HAPF)--SHA SHA--SHAF } component "MQTT Device Wrapper" as IOTMQ { interface " " as ZDF interface " " as ZSF interface " " as ZIGF [MQTT smart device] as ZD <> [MQTT smart sensor] as ZS <> [paho.mqtt.java] as ZIG <> #CCCCCC [SmartHut to paho.mqtt.java adapter] as SZA ZD--ZDF ZS--ZSF ZIG--ZIGF SZA--SZAF ZIGF)--SZA } component "User Interface" as UIC { interface " " as WSCF [User Interface] as UI [WebSockets API (JavaScript)] <> as WSC WSCF)--UI WSC--WSCF } component "Users and Devices" as DEV { interface " " as DIDBF interface " " as WSSF [Device Engine] as DI note left of DI: Already implemented using Spring Boot [javax.websocket API] as WSS <> [User and Device DB <$database{scale=0.33}> (PostgreSQL)] <> as DIDB HDF )-- HAP HSF )-- HAP ZDF )-- ZIG ZSF )-- ZIG DI--(WSSF WSSF--WSS SHAF )-- DI SZAF )-- DI DIDBF-DIDB DI -( DIDBF } component "Triggers and Automations" as TRIG { interface " " as TDBF [Trigger and Automation Engine] as T [Trigger and Automation DB <$database{scale=0.33}> (PostgreSQL)] <> as TDB TDBF-TDB T -( TDBF } component "Scenes" as SCENE { interface " " as SCDBF [Scene Engine] as SC [Scene DB <$database{scale=0.33}> (PostgreSQL)] <> as SCDB SCDBF-SCDB SC -( SCDBF } DI--DIF TF--T SCF--SC WSS <..> WSC: WebSocket protocol (RFC 6455) DI --( TF T --( SCF DIF )-- SC DIF )-- UI UI --( SCF UI --( TF skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` # Ex - Physical and Deployment Views {.instructions a. Extend your architectural model with the following viewpoints: 1. Physical or Container View 2. Deployment View Your model should be non-trivial: include more than one physical device/virtual container (or both). Be ready to discuss which connectors are found at the device/container boundaries. b. Write an ADR about which deployment strategy you plan to adopt. The alternatives to be considered are: big bang, blue/green, shadow, pilot, gradual phase-in, canary, A/B testing. c. (Optional) Prepare a demo of a basic continuous integration and delivery pipeline for your architectural documentation so that you can obtain a single, integrated PDF with all the viewpoints you have modeled so far. For example: - configure a GitHub webhook to be called whenever you push changes to your documentation - setup a GitHub action (or similar) to build and publish your documentation on a website Pass: 1 physical view, 1 deployment view, 1 ADR (b.) Good: >1 physical view, >1 deployment view, 1 ADR (b.) Exceed: 1 physical view, 1 deployment view, 1 ADR (b.) + 1 demo (c.) } ### Container View ```puml @startuml !include Person(user, "User") System_Boundary(system, "SmartHut") { ContainerDb(udb, "Users and Devices DB", "PostgreSQL") ContainerDb(tdb, "Triggers and Automations DB", "PostgreSQL") ContainerDb(sdb, "Scenes DB", "PostgreSQL") Container(ui, "User Interface", "React Javascript + static HTTP server") Container(uw, "Users and Devices Engine", "Spring Java") Container(tw, "Triggers and Automations Engine", "Spring Java") Container(sw, "Scenes Engine", "Spring Java") } System_Ext(msd, "Smart Device", "MQTT compatible") System_Ext(mss, "Smart Sensor", "MQTT compatible") System_Ext(hsd, "Smart Device", "Homekit compatible") System_Ext(hss, "Smart Sensor", "Homekit compatible") Rel(uw, hsd, "Connects to", "Homekit protocol") Rel(uw, hss, "Connects to", "Homekit protocol") Rel(uw, msd, "Connects to", "MQTT protocol") Rel(uw, mss, "Connects to", "MQTT protocol") Rel(user, ui, "Interact with") Rel(uw, udb, "Reads/Writes", "JDBC") Rel(tw, tdb, "Reads/Writes", "JDBC") Rel(sw, sdb, "Reads/Writes", "JDBC") Rel(uw, tw, "Uses", "HTTP requests") Rel(tw, sw, "Uses", "HTTP requests") Rel(sw, uw, "Uses", "HTTP requests") Rel(ui, tw, "Uses", "HTTP requests") Rel(ui, sw, "Uses", "HTTP requests") Rel(ui, uw, "Uses", "HTTP requests") @enduml ``` ### Deployment View ```puml @startuml title SmartHut Deployment View database "Users and Devices" { [Engine] as UW [DB] as UDB UW - UDB } database "Triggers and Automations" { [Engine] as TW [DB] as TDB TW - TDB } database "Scenes" { [Engine] as SW [DB] as SDB SW - SDB } UW -- TW: HTTPS TW -- SW: HTTPS UW -- SW: HTTPS node "Device" { [Web Based User Interface] as UI } cloud "User owned smart devices" { [Homekit Smart Device] as HD [Homekit Smart Sensor] as HS [MQTT Smart Device] as MD [MQTT Smart Sensor] as MS } cloud "User Interface CDN" { [User Interface Web Server] as CDN } UW -- HD: Homekit protocol UW -- HS: Homekit protocol UW -- MD: MQTT UW -- MS: MQTT UI -- CDN: HTTPS UI -- UW: HTTPS UI -- TW: HTTPS UI -- SW: HTTPS skinparam monochrome true skinparam shadowing false skinparam defaultFontName Courier @enduml ``` ![Architectural Decision Record](./decisions/0007-canary-release.madr) ## CI/CD pipeline A proof-of-concept CI/CD pipeline was implemented using this very documentation repository and this [GitHub Actions tutorial](https://github.com/marketplace/actions/deploy-to-github-pages). The YAML Github Actions pipeline configurations is the following: ```yml name: Build and Deploy on: [push] permissions: contents: write jobs: build-and-deploy: concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession. runs-on: ubuntu-latest steps: - name: Checkout 🛎️ uses: actions/checkout@v3 - name: Install and Build run: | sudo apt install graphviz yarn install yarn build - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@v4 with: folder: upload/sa/model # The folder the action should deploy. ``` Therefore, this documentation website is also available in [Github Pages](https://usi-msde-sa-2023.github.io/sa--maggicl/). # Ex - Availability and Services {.instructions The goal of this week is to plan how to deliver your software as a service with high availability. 1. If necessary, change your deployment design so that your software is hosted on a server (which could be running as a Cloud VM). Your SaaS architecture should show how your SaaS can be remotely accessed from a client such as a Web browser, or a mobile app 2. Sketch your software as a service pricing model (optional) 3. How would you define the availability requirements in your project domain? For example, what would be your expectation for the duration of planned/unplanned downtimes or the longest response time tolerated by your clients? 4. Which strategy do you adopt to monitor your service's availability? Extend your architecture with a watchdog or a heartbeat monitor and motivate your choice with an ADR. 5. What happens when a stateless component goes down? model a sequence diagram to show what needs to happen to recover one of your critical stateless components 6. How do you plan to recover stateful components? write an ADR about your choice of replication strategy and whether you prefer consistency vs. availability. Also, consider whether event sourcing would help in your context. 7. How do you plan to avoid cascading failures? Be ready to discuss how the connectors (modeled in your connector view) impact the reliability of your architecture. 8. How did you mitigate the impact of your external dependencies being not available? (if applicable) Pass: 1, 3, 4, one of: 5, 6, 7, 8 Good: 1, 2, 3, 4, two of: 5, 6, 7, 8 Exceed: 1, 2, 3, 4, 5, 6, 7, 8 } # Ex - Flexibility {.instructions Only dead software stops changing. You just received a message from your customer, they have an idea. Is your architecture ready for it? 1. Pick a new use case scenario. Precisely, what exactly do you need to change of your existing architecture so that it can be supported? Model the updated logical/process/deployment views. 2. Pick another use case scenario so that it can be supported without any major architectural change (i.e., while you cannot add new components, it is possible to extend the interface of existing ones or introduce new dependencies). Illustrate with a process view, how your previous design can satisfy the new requirement. 3. Change impact. One of your externally sourced component/Web service API has announced it will introduce a breaking change. What is the impact of such change? How can you control and limit the impact of such change? Update your logical view 4. Open up your architecture so that it can be extended with plugins by its end-users. Where would be a good extension point? Update your logical view and give at least one example of what a plugin would actually do. 5. Assuming you have a centralized deployment with all stateful components storing their state in the same database, propose a strategy to split the monolith into at least two different microservices. Model the new logical/deployment view as well as the interfaces of each microservice you introduce. Pass: 1, one out of 2-5. Good: 1, two out of 2-5. Exceed: 1-5. }