Applying SOLID Principles in Fuuz

Applying SOLID Principles in Fuuz

Applying SOLID Principles in Fuuz

Article Type: Concept
Audience: Solution Architects, Solution Engineers, Project Managers, System Administrators
Module: Platform Architecture / Development Best Practices
Applies to Versions: All Versions

1. Overview

SOLID is an acronym for five foundational software design principles — Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion — originally formulated for object-oriented programming. When applied to the Fuuz Industrial Operations Platform, these principles translate directly into architectural guidance for how to design data models, build data flows, compose screens, and package applications. Following SOLID practices in Fuuz results in applications that are easier to maintain, safer to extend, and more resilient to changing business requirements.

Fuuz applications are composed of three primary layers — Data Models, Data Flows, and Screens — all organized within Modules and ModuleGroups and distributed as .fuuz packages. Each of these layers presents opportunities to either violate or enforce SOLID principles. A flow that does too many things at once, a data model that conflates unrelated concerns, or a screen that mixes roles are all common patterns that SOLID helps to prevent. This article walks through each principle with concrete Fuuz examples and anti-patterns to avoid.

This article is intended for Solution Architects designing application structure, Solution Engineers implementing flows and screens, and Project Managers evaluating code quality and maintainability standards. It assumes familiarity with core Fuuz concepts including Modules, Flows, Screens, GraphQL queries, Connectors, and Sequences.

Note — Best Practice Guidance: The patterns described in this article are recommendations, not platform requirements. Fuuz does not enforce SOLID principles at a technical level — you can build fully functional applications without following them. These guidelines exist to help teams build applications that are easier to maintain, extend, and scale over time. Apply them proportionally to the complexity and longevity of your application. For short-lived prototypes or simple utility flows, strict adherence may add unnecessary overhead.

2. Architecture & Data Flow

Definitions

  • Single Responsibility Principle (SRP): Every artifact — flow, model, screen — should have one, and only one, reason to change. It should own exactly one concern.
  • Open/Closed Principle (OCP): Artifacts should be open for extension but closed for modification. New functionality should be added by extending existing components, not by rewriting them.
  • Liskov Substitution Principle (LSP): Subtypes or variants must be interchangeable with their base type without breaking the system. In Fuuz, this applies to model variants, sub-flow contracts, and typed records.
  • Interface Segregation Principle (ISP): No consumer should be forced to depend on data or structure it does not use. Flows, queries, and screens should have lean, purpose-built input and output contracts.
  • Dependency Inversion Principle (DIP): High-level logic should not depend on concrete implementations. Flows should depend on named abstractions — Connectors, Sequences, Saved Queries — rather than hardcoded endpoints or logic.
  • Module: A functional unit within a ModuleGroup containing data models, flows, and screens for a specific domain area.
  • ModuleGroup: The top-level application container in Fuuz, grouping one or more modules into a deployable application.
  • Connector: A platform-managed abstraction for external system credentials and connection details, referenced by name in flows.
  • Sequence: A platform-managed counter used to generate human-readable, ordered identifiers without embedding logic in flows.
  • Saved Query: A named, reusable GraphQL query stored in the platform and referenced by flows or screens by name rather than embedding raw GraphQL everywhere.
  • setContext / mergeContext: Flow nodes used to store shared state. setContext replaces the context object; mergeContext adds to it. Used to externalize configuration from core logic.

Components

  • Data Models: Schema definitions for application entities. SOLID governs how concerns are separated across models and how model variants maintain consistent contracts.
  • Data Flows: Server-side logic components. SOLID governs flow decomposition, context management, sub-flow contracts, and external dependency handling.
  • Screens: User interface components. SOLID governs role separation, query scope, and configuration-driven behavior.
  • Packages (.fuuz files): Deployment units. SOLID governs module boundaries and extension patterns.
  • Connectors: Abstraction layer for external integrations. Central to the Dependency Inversion Principle.
  • Sequences: Platform-managed identifier generators. Abstract numbering logic away from flows.

SOLID-to-Fuuz Layer Mapping

Each SOLID principle maps primarily to one or more Fuuz layers:

PrinciplePrimary Fuuz LayerKey Mechanism
Single ResponsibilityFlows, Models, ScreensFlow decomposition, model domain separation
Open/ClosedPackages, Flows, ScreensModule extension, setContext, $appConfig
Liskov SubstitutionModels, Sub-flowsConsistent base fields, uniform sub-flow contracts
Interface SegregationGraphQL Queries, Flows, ScreensLean queries, minimal payload contracts, role screens
Dependency InversionConnectors, Sequences, Saved QueriesNamed abstractions, no hardcoded credentials or endpoints

3. Use Cases

OEE Calculation Flow Decomposition (SRP): A manufacturing application needs to calculate OEE, send downtime alerts, and update work order status. Instead of building one large flow, the team creates three separate flows: calculateOeeHourly, notifyDowntimeAlert, and updateWorkOrderStatus. Each flow has a single reason to change — if the OEE formula changes, only calculateOeeHourly is touched.

Extending an Application Without Modifying the Base (OCP): A base Quality module handles inspection records. A new compliance requirement demands digital signature tracking. Rather than modifying the base Quality module, a new Compliance Signatures module is added to the ModuleGroup, extending the application without touching proven, deployed code.

Multi-Plant Deployment with Configurable Thresholds (OCP): An OEE alert flow reads its downtime threshold and notification group directly from $appConfig wherever needed. No flow modification or cloning is required when a second plant has different thresholds — only the app configuration changes.

Runtime Context Consolidation (OCP): A production event flow needs the current timestamp, the workcenter type, and a derived night shift flag across multiple downstream nodes. A setContext node at the top computes all three once from the incoming payload. When the night shift boundary rule changes, only the setContext node is updated — every downstream node referencing $state.context.isNightShift automatically reflects the new logic without modification.

Uniform Workcenter Type Handling (LSP): A dashboard displays current status for all workcenters, regardless of type (Machine, Assembly Line, Clean Room). Because all workcenter type variants share the same base fields (name, status, currentShift, oeeTarget), the dashboard query and screen work uniformly across all types without special-casing.

Interchangeable Notification Sub-flows (LSP): A parent flow routes alerts to different channels depending on severity — email for warnings, SMS for critical. Both sendEmailNotification and sendSmsNotification sub-flows accept the same context shape (message, severity, recipientGroupId) and return the same output structure. The parent flow is agnostic to which handler it calls.

Lean GraphQL Queries on a Production Screen (ISP): A production entry screen only needs id, workOrderNumber, and targetQuantity from the WorkOrder model. Instead of fetching all 25 fields on the model, the screen query requests only those three. This reduces payload size, speeds up rendering, and decouples the screen from unrelated model changes.

Role-Specific Screen Design (ISP): Operators need a simple production entry screen with 5 fields. Supervisors need a review screen with production history, defect analysis, and approval workflows. Rather than building one screen with 30 fields and hiding half by role, two distinct screens are built — each focused on its user's task.

ERP Integration via Named Connector (DIP): A flow syncs work orders from Plex. The flow references the Connector named PlexProduction — not a hardcoded URL or credentials. When the Plex environment changes from sandbox to production, only the Connector configuration is updated. No flow logic changes.

Document Numbering via Sequence (DIP): Work order numbers are generated using a platform Sequence named WorkOrderNumber. The flow calls the Sequence by name and receives the next value. If the numbering format or starting point changes, the Sequence configuration is updated — the flow is untouched.

Centralized Data Access via Saved Queries (DIP): Three different flows need to look up active work orders by workcenter. Instead of embedding the same GraphQL query in all three flows, a Saved Query named ActiveWorkOrdersByWorkcenter is created. All three flows reference it by name. When the WorkOrder model adds a new filter field, only the Saved Query is updated.

Screen Configuration via $appConfig (OCP + DIP): A dashboard displays status colors based on OEE thresholds. Colors and thresholds are stored in $appConfig rather than hardcoded in screen bindings. When the customer changes their KPI targets, only the app configuration is updated — the screen logic is unchanged and no redeployment is required.

4. Screen Details

SOLID principles influence screen design at multiple levels: how screens are scoped, how they consume data, how they handle configuration, and how they serve different user roles. The following guidance applies to the Fuuz Screen Designer.

Single Responsibility in Screens

Each screen should serve one user task. Common violations include combining list and entry functionality, mixing operator and supervisor workflows, and embedding configuration management in operational screens.

Violation PatternSOLID Correction
Work Order List + Production Entry on same screenSeparate into Work Order List screen and Production Entry screen
Operator and Supervisor views combined with role-based visibility hidingBuild dedicated screens per role
Dashboard that also includes a data entry formSeparate dashboard and form into distinct screens

