- Published on
Firmware Architecture Berbasis Class (Layered & Deterministic)
- Authors
π Artikel 4: Firmware Architecture Berbasis Class (Layered & Deterministic)
Posisi: Artikel 4 dari 8 Domain Keputusan: Architecture & Layering Status Lock: π Layering & Dependency Direction Freeze Entry Context: IndustrialNode/IndustrialNode.ino
- π Artikel 4: Firmware Architecture Berbasis Class (Layered & Deterministic)
- 1. Problem Reality
- 2. Root Cause Analysis
- 3. Design Principle (Rule yang Dikunci)
- π Rule 1 β 3-Layer Model Final
- π Rule 2 β Dependency Direction Strict (DAG)
- π Rule 3 β Driver is Pure Hardware Boundary
- π Rule 4 β Service is Domain Boundary (Final)
- π Rule 5 β Application is System Decision Layer (Final)
- π Rule 6 β Composition Root Only in IndustrialNode.ino
- Setelah Artikel 4
- Inti yang Harus Dipahami Engineer Baru
- π Rule 4 β Service is Domain Boundary
- π Rule 5 β Application is Decision Layer
- π Rule 6 β Composition Root Only in IndustrialNode.ino
- Setelah Artikel 4
- Inti yang Harus Dipahami Engineer Baru
- 4. Implementation Pattern (ESP32 Arduino Context)
- 5. Constraint & Embedded Impact
- 6. Failure Scenario
- 7. Anti-Pattern
- 8. Freeze Point
- 9. Engineering Checklist
- 10. Summary (5 Bullet Maksimal)
1. Problem Reality
Setelah Artikel 2 dan 3:
- Global mutable sudah hilang.
- Allocation sudah disiplin.
- Constructor injection sudah eksplisit.
Namun firmware masih bisa runtuh jika arsitektur tidak dibekukan.
Masalah umum yang muncul:
svc_mulai includeapp_drv_tahu detail logic bisnis- Application langsung akses hardware
- Circular include muncul perlahan
- File flat Arduino mempermudah include sembarangan
Karena Arduino tidak mengizinkan nested folder kompleks, semua file berada dalam satu root.
Tanpa aturan dependency direction, struktur akan kembali menjadi spaghetti β versi class.
2. Root Cause Analysis
Masalahnya bukan pada class.
Masalahnya adalah:
Tidak ada direction rule antar layer.
Contoh pelanggaran tipikal:
#include "app_ControlApp.h" // di dalam svc_CommManager.cpp β
Atau:
#include "svc_ActuatorService.h" // di dalam drv_RelayDriver.cpp β
Akibatnya:
- Circular dependency
- Tight coupling
- Refactor sulit
- Boundary kabur
Dependency graph menjadi cyclic.
π Visualisasi Dependency Chaos Tanpa Layering


