- Published on
I built a small event-driven workflow for side projects
Jigar Patel
3 min read
I used to keep every automation as a standalone script. It worked—until it didn’t. When three scripts needed the same state, logs, and retry behavior, I had already paid the cost of a poor architecture.
I rebuilt the setup into a tiny event-driven architecture with two rules:
- treat every event as an immutable input contract,
- treat every handler as a replaceable unit.
The shape I use
This is the exact mental model I now follow:
Cron / Webhook / File change
|
[Event] --- schema validation ---> [Dispatcher]
|
---------
| | |
Handler A Handler B Handler C
| | |
Output artifacts, logs, metrics
I keep it deliberately boring. No distributed tracing, no heavy message queue, no framework I can’t explain in five minutes. Just clear message boundaries and deterministic retries.
Concrete layout
projects/
automations/
dispatcher/
parse-event.sh
route.sh
handlers/
screenshot-archiver/main.sh
note-indexer/main.sh
notify-daily/main.sh
schemas/
event.json
I treat each handler as pure as possible:
- read event payload,
- do one task,
- emit predictable output,
- return non-zero on failure.
Practical checklist I run before adding a new handler
- Do I already have an existing event for this action?
- Does this handler map to exactly one output type?
- Can I unit-test the schema validator with fixture JSON?
- Is retry safe (idempotent) for this action?
- Have I added a clear log format (
ts,eventId,handler,result)?
Minimal command loop
# validate payload before dispatch
jq -e '.eventType and .payload and .source' event.json >/dev/null
# route the event to one handler
./dispatcher/route.sh event.json screenshot-archiver >/tmp/dispatch.log 2>&1
# inspect standardized result
cat /tmp/dispatch.log
I still keep the dispatcher tiny on purpose. The complexity lives in the handler logic where I can test and reason about it.
What I learned
- Shared abstractions don’t scale unless the interface is stable.
- Contracts are more valuable than code reuse.
- Logs are architecture too; they are my operational memory.
Learning outcomes
- I can add a new workflow in under 10 minutes once the event contract exists.
- Failures are diagnosable without reading each handler source.
- I can safely rerun a handler because every action is idempotent.
What to improve next
- Add a tiny JSON schema linter job in CI.
- Add a dead-letter folder for failed events and auto-retry policy.
- Write a short
HANDLERS.mdwith owner and expected output format for each handler.