Open/Closed in Screens

Screens should be configurable without being rewritten. Use $appConfig for thresholds, labels, colors, and display options. Use $state for runtime context. Avoid hardcoded values in component properties or JSONata bindings.

/* Open/Closed: Read OEE color threshold from $appConfig */
$state.payload.oeeValue > $appConfig.oeeGoodThreshold ? "green" : "red"

/* Violation: Hardcoded threshold */
$state.payload.oeeValue > 85 ? "green" : "red"

Interface Segregation in Screen Queries

Table and form screens should query only the fields they need. Use the Screen Designer's GraphQL builder to select specific fields rather than accepting default full-record queries. This reduces payload size and decouples screens from model changes.

Screen TypeFields to QueryFields to Avoid Querying
Work Order Listid, workOrderNumber, status, dueDate, assignedWorkcenterAll detail fields, BOM lines, history records
Production Entry Formid, workOrderNumber, targetQuantity, product.nameSupplier info, financial fields, audit timestamps
OEE Dashboardworkcenter.name, oeeValue, availabilityRate, performanceRate, qualityRateRaw event logs, individual cycle records

5. Technical Details

S — Single Responsibility: Flow Decomposition Pattern

A well-structured Fuuz flow should perform one logical operation. The recommended decomposition pattern for complex operations is a master flow that orchestrates focused child flows:

Master Flow: processProductionEvent
└── Child Flow: validateProductionRecord
└── Child Flow: calculateOeeContribution
└── Child Flow: updateWorkOrderProgress
└── Child Flow: notifyIfThresholdBreached

Each child flow has a single entry context and a single output. When the OEE formula changes, only calculateOeeContribution is modified. All other child flows and the master flow remain untouched.

O — Open/Closed: Runtime Context via setContext

Use a setContext node at the top of the flow to derive and consolidate runtime values — such as computed fields, payload-driven lookups, or current timestamps — into a single named context. This keeps all downstream logic nodes closed to change: if the derivation logic evolves, only the setContext node is updated.

/* setContext node — top of flow: derive runtime values once, reuse everywhere */
{
"eventTimestamp": $now(),
"shiftId": $state.payload.shiftId,
"workcenterType": $state.payload.workcenter.type,
"isNightShift": $state.payload.shiftStart > $appConfig.nightShiftStartTime or $state.payload.shiftStart < $appConfig.nightShiftEndTime
}

/* Violation: same derivation repeated inline across multiple downstream nodes */
/* Node A */ $now() & " — " & $state.payload.workcenter.type
/* Node B */ $state.payload.shiftStart > "20:00" or $state.payload.shiftStart < "06:00"
/* Node C */ $state.payload.shiftId & "-" & $state.payload.workcenter.type

Downstream nodes reference $state.context.isNightShift, $state.context.workcenterType, and so on — never re-deriving values inline. When the shift classification logic changes (e.g. night shift boundary moves to 21:00), only the setContext node is updated. All downstream nodes remain untouched.

L — Liskov Substitution: Model Base Field Contract

When designing models with type variants, define a base field set that all variants must carry. Document this contract explicitly in the model's description field:

Base FieldTypeRequired on All Variants
nameStringYes
statusEnum (WorkcenterStatus)Yes
currentShiftRelation → ShiftYes
oeeTargetFloatYes

Variant-specific fields (e.g., cleanRoomClassification for Clean Room type) are additive. Any flow or screen that operates on workcenters in general uses only the base contract fields and remains agnostic to type.

I — Interface Segregation: Minimal Flow Payload Contracts

When designing sub-flow inputs, define the minimum payload needed for that flow's task. Do not pass the entire upstream context. Use JSONata transforms to extract and shape the input:

/* Transform before calling notifyDowntimeAlert sub-flow */
/* Only pass what the notification flow needs */
{
"message": "Downtime threshold exceeded on " & $state.payload.workcenter.name,
"severity": "critical",
"recipientGroupId": $state.context.notificationGroupId
}

The notification sub-flow does not receive — and therefore does not depend on — production quantities, OEE values, shift records, or any other upstream data it doesn't need.

D — Dependency Inversion: Connector and Sequence Usage

All external integrations must use named Connectors. Never embed URLs, tokens, or credentials in flow logic:

/* Correct: Reference connector by name in HTTP Request node */
{
"connector": "PlexProduction",
"endpoint": "/api/v1/workorders",
"method": "GET"
}

/* Violation: Hardcoded URL and token */
{
"url": "https://mycompany.plex.com/api/v1/workorders",
"headers": { "Authorization": "Bearer abc123token" }
}

Similarly, all human-readable ID generation must use Sequences:

/* Correct: Use platform Sequence node */
Sequence Node → name: "WorkOrderNumber" → result: "WO-000123"

/* Violation: Generate number in JSONata */
"WO-" & $string($count($state.payload.existingOrders) + 1)

Anti-Patterns Reference

Anti-PatternViolated PrincipleCorrection
One flow handles OEE calc + notification + WO updateSRPSplit into three focused flows
Hardcoded threshold values inside flow logic nodesOCPMove all configurable values to setContext
Workcenter type variants with inconsistent base fieldsLSPDefine and enforce a base field contract for all variants
Querying full records when only 3 fields are neededISPWrite targeted queries with only required fields
Hardcoded ERP URL and token in HTTP Request nodeDIPConfigure a named Connector and reference it in the node
Operator and supervisor tasks on the same screenSRP + ISPBuild separate role-specific screens
Modifying base module to add new featureOCPAdd a new module to the ModuleGroup instead
ID generation via JSONata counter logicDIPUse a platform Sequence node

6. Resources

  • Related KB Article: Data Flows — Building and Managing Server-Side Logic
  • Related KB Article: Connectors — Configuring External System Integrations
  • Related KB Article: Sequences — Platform-Managed Identifier Generation
  • Related KB Article: Saved Queries — Reusable GraphQL Query Patterns
  • Related KB Article: Screen Designer — Building Role-Based User Interfaces
  • Related KB Article: Packages — Structuring and Deploying Fuuz Applications
  • Related KB Article: $appConfig — Application-Level Configuration Reference
  • Related KB Article: setContext and mergeContext — Managing Flow State
  • External Reference: SOLID Principles — Robert C. Martin (Uncle Bob) — Original formulation of the five principles
  • External Reference: Fuuz Platform Developer Documentation — fuuz.com/docs

7. Troubleshooting

IssueCauseResolution
A change to the OEE formula breaks the notification logic in the same flowSRP violation — multiple responsibilities in one flowDecompose the flow into separate child flows per responsibility. Use a master orchestrator flow to call them in sequence.
Deploying to a second plant requires editing core flow logicOCP violation — configurable values are hardcoded in logic nodesMove all plant-specific values (thresholds, IDs, targets) into a setContext node at the top of the flow. Reference via $state.context.
Dashboard screen breaks when a new workcenter type is addedLSP violation — dashboard query depends on type-specific fieldsUpdate the screen query to use only base contract fields. Add type-specific fields only in type-specific views.
Sub-flow breaks when upstream flow adds new fields to its payloadISP violation — sub-flow receives the full upstream payloadAdd a JSONata transform node before calling the sub-flow to extract and shape only the required fields into the sub-flow's input.
Integration flow fails after ERP environment changeDIP violation — URL or credentials hardcoded in the HTTP Request nodeReplace hardcoded connection details with a named Connector. Update the Connector configuration to point to the new environment without touching the flow.
Work order numbers become duplicated under concurrent loadDIP violation — ID generation logic implemented in JSONata instead of using a SequenceReplace the JSONata counter with a platform Sequence node. Sequences are atomic and concurrency-safe by design.
Screen performance is slow on low-bandwidth plant floor tabletsISP violation — screen queries are returning full records with many unused fieldsAudit the screen's GraphQL query and remove all fields not rendered in the UI. Use the Screen Designer's field selector to build lean queries.
Adding a new feature requires modifying a module shared across multiple deploymentsOCP violation — functionality is being added to a shared base module instead of extendedCreate a new module within the ModuleGroup for the new feature. The base module remains stable; the new module extends the application.
Operator screen is confusing because it shows too many fields irrelevant to their taskSRP + ISP violation — single screen trying to serve multiple rolesSplit into role-specific screens. Operator screen shows only production-critical fields. Supervisor screen provides review and approval workflow.
A notification flow behaves differently depending on which parent flow calls itLSP violation — the notification sub-flow has inconsistent input expectationsDefine a strict input contract for the sub-flow (message, severity, recipientGroupId). All calling flows must shape their payload to match this contract before invoking it.
Color thresholds on a dashboard are wrong after a KPI target changeOCP violation — threshold values are hardcoded in screen bindingsMove threshold values to $appConfig. Update $appConfig to change behavior without modifying the screen.
Three different flows embed the same GraphQL query for active work ordersDIP violation — data access logic is duplicated across flowsCreate a Saved Query for active work orders. All three flows reference it by name. When the query needs to change, update it in one place.