Tanpa aturan arah, semua modul bisa saling tarik.
Firmware kehilangan bentuk.
Baik. Berikut full version yang sudah diperbaiki (khususnya Rule 4 & 5), tanpa mengubah struktur lain, dan tanpa ambiguitas serta tanpa melanggar layering.
3. Design Principle (Rule yang Dikunci)
Artikel ini adalah titik di mana struktur firmware tidak lagi bebas berubah.
Sebelum titik ini:
- kode masih bisa dipindah-pindah
- tanggung jawab masih bisa dicampur
- eksperimen masih diperbolehkan
Setelah titik ini:
- struktur menjadi tetap
- setiap file punya batas tanggung jawab
- perubahan hanya boleh terjadi di dalam layer, bukan antar layer
Tujuannya bukan βrapi secara teoriβ, tetapi:
mencegah firmware menjadi sulit dipelihara setelah jumlah file bertambah
π Rule 1 β 3-Layer Model Final
Firmware dibagi menjadi 4 kelompok file berdasarkan prefix:
app_ β logic utama sistem
svc_ β pengelola domain
drv_ β akses hardware
sys_ β tipe dan konfigurasi dasar
Karena Arduino tidak mendukung struktur folder kompleks, maka:
prefix file = satu-satunya cara membedakan layer
Contoh nyata di project:
app_control.cpp
svc_sensor.cpp
drv_relay.cpp
sys_config.h
Jika prefix tidak konsisten, maka:
- engineer tidak tahu file itu berada di layer mana
- dependency mudah salah arah
π Rule 2 β Dependency Direction Strict (DAG)
Arah include ditentukan secara eksplisit:
app_ β svc_ β drv_ β sys_
Ini berarti:
app_boleh memakaisvc_svc_boleh memakaidrv_drv_hanya boleh memakaisys_
Alasan praktis:
mencegah dependency saling silang yang sulit ditelusuri
Contoh masalah jika dilanggar:
- driver memanggil service β driver tidak bisa dipakai ulang
- service memanggil app β logika jadi berputar
- circular include β compile error atau coupling tinggi
DAG memastikan:
tidak ada siklus dependency
π Rule 3 β Driver is Pure Hardware Boundary
Driver hanya berisi kode seperti:
digitalWriteanalogReadWire.readSPI.transfer
Driver tidak boleh mengandung:
if mode == AUTO
if mqtt_connected
if temperature > threshold
Alasan:
driver harus tetap bisa dipakai ulang tanpa tahu sistem di atasnya
Contoh:
drv_relay.cpp hanya tahu:
set HIGH
set LOW
Tidak tahu:
- kenapa relay ON
- kapan relay ON
- siapa yang memutuskan
π Rule 4 β Service is Domain Boundary (Final)
Service berada di tengah:
- menerima data dari driver
- menyediakan fungsi ke application
Contoh:
svc_sensor
svc_actuator
svc_comm
Service boleh:
- mengolah data sensor
- melakukan filtering sederhana
- melakukan logic lokal sederhana (tidak bergantung kondisi global sistem)
- menyediakan API seperti
getTemperature()
Contoh logic yang boleh di service:
if (temp > threshold)
actuatorService.on();
else
actuatorService.off();
Selama:
tidak ada mode (AUTO/MANUAL)
tidak ada state machine
tidak ada kondisi global sistem
Service tidak boleh:
mengandung mode sistem (AUTO/MANUAL)
mengatur state machine utama
menggunakan kondisi global (system_error, network_state, dll)
mengakses ISR langsung
Alasan:
service hanya menangani perilaku lokal domain, bukan keputusan sistem
π Rule 5 β Application is System Decision Layer (Final)
Application adalah tempat keputusan tingkat sistem dibuat.
Contoh:
- mode AUTO / MANUAL
- fail-safe
- override
- kombinasi beberapa service
Application:
- menggunakan service
- menggabungkan beberapa input
- menentukan perilaku sistem secara keseluruhan
Contoh yang benar (fail-safe, tidak melanggar layer):
if (system_error)
{
actuatorService.forceOff();
}
Penjelasan:
Application membuat keputusan
Service mengeksekusi aksi
Driver tetap tersembunyi
Application tidak boleh:
memanggil relay langsung
memanggil digitalWrite langsung
mengakses Wire/SPI langsung
Alasan:
application menangani keputusan sistem, bukan implementasi hardware
π Rule 6 β Composition Root Only in IndustrialNode.ino
Semua object dibuat hanya di:
IndustrialNode.ino
Contoh:
RelayDriver relay(5);
TempSensor sensor;
ActuatorService actuator(relay);
ControlApp app(sensor, actuator);
Tidak boleh:
- membuat object di banyak file
- membuat object tersembunyi di dalam class lain
Alasan:
agar semua dependency terlihat di satu tempat
Setelah Artikel 4
Mulai titik ini, aturan tidak boleh dilanggar:
β tidak boleh ubah layer
β tidak boleh include melanggar arah
β tidak boleh shortcut antar layer
Jika dilanggar, efeknya biasanya:
- dependency sulit dilacak
- perubahan kecil merusak banyak file
- firmware sulit dikembangkan
Inti yang Harus Dipahami Engineer Baru
Layering ini bukan untuk βrapiβ, tetapi untuk:
membatasi dampak perubahan
Contoh:
- ubah driver β tidak merusak application
- ubah logic β tidak menyentuh hardware
- ganti sensor β hanya ubah driver/service
Jika boundary ini dijaga, firmware tetap bisa berkembang tanpa menjadi kacau.
π Rule 4 β Service is Domain Boundary
Service berada di tengah:
- menerima data dari driver
- menyediakan fungsi ke application
Contoh:
svc_sensor
svc_actuator
svc_comm
Service boleh:
- mengolah data sensor
- melakukan filtering sederhana
- menyediakan API seperti
getTemperature()
Service tidak boleh:
memutuskan mode sistem
mengatur state machine utama
mengakses ISR langsung
Alasan:
service harus tetap netral terhadap keputusan sistem
π Rule 5 β Application is Decision Layer
Application adalah tempat semua keputusan dibuat.
Contoh:
- jika suhu > threshold β nyalakan relay
- jika error β masuk fail-safe
Application:
- menggunakan service
- tidak langsung menyentuh hardware
Tidak boleh:
digitalWrite langsung
Wire.read langsung
Alasan:
memisahkan keputusan dari implementasi hardware
π Rule 6 β Composition Root Only in IndustrialNode.ino
Semua object dibuat hanya di:
IndustrialNode.ino
Contoh:
RelayDriver relay(5);
TempSensor sensor;
ControlApp app(sensor, relay);
Tidak boleh:
- membuat object di banyak file
- membuat object tersembunyi di dalam class lain
Alasan:
agar semua dependency terlihat di satu tempat
Setelah Artikel 4
Mulai titik ini, aturan tidak boleh dilanggar:
β tidak boleh ubah layer
β tidak boleh include melanggar arah
β tidak boleh shortcut antar layer
Jika dilanggar, efeknya biasanya:
- dependency sulit dilacak
- perubahan kecil merusak banyak file
- firmware sulit dikembangkan
Inti yang Harus Dipahami Engineer Baru
Layering ini bukan untuk βrapiβ, tetapi untuk:
membatasi dampak perubahan
Contoh:
- ubah driver β tidak merusak application
- ubah logic β tidak menyentuh hardware
- ganti sensor β hanya ubah driver/service
Jika boundary ini dijaga, firmware tetap bisa berkembang tanpa menjadi kacau.
4. Implementation Pattern (ESP32 Arduino Context)
4.1 Struktur File Final (Flat Arduino)
IndustrialNode/
β
βββ IndustrialNode.ino
β
βββ app_ControlApp.h
βββ app_ControlApp.cpp
β
βββ svc_SensorService.h
βββ svc_SensorService.cpp
βββ svc_ActuatorService.h
βββ svc_ActuatorService.cpp
β
βββ drv_RelayDriver.h
βββ drv_RelayDriver.cpp
β
βββ sys_Config.h
βββ sys_Status.h
Tidak ada nested folder.
Prefix adalah boundary.
4.2 Dependency Direction Diagram


