Designing Modular n8n Workflows for Problems You Haven’t Met Yet
When building an n8n workflow, it is easy to design around the problem immediately in front of you.
There is a requirement, a known input, a known system, and an expected output. The obvious approach is to connect those pieces together and make them work.
That solves today's problem.
It can also create tomorrow's problem.
A better approach is to identify where variation is likely to occur and design those decisions into the workflow from the beginning.
Design for the problem class
Consider a workflow designed to automate basic network switch configuration.
The initial test environment might contain a Cisco switch running NX-OS. If the immediate requirement is simply:
Read NX-OS Configuration
↓
Determine Required Changes
↓
Generate Commands
↓
Apply Configuration
then it is very easy to build an NX-OS automation workflow.
The problem is that the actual requirement is unlikely to remain "automate this NX-OS switch."
If the proof of concept works, the next questions are predictable:
Can we use this on our other switches?
Can this support IOS?
What about Junos?
Can we use it on another part of the network?
At that point, a workflow designed specifically around NX-OS has to be pulled apart and generalised.
The original implementation solved the immediate problem, but in doing so created work for the next stage of the project.
Make variation explicit
In the workflow I am building, switch configuration dumps are retrieved from a repository.
Rather than assuming that every configuration belongs to an NX-OS device, the first stage determines what operating system produced the configuration.
Conceptually:
Configuration Repository
↓
Retrieve Configuration
↓
Inspect Configuration
↓
Identify Operating System
↓
Set OS Field
The workflow item might then contain something similar to:
{
"hostname": "switch-01",
"os": "nxos",
"config": "..."
}
From that point forward, the workflow knows what type of device it is processing.
That distinction becomes part of the data rather than an assumption buried inside the workflow.
Route rather than assume
Once the operating system has been identified, a Switch node can route the item to the appropriate implementation.
┌── NX-OS Processing
│
Configuration ── Switch ── IOS Processing
│
├── Junos Processing
│
└── Other / Unsupported
The important part is that these branches can exist before every implementation does.
The NX-OS path might be fully functional while the others initially lead to an unsupported handler.
OS = nxos
↓
NX-OS Workflow
OS = ios
↓
Not Yet Implemented
OS = junos
↓
Not Yet Implemented
That might appear unnecessary while NX-OS is the only system currently in scope.
It is not.
The workflow has already established where operating-system-specific behaviour belongs.
When IOS support is eventually requested, the architecture does not need to change. An existing branch simply needs an implementation.
Separate detection from implementation
This also prevents platform-specific logic from spreading throughout the workflow.
Without an explicit abstraction, assumptions tend to accumulate:
Parse NX-OS Interface
↓
Check NX-OS VLAN
↓
Generate NX-OS Command
↓
Validate NX-OS Result
Adding another platform later means finding all of those assumptions and working out which parts are genuinely common and which are platform-specific.
A modular workflow instead makes that boundary deliberate:
Retrieve Device Data
↓
Identify Platform
↓
Normalise Common Data
↓
Platform Decision
↓
Platform-Specific Processing
↓
Common Result
The common parts remain common.
The implementation details remain behind the platform decision.
Normalise the data early
A useful extension of this pattern is to make downstream nodes care as little as possible about where their data originated.
For example, different platforms may represent an interface differently:
NX-OS:
Ethernet1/1
IOS:
GigabitEthernet1/0/1
Junos:
ge-0/0/1
The parsing logic may therefore need to be platform-specific.
But the rest of the workflow may only need something like:
{
"device": "switch-01",
"interface": "Ethernet1/1",
"description": "Customer Link",
"admin_state": "up",
"vlan": 120
}
Once data has been converted into a common internal representation, later stages no longer need to understand how NX-OS, IOS, or Junos represented it originally.
This creates a useful architectural boundary:
Platform-Specific Input
↓
Normalisation
↓
Common Internal Data
↓
Business Logic
↓
Platform-Specific Output
The workflow becomes modular in both directions.
Prepare decision points while designing
This principle extends beyond network operating systems.
Whenever a workflow reaches a decision, it is worth considering what other valid outcomes could eventually exist.
If the current requirement says:
if type = A
do something
the design question should not only be:
How do I handle A?
It should also be:
What else could type reasonably become?
Those possibilities do not all need to be implemented.
They should, however, influence where the decision is placed and how the surrounding workflow is structured.
This applies to things such as:
- device platforms
- API providers
- customer types
- authentication methods
- environments
- regions
- notification destinations
- data sources
- output formats
- infrastructure vendors
A branch that currently has no implementation is cheap.
Restructuring a large production workflow because the possibility of that branch was never considered is not.
Scope documents have a very short half-life
A scope document describes what people believe they want before they have seen the result.
Once stakeholders see a working implementation, that changes.
A successful workflow demonstrates possibilities that were previously abstract.
Someone will see it and ask:
Could we also use this for...?
That is not necessarily scope creep caused by poor requirements.
It is often a natural consequence of demonstrating something useful.
The original scope may have said NX-OS because NX-OS was the immediate problem. Once the automation exists, extending the same capability to other platforms becomes an obvious idea.
The architecture should expect that.
Do not build everything in advance
Modular design does not mean implementing every hypothetical future requirement.
Building complete IOS, Junos, EOS, and other implementations before anyone needs them would be the opposite problem: engineering functionality without a requirement.
The distinction is between implementing future functionality and leaving somewhere for future functionality to go.
For example:
Bad:
Build support for ten platforms nobody has requested.
Better:
Identify platform
↓
Switch on platform
↓
Implement NX-OS
↓
Explicitly handle everything else as unsupported
The second design costs very little more than hard-coding NX-OS.
But it establishes the extension point.
Design for expansion, implement for today
A useful rule is:
Architect for foreseeable variation.
Implement for current requirements.
The architecture acknowledges that the world contains more than the current use case.
The implementation remains focused on what is actually required.
That balance avoids both extremes.
At one extreme:
Hard-code today's assumptions everywhere.
At the other:
Build an enormous generic framework for requirements
that may never exist.
Neither is particularly useful.
The goal is to identify the places where change is predictable and make those places easy to extend.
A well-designed workflow makes the next request boring
One of the best signs of a good modular design is what happens when somebody asks for something new.
If a stakeholder eventually says:
This works well for NX-OS.
Can we add IOS?
the answer should not require redesigning the workflow.
Ideally, the architecture already has an IOS path waiting for an implementation.
The new requirement becomes:
Implement IOS module
rather than:
Redesign the automation so it understands that
more than one operating system exists.
That difference becomes increasingly important as workflows grow.
A workflow designed only for its first requirement accumulates assumptions.
A workflow designed around explicit interfaces, decision points, and common data structures accumulates extension points.
The best time to create those extension points is while the workflow is still being designed and debugged, when changing the architecture is cheap.
A scope document may only describe today's requirement.
A good design should make a reasonable attempt to anticipate tomorrow's obvious question.