Best Practices

  • Name Flows Descriptively by Action: Use verb + noun names like calculateOeeHourly, closeWorkOrder, notifyDowntimeAlert. A well-named flow communicates its single responsibility immediately.
  • Start Every Flow with setContext: Even simple flows benefit from a context node. It creates a clear separation between configuration and logic, making the flow easier to extend later.
  • Define Sub-flow Contracts in the Flow Description: Document the expected input structure and output structure in the flow's description field. Future developers (and AI tools) depend on this to use the sub-flow correctly.
  • Never Hardcode Connector Credentials: Every external system connection must use a named Connector. This is both a SOLID principle and a security requirement.
  • Use Sequences for All Human-Readable IDs: Do not generate document numbers, order numbers, or sequence-based identifiers in JSONata. Sequences are atomic, configurable, and concurrency-safe.
  • Build Screens Per Role, Not Per Data Model: The natural tendency is to build one screen per model. Instead, build screens per user task and role. Multiple screens can share the same underlying model.
  • Audit Queries Before Deployment: Before deploying a screen, review all GraphQL queries and remove unused fields. Lean queries are faster, cheaper, and less coupled to model changes.
  • Extend via New Modules, Not Modified Modules: When adding capabilities to a deployed application, create a new module. Treat deployed modules as stable contracts.
  • Document Base Field Contracts on Models with Type Variants: Add a comment or description to the model noting which fields are part of the base contract that all variants must carry.
  • Test Sub-flows in Isolation: Because sub-flows have defined input/output contracts, they can be tested independently by sending a crafted payload. Build and validate each sub-flow before wiring it into a master flow.
  • Use $appConfig for Customer-Specific Configuration: Threshold values, display preferences, KPI targets, and feature flags belong in $appConfig, not in flow logic or screen bindings.
  • Review All Flows When Onboarding a New Plant: Multi-plant deployments surface SOLID violations quickly. Use new plant onboarding as an opportunity to audit flows for hardcoded values and model violations.
  • Prefer Saved Queries Over Repeated Inline GraphQL: If the same query appears in more than one flow or screen, promote it to a Saved Query. This centralizes maintenance and reduces risk of inconsistency.

8. Revision History

VersionDateEditorDescription
1.02026-03-06Fuuz Documentation TeamInitial Release

Fuuz Industrial Operations Platform — Knowledge Base


    • Related Articles

    • Data Flow Design Standards

      Article Type: Standard / Reference Audience: Solution Architects, Application Designers, Developers Module: Fuuz Platform - Data Flow Designer Applies to Versions: 2025.12+ 1. Overview Data Flow Design Standards define the mandatory requirements and ...
    • Data Flow Nodes Reference

      Fuuz Data Flow Nodes - Complete Reference Article Type: Reference Audience: Developers, App Admins, Solution Architects Module: Data Flows / Data Ops Applies to Versions: All 1. Overview The Fuuz Industrial Operations Platform provides a ...
    • Fuuz Form Detail Screen Specification

      Fuuz Industrial Intelligence Platform Article Type: Standard Standard Form Design Specification A unified framework for building single-record editing screens across Master Data, Transactional, and Setup applications—ensuring consistent user ...
    • Gateway Deployment & Architecture

      Article Type: Concept Audience: Solution Architects, Enterprise Administrators, Partners Module: Fuuz Gateway, Fuuz Enterprise Applies to Versions: 2025.12 1. Overview Fuuz is the first industrial operations platform to offer a true cloud-to-edge ...
    • Fuuz Deployment Methodologies

      Article Type: Concept Audience: Solution Architects, Enterprise Administrators, IT Infrastructure, Operations Directors, Integration Specialists Module: Platform Architecture & Infrastructure Applies to Versions: All Versions 1. Overview The Fuuz ...