Panah hanya satu arah:
Application β Service β Driver
Tidak ada panah naik.
4.3 IndustrialNode.ino (Composition Root)
#include "drv_RelayDriver.h"
#include "svc_ActuatorService.h"
#include "app_ControlApp.h"
RelayDriver relay(5);
ActuatorService actuator(relay);
ControlApp app(actuator);
void setup() {
app.init();
}
void loop() {
app.run();
}
Semua dependency terlihat eksplisit.
5. Constraint & Embedded Impact
Layering bukan hanya estetika arsitektur. Ia berdampak langsung pada reliability dan maintainability.
5.1 RAM Impact
Layering yang jelas:
- Mengurangi duplikasi state.
- Menghindari object tersebar tanpa owner.
- Mengurangi kebutuhan global buffer.
Driver kecil β Service agregasi β Application orkestrasi.
State terkonsolidasi.
5.2 Flash Impact
Struktur file terpisah memang menambah boilerplate, namun:
- Mengurangi include liar.
- Mengurangi template instantiation berulang.
- Menghindari code bloat akibat circular include workaround.
Binary lebih stabil.
5.3 Stack Impact
Tanpa layering:
- Application bisa langsung panggil driver.
- Driver bisa callback ke application.
- Stack chain menjadi tidak terkontrol.
Dengan layering DAG:
Call stack hanya turun.
Application β Service β Driver
Tidak pernah naik kembali.
Stack depth lebih mudah diprediksi.
5.4 Determinism Impact
Layering memaksa:
- ISR hanya notify (Artikel 5).
- Driver tidak tahu policy.
- Application tidak tahu register.
Akibatnya:
- Tidak ada side effect tersembunyi.
- Tidak ada dependency balik.
Determinism meningkat karena alur eksekusi menjadi eksplisit.
6. Failure Scenario
Layering bukan teori. Tanpa freeze ini, sistem akan kembali rusak.
Scenario 1 β Driver Tahu Logic Bisnis
Misalnya:
void RelayDriver::set(bool on) {
if (systemMode == AUTO) { // β
digitalWrite(pin_, on);
}
}
Driver tahu systemMode.
Jika nanti mode bertambah:
- Driver harus diubah.
- Refactor menyebar.
Dengan layering freeze:
Driver hanya hardware. Mode ditentukan di Application.
Scenario 2 β Service Include Application
#include "app_ControlApp.h" // β
Circular dependency muncul.
Dampak:
- Include guard rumit.
- Refactor hampir mustahil.
- Compile error misterius.
Dengan direction rule:
Service tidak boleh tahu Application.
Scenario 3 β Application Bypass Service
relay.set(true); // dipanggil langsung dari app
Batas domain dilanggar.
Akibatnya:
- Guard logic di Service dilewati.
- Logging tidak konsisten.
- Fail-safe bisa terabaikan.
Dengan rule:
Application hanya berbicara ke Service.
Scenario 4 β Circular Include Diam-Diam
File A include B. B include C. C include A.
Build masih jalan (karena forward declare), tapi dependency graph cyclic.
Refactor berikutnya meledak.
Dengan DAG rule:
Tidak mungkin terjadi.
7. Anti-Pattern
Daftar merah Artikel 4:
- β Driver include Service
- β Service include Application
- β Application akses hardware langsung
- β Circular include
- β File tanpa prefix layer
- β Multiple composition root
- β Instansiasi object tersebar
Dampak:
- Coupling meningkat
- Debug sulit
- Testability rendah
- Boundary kabur
8. Freeze Point
Setelah Artikel 4, keputusan berikut dianggap final:
- 3-layer model wajib (app*, svc*, drv* + sys*).
- Dependency direction hanya turun (DAG).
- Driver hanya hardware.
- Service hanya domain.
- Application hanya orchestration.
- Composition root hanya di
IndustrialNode.ino. - Tidak ada circular include.
Layering tidak boleh berubah di artikel berikutnya.
Artikel 5β8 harus patuh pada struktur ini.
9. Engineering Checklist
Audit sebelum menambah fitur:
- Apakah ada file tanpa prefix layer?
- Apakah ada include yang melanggar direction?
- Apakah Application akses driver langsung?
- Apakah Service tahu logic UI/policy?
- Apakah ada instansiasi object di luar
IndustrialNode.ino?
Jika satu saja βyaβ β melanggar Artikel 4.
10. Summary (5 Bullet Maksimal)
- OOP tanpa layering tetap bisa menjadi spaghetti.
- Dependency direction harus satu arah.
- Prefix file adalah boundary enforcement di Arduino flat structure.
- Composition root hanya satu.
- Layering freeze menentukan bentuk firmware jangka panjang.
Catatan Penyusunan Artikel ini disusun sebagai materi edukasi dan referensi umum berdasarkan berbagai sumber pustaka, praktik lapangan, serta bantuan alat penulisan. Pembaca disarankan untuk melakukan verifikasi lanjutan dan penyesuaian sesuai dengan kondisi serta kebutuhan masing-masing sistem.