Skip to content

Below is a principal-level way to think about Modularization & Component Design in industrial machine software.

This sits directly inside your project’s architecture track, where the source of truth emphasizes that machine software must stay maintainable despite hardware complexity, long-running workflows, and mixed concerns like UI, device control, orchestration, alarms, and configuration. It also explicitly calls out domain boundaries, service vs controller vs workflow responsibilities, device manager patterns, and separation of UI, workflow, and device logic as core concerns.


PART 1 — WHY MODULARIZATION IS CRITICAL

Industrial machine software gets large much faster than many engineers expect.

At first, a machine app can look simple: connect devices, show status, run a recipe, move stages, capture images, and handle alarms. But in a real system, each of those becomes its own world:

  • motion has constraints, homing, limits, interlocks, and asynchronous completion
  • vision has acquisition timing, calibration, buffering, and failure modes
  • IO has sensor interpretation, debounce, and safety-related meaning
  • workflows are long-running, interruptible, and stateful
  • UI must stay responsive while everything else is happening
  • faults and recovery must work even when the machine is already in a bad state

That is why industrial architecture becomes fragile when everything is allowed to know about everything else.

Without modularization, the system usually decays in a very predictable way:

  • workflow code starts calling vendor SDKs directly
  • UI logic starts containing machine decisions
  • alarm handling gets duplicated in multiple places
  • device state leaks across the system as shared mutable objects
  • one small hardware change forces edits across ten unrelated files

The result is not just messy code. It is operational risk.

A poorly modularized business system might create maintenance pain. A poorly modularized machine system can create:

  • unsafe behavior
  • inconsistent machine state
  • hard-to-reproduce bugs
  • very slow debugging under production pressure
  • long commissioning cycles
  • fear of changing anything

Modularization matters because it gives you containment.

It lets you isolate concerns such as:

  • motion behavior from inspection workflow
  • workflow decisions from device protocol details
  • UI presentation from machine execution
  • device fault interpretation from operator-facing alarms

A strong modular structure is what allows a machine system to grow from “lab prototype that only Alice understands” into “production software that five engineers can evolve safely.”

A simple example:

Bad structure:

  • inspection workflow directly calls stage SDK
  • workflow directly turns lights on/off
  • workflow directly checks vacuum sensor bits
  • UI directly reads device objects and sends motion commands

Good structure:

  • workflow coordinates high-level actions
  • motion module owns physical movement
  • vision module owns acquisition behavior
  • IO/safety module owns permissives and machine conditions
  • UI only talks to application-facing interfaces and state models

That is the difference between a system that can evolve and one that becomes a permanent debugging exercise.


PART 2 — WHAT IS A MODULE / COMPONENT

In this context:

Module

A module is a logical slice of the system that owns a meaningful area of responsibility.

Examples:

  • Motion
  • Vision
  • Workflow Execution
  • Alarm Management
  • Recipe Management
  • Machine State
  • UI / HMI
  • Device Integration
  • Safety / Interlock View

A module is mainly about ownership and boundary.

It answers:

  • What area of the machine does this part own?
  • What decisions are made here?
  • What information is private here?
  • What interface does the rest of the system get?

Component

A component is a concrete unit inside a module with a defined responsibility and interface.

Examples inside a Motion module:

  • AxisController
  • MotionCoordinator
  • PositionMonitor
  • HomingService
  • SoftLimitValidator

Examples inside a Vision module:

  • CameraController
  • AcquisitionSession
  • LightController
  • CalibrationProvider

So the relationship is roughly:

  • module = boundary of responsibility
  • component = internal building block with a defined role

A good component has three properties:

1. Clear purpose

You should be able to say in one sentence what it owns.

Bad:

  • “This service manages machine stuff.”

Good:

  • “This component owns the execution of homing sequences for motion axes.”
  • “This component translates raw PLC bits into typed machine conditions.”
  • “This component coordinates an inspection run across motion and vision.”

2. Well-defined interface

Other parts of the system should know:

  • what they can ask it to do
  • what data it returns
  • what events it emits
  • what failures it can signal

3. Minimal external dependencies

A component should not depend on half the system just to perform a simple job.

If your RecipeValidator needs motion SDK, UI state, alarm history, and workflow controller just to validate a recipe, the design is already wrong.


PART 3 — DEFINING BOUNDARIES

A boundary is the most important modularization decision.

A boundary defines:

  • what a module owns
  • what it exposes
  • what it hides
  • who is allowed to depend on it
  • what kinds of changes should remain local to it

A good boundary is not just technical. It reflects how the machine really behaves.

For example, a Motion module should own:

  • axis abstractions
  • move commands
  • homing
  • position/state monitoring
  • motion faults
  • motion constraints at motion level

It should not own:

  • product recipe intent
  • operator screen logic
  • inspection pass/fail decisions
  • lot execution state

That means the workflow layer can ask motion to do something, but it should not reach inside and manipulate axis internals.

ASCII Component Diagram

text
+---------------------------------------------------------------+
|                       Machine Software                        |
+---------------------------------------------------------------+
|                                                               |
|  +------------------+     +------------------+                |
|  |   Operator UI    | --> | Application      |                |
|  |   / HMI Module   |     | Orchestration    |                |
|  +------------------+     +------------------+                |
|            |                        |                         |
|            |                        |                         |
|            v                        v                         |
|  +------------------+     +------------------+                |
|  | Machine State    |     | Recipe / Run     |                |
|  | Module           |     | Execution Module |                |
|  +------------------+     +------------------+                |
|            |                        |                         |
|            +------------+-----------+                         |
|                         |                                     |
|                         v                                     |
|      +------------------+------------------+                  |
|      |   Equipment Capability Modules      |                  |
|      |-------------------------------------|                  |
|      | Motion | Vision | IO | Alarms       |                  |
|      +------------------+------------------+                  |
|                         |                                     |
|                         v                                     |
|      +-------------------------------------+                  |
|      | Device Integration / SDK Adapters   |                  |
|      +-------------------------------------+                  |
|                         |                                     |
|                         v                                     |
|      +-------------------------------------+                  |
|      | Physical Devices / Controllers      |                  |
|      +-------------------------------------+                  |
|                                                               |
+---------------------------------------------------------------+

How to read this diagram

This is not a deployment diagram. It is a responsibility diagram.

Key idea:

  • UI does not talk directly to device SDKs
  • orchestration does not bypass equipment modules
  • equipment capability modules expose machine-facing behavior
  • vendor SDK details stay buried in device integration/adapters

That is the boundary discipline that keeps the system understandable.

Encapsulation matters more in machines

In machine software, hiding internals is not academic purity. It prevents dangerous accidental coupling.

For example:

Bad:

  • any module can set Axis.CurrentPosition
  • any module can clear alarms
  • any module can flip “MachineReady”
  • any screen can command a stage move directly

Good:

  • state is published, not freely mutated
  • commands go through explicit interfaces
  • transitions happen through controlled services
  • faults are raised through owned paths

PART 4 — COUPLING & COHESION

Coupling

Coupling is how much one module depends on another module’s details.

Examples of high coupling:

  • workflow knows exact PLC bit names
  • UI knows motion controller error codes
  • recipe validation depends on specific camera SDK structs
  • alarm logic reads random flags from ten services

High coupling creates ripple effects.

You change one thing in motion, and suddenly:

  • workflow breaks
  • UI breaks
  • recipe loading breaks
  • recovery logic behaves differently

That is how systems become fragile.

Cohesion

Cohesion is how strongly related the responsibilities inside one module are.

A high-cohesion module has responsibilities that belong together.

Example of high cohesion:

Motion module contains:

  • move planning at machine level
  • homing
  • soft-limit validation
  • motion completion tracking
  • axis fault interpretation

These are all closely related to motion ownership.

Example of low cohesion:

A MachineManager that contains:

  • camera connect
  • stage homing
  • recipe parsing
  • alarm acknowledgement
  • operator login
  • data export

That is not a module. That is a junk drawer.

Goal: low coupling, high cohesion

That combination produces the behavior you want:

  • changes stay local
  • failures are easier to isolate
  • ownership is clear
  • testing is realistic
  • reasoning about the system becomes possible

A useful test:

If I replace the camera vendor SDK, what should change?

Good answer:

  • mostly the vision integration layer
  • maybe some vision internals
  • minimal impact outside the vision boundary

Bad answer:

  • workflow, UI, alarms, recipe loader, logging, motion synchronization, and half the service layer

That answer tells you whether the boundary is real.


PART 5 — COMMUNICATION BETWEEN MODULES

Modules should communicate through controlled, intentional mechanisms.

In machine software, the main patterns are:

  • interfaces for synchronous requests
  • commands for owned actions
  • events/messages for state change or completion notification

The important thing is not which pattern sounds fashionable. The important thing is that communication is:

  • explicit
  • directional
  • typed
  • owned
  • observable

Typical communication model

  • UI sends command to orchestration
  • orchestration invokes module capabilities
  • modules publish state/events
  • machine state aggregates and exposes system view
  • alarms are raised through a dedicated path

ASCII Interaction Diagram

text
Operator
   |
   | Start Inspection
   v
+------------------+
| UI / HMI         |
+------------------+
   |
   | command
   v
+------------------+
| Run Orchestrator |
+------------------+
   |         |           |
   |         |           |
   |         |           +----------------------+
   |         |                                  |
   v         v                                  v
+--------+ +--------+                     +-------------+
| Motion | | Vision |                     | IO / Safety |
+--------+ +--------+                     +-------------+
   |         |                                  |
   | state   | event                            | condition
   +---------+---------------+------------------+
                           |
                           v
                  +------------------+
                  | Machine State /  |
                  | Alarm Module     |
                  +------------------+
                           |
                           v
                  +------------------+
                  | UI Update        |
                  +------------------+

How to read this diagram

The orchestrator coordinates work, but it does not become the owner of everything.

That is a very important distinction.

Bad orchestration means:

  • it knows all device details
  • it contains alarm rules
  • it computes all state
  • it owns retries for every subsystem
  • it turns into a giant god object

Good orchestration means:

  • it sequences cross-module behavior
  • it invokes owned capabilities of other modules
  • it reacts to outcomes
  • it manages process-level intent, not low-level mechanics

Why explicit communication matters

When communication is implicit, systems become impossible to reason about.

Examples of implicit communication:

  • shared mutable singleton state
  • random service locator calls
  • module A silently mutates data that module B is watching
  • UI polls hidden internal objects directly
  • side effects inside getters or subscriptions

In long-running machine software, this kind of implicit coupling leads to “ghost behavior”:

  • machine state changes but nobody knows why
  • UI shows a stale mode
  • one module assumes a device is ready while another is reinitializing it
  • alarms clear unexpectedly after some unrelated operation

Strong systems reduce this by making communication routes obvious.


PART 6 — EVOLUTION & EXTENSIBILITY

Industrial software never stays still.

Real systems evolve because of:

  • new hardware revisions
  • replacement vendors
  • added inspection modes
  • new workflow branches
  • field service requirements
  • new alarm conditions
  • better recovery logic
  • different customer variants

If the original system has weak modularization, every evolution becomes surgery.

If the system has strong modularization, evolution becomes controlled extension.

What modular design enables

Adding a new device

If vision is a true module with a stable application-facing contract, you can introduce a second camera implementation without forcing workflow or UI to understand vendor specifics.

Replacing a component

If motion completion logic is encapsulated, you can refine the internal algorithm for move monitoring without changing the orchestration boundary.

Extending workflows

If workflow logic depends on explicit module contracts, you can add a new inspection sequence that reuses motion, vision, and safety capabilities without duplicating those concerns.

Supporting machine variants

A modular architecture lets you vary internal implementations or compose different module sets while preserving the overall system shape.

The key point is this:

Extensibility is not about making everything abstract. It is about protecting the right decisions behind the right boundaries.

Too many engineers hear “extensible” and build a forest of interfaces that nobody needs.

Real extensibility in machine systems usually comes from:

  • stable ownership boundaries
  • narrow contracts
  • capability-oriented APIs
  • hidden vendor/protocol details
  • state and behavior localized to the owning module

PART 7 — REAL-WORLD FAILURE SCENARIOS

These are the failure patterns I see most often in machine systems.

1. Module boundaries unclear → responsibilities overlap

What it looks like

  • workflow layer contains motion safety checks
  • motion layer also contains workflow assumptions
  • UI prevents some commands, but backend also prevents them differently
  • alarm interpretation exists in both device layer and orchestration layer

Why it happens

The system grows incrementally. Engineers solve immediate problems where they stand.

Over time, logic spreads across layers because nobody defines ownership clearly.

How engineers fix it

They define for each concern:

  • who owns the rule
  • who exposes the result
  • who is allowed to make the decision
  • who only consumes the outcome

Example:

  • motion owns move validity at motion level
  • safety/conditions module owns machine permissives
  • workflow owns process sequencing decisions
  • UI only reflects what is allowed and visible

2. Tight coupling → small change breaks multiple modules

What it looks like

A new camera initialization step breaks:

  • startup workflow
  • manual service screen
  • recipe activation
  • alarm display
  • image view

Why it happens

Because multiple modules were directly coded against camera details instead of depending on a vision boundary.

How engineers fix it

They introduce a real vision capability boundary:

  • connect / initialize
  • start acquisition
  • stop acquisition
  • report readiness/state/faults
  • expose machine-meaningful failures

Then they move SDK-specific behavior behind it.


3. Duplicated logic across modules

What it looks like

Readiness checks appear in:

  • startup sequence
  • auto-run sequence
  • manual mode screen
  • service tool
  • recovery flow

Each one is slightly different.

Why it happens

Because “it was faster” to reimplement the rule locally than define a shared owner.

What it causes

  • contradictory behavior
  • inconsistent operator experience
  • intermittent bugs
  • support confusion

How engineers fix it

They centralize ownership.

For example:

  • MachineConditionsService owns readiness evaluation
  • everyone else consumes a typed result like CanStartRun, CanEnterServiceMode, CanHomeAxes

4. Shared state across modules → inconsistent behavior

What it looks like

Everyone reads and writes the same “machine state” object.

  • one module sets IsReady = true
  • another sets it false during reconnect
  • UI caches an old value
  • workflow starts based on stale assumptions

Why it happens

Shared state feels easy at first. It avoids interface design.

Why it is dangerous

Because there is no ownership of truth.

In machine systems, state is already complicated. Shared writable state makes it much worse.

How engineers fix it

They move to controlled state ownership:

  • each module owns its own internal state
  • system state is composed from published module state
  • state mutation happens only through owned flows
  • consumers observe or query, but do not freely mutate

5. Debugging requires understanding the entire system

What it looks like

A pause/resume bug requires reading:

  • UI code
  • workflow code
  • motion callbacks
  • device connection handling
  • alarm suppression logic
  • PLC adapter code

Why it happens

Because the feature crosses unclear boundaries and hidden dependencies.

How engineers fix it

They redesign around observable module contracts and traceable interactions:

  • explicit command entry points
  • explicit events and state transitions
  • correlation IDs / operation IDs
  • owned fault reporting paths
  • fewer side effects crossing boundaries

A well-modularized system does not eliminate debugging pain. It contains it.


PART 8 — SOFTWARE DESIGN IMPLICATIONS

Modularization must be intentional.

It does not happen automatically just because the codebase has folders named Services, Managers, or Controllers.

In machine software, intentional modularization usually requires explicit decisions about:

  • ownership
  • dependency direction
  • contract shape
  • state boundaries
  • communication patterns
  • failure propagation

Bad approach

text
+--------------------------------------------------+
|                  MachineApp                      |
|--------------------------------------------------|
| UI, workflow, motion calls, camera SDK, alarms, |
| recipe loading, state flags, PLC reads, logging  |
| all mixed through shared services and globals    |
+--------------------------------------------------+

This kind of system often uses classes with names like:

  • MachineService
  • SystemManager
  • MainController
  • GlobalContext

These names usually hide the fact that there is no real decomposition.

Good approach

text
+------------------+      +------------------+
| UI / HMI         | ---> | Application      |
|                  |      | Orchestration    |
+------------------+      +------------------+
                                   |
        +--------------------------+--------------------------+
        |                          |                          |
        v                          v                          v
+------------------+     +------------------+      +------------------+
| Motion Module    |     | Vision Module    |      | IO / Safety      |
| owns motion      |     | owns acquisition |      | owns conditions  |
+------------------+     +------------------+      +------------------+
        |                          |                          |
        +-------------+------------+--------------------------+
                      |
                      v
             +----------------------+
             | State / Alarm / Run  |
             | interpretation layer |
             +----------------------+

ASCII Dependency Diagram

text
[UI / HMI]
    |
    v
[Application / Orchestrators]
    |
    +---------> [Machine State]
    |
    +---------> [Run / Recipe]
    |
    +---------> [Motion]
    |
    +---------> [Vision]
    |
    +---------> [IO / Safety]
                     |
                     v
            [Device Integration Adapters]
                     |
                     v
              [Vendor SDK / PLC / HW]

How to read this dependency diagram

Dependencies should mostly point downward toward owned capabilities.

What you want to avoid:

text
[UI] <----> [Workflow] <----> [Motion] <----> [Vision]
   \            |               ^               /
    \-----------+---------------+--------------/

That kind of lateral mesh creates uncontrolled coupling.

Practical design rules

These are the rules I would actually use on a machine project:

1. Organize around machine capabilities, not technical utility buckets

Prefer:

  • Motion
  • Vision
  • Workflow
  • Alarms
  • Recipes
  • State
  • Device Integration

Over:

  • Helpers
  • Services
  • Common
  • Managers
  • Utils

2. Separate coordination from execution

  • orchestrators coordinate cross-module behavior
  • modules execute owned behavior
  • adapters talk to hardware

3. Keep vendor details at the edge

Never let raw SDK semantics become the language of the whole application.

4. Make state ownership explicit

Do not let every module mutate global machine state.

5. Design for failure paths, not just happy paths

A boundary is only real if failure handling is also owned clearly.

6. Prefer machine language over implementation language

Interfaces should say things like:

  • HomeAxis
  • StartInspectionRun
  • AcquireFrame
  • EvaluateStartPermissives

Not:

  • ExecuteVendorSequence42
  • SetBitMask
  • InvokeTransportLayerAction

Unless that really is the owned abstraction.


PART 9 — INTERVIEW / REAL-WORLD TALKING POINTS

How to explain modularization clearly

A strong answer sounds like this:

In industrial machine software, modularization is about assigning clear ownership to major machine concerns such as motion, vision, IO, workflow, recipes, and alarms. The goal is to keep hardware-specific details, long-running process logic, and UI behavior from becoming tangled. Good modules expose narrow, explicit contracts, hide internal complexity, and reduce ripple effects when devices, workflows, or machine variants change.

Difference between module and component

A simple interview-ready explanation:

A module is a larger responsibility boundary, such as Motion or Vision. A component is a smaller unit inside that boundary with a more specific job, such as a homing service, acquisition controller, or fault interpreter. Modules define ownership at system level; components structure that ownership internally.

Common mistakes engineers make

The most common ones are:

  • creating “god services” instead of real modules
  • letting UI call device code directly
  • mixing orchestration with device execution logic
  • using shared mutable state as a shortcut
  • duplicating rules across manual mode, auto mode, and startup flows
  • exposing vendor SDK types beyond the integration boundary
  • designing boundaries only for happy-path calls, not for faults and recovery

What strong engineers understand about system decomposition

Strong engineers understand that in machine software:

  • decomposition is driven by machine behavior, not just class count
  • boundaries must survive hardware instability and long-running workflows
  • ownership of state and failure handling is as important as ownership of commands
  • the real test of modularity is whether change stays local
  • a module is successful when other teams can use it without knowing its internals

A strong closing statement for interviews

Good modularization in industrial software is not about making the code look clean in isolation. It is about creating boundaries that match the machine’s real responsibilities, so motion, vision, workflow, alarms, and UI can evolve without hidden coupling. That is what makes the system safer to change, easier to debug, and more maintainable over the machine’s long lifecycle.


If you want, I can continue with the next topic in the same style.

Docs-first project memory for AI-assisted implementation.