I often see people discuss embedded development and web development as being two opposite sides of the spectrum of software engineering: one is low-level and hardcore, and the other one is high-level and almost trivial. After more than 20 years working in both domains, I have come to view web and embedded as highly similar—I can use the same approaches in both worlds.
The usual discourse goes as follows:
- Embedded development is complex and open to a select few experienced developers. It requires precise thinking and strict software engineering.
- Web development, on the other hand is easy, relaxed, mainly concerned with making things pretty and throwing CSS, javascript frameworks, and SQL at a CRUD application
Web applications and embedded systems are both distributed systems
In an embedded system, you have sensors, actuators, multiple microcontrollers, often a Linux system, increasingly standard cloud connectivity, and over-the-air updates. In a web application, you have client browsers, servers, storage services, databases, caches, logging, and monitoring. These components need to communicate to present the user with the desired functionality.
Furthermore, these components need to be monitored and maintained. The system is usually permanently online. Building software updates is intricate; distributed and update processes often require orchestration. Debugging at scale usually requires monitoring and log collection, as interacting with any individual component is often impossible.
Web and embedded software engineering is remarkably similar
At a more technical level, the software and architecture of web and embedded systems are built on the same underlying abstractions and must fulfill similar constraints.
At a higher abstraction level:
- The user application is implemented by a distributed system
- Actors communicate over a network, using a variety of protocols
- The application is long-lived, requiring special attention to error handling
- Most actions require a sequence of asynchronous steps
- Deployment at scale requires synchronization because of interdependent components
- Because of their complexity, systems often exhibit emergent behavior
- The execution environment is remote (local web development usually requires some docker setup; embedded development requires some playground for the actual sensors and actors)
- Performance is an essential component of system design due to power, real-time, response time, or cost constraints
The similarities are even more striking once we zoom in on the code level:
- User and hardware interaction often require complex behavior due to UX design,
- Most of the code is sequencing asynchronous steps
- There is no happy path. Because at least part of the system runs without direct user supervision, we need at least restarts and error logging
- The same software patterns prove very effective:
- state machines, promises, or coroutines
- event-driven development
- structured logging and observability metrics
- structured RPC IDLs
- monoidal state handling (state reducers like redux store)
- Instrumentation for debugging is extremely useful, yet can quickly impact runtime behavior
- Many issues can only be caught in production (or in-field testing). Mocking only goes so far due to the emergent behaviors mentioned above
There is a lot to learn from both fields
I have learned a lot from working in both domains and have carried over not just ideas but actual code and tooling. Framing embedded development as low-level systems requiring advanced skills versus web development as a free-for-all, low-hanging fruit obscures how similar they are.
Both are hard to get right, both benefit from a holistic systems approach, and actual code and tooling carry over surprisingly easily.
I plan this to become a longer series that explores what I learned in my career in web and embedded, so stay tuned!
(cover image: ranzino flickr)