Architecture & systems
How the whole system fits together
You already have the building blocks — modules, customizations, schema extensions, the three-tier admin map. This guide steps back and shows the whole machine: how those pieces connect, what holds them together, and why the arrangement keeps a vendor app update-safe for years. Read it once to get the mental model, then drop into the handbook pages and playbooks where you actually build.
Why it matters
Section titled “Why it matters”A CodeCanyon app is someone else’s codebase that you license and keep upgrading. The naive way to extend it — edit the vendor’s files in place — wins today and loses on every release: each update becomes a merge battle. The Zajaly architecture exists to make one promise true: the vendor can ship update after update while everything you built survives untouched. That promise only holds because of how the pieces fit, not any single trick.
Think of it like LEGO blocks. Each feature is a self-contained brick that snaps onto the app without gluing itself to the vendor’s code. Add a brick, remove a brick, or swap one for another — the structure never cracks.
The one rule the whole system obeys
Section titled “The one rule the whole system obeys”Every pattern in this architecture is a different expression of one idea:
Listen, don't edit.Extend, don't modify.Isolate, don't scatter.Toggle, don't redeploy.If a change ever forces you to break one of those lines, that is the signal you reached for the wrong lane. The rest of this guide is just how the system makes each line cheap to follow.
The four pillars
Section titled “The four pillars”Four principles carry the whole design. Internalize these and every specific rule downstream becomes obvious.
| Pillar | What it means | What it buys you |
|---|---|---|
| Single-toggle control | One flag turns any feature on or off | Emergency kill switch in seconds, no deploy |
| Feature isolation | Each feature is its own self-contained package | Find it, update it, delete it — all in one folder |
| Event-driven integration | Features listen to events; they never edit core code | Vendor updates are a clean git pull, zero merges |
| Defensive coding | Never assume a field exists or has the type you expect | Third-party data quirks degrade gracefully, not crash |
Two of those pillars carry the most weight, so they’re worth a closer look.
Event-driven integration — the engine
Section titled “Event-driven integration — the engine”Laravel already fires events for nearly everything that happens in the app — a user registers, logs in, verifies email, resets a password. Instead of editing the vendor’s controller to bolt on your behavior, your feature subscribes to the event the app already emits and reacts to it. The vendor never knows your code exists.
graph TD Vendor["Vendor app fires an event<br/>(e.g. User Registered)"] Vendor --> A["Module A listens<br/>→ sync user to a CRM"] Vendor --> B["Module B listens<br/>→ send to analytics"] Vendor --> C["Module C listens<br/>→ write an audit log"] A --> Done["Vendor update? Just pull.<br/>Nothing to merge."] B --> Done C --> DoneThe arrows only ever point away from the vendor. Core code emits; your modules consume. Because nothing flows back into the vendor’s files, the next release drops in clean. That single property is what turns a 3-hour merge-conflict afternoon into a git pull.
Defensive coding — the seatbelt
Section titled “Defensive coding — the seatbelt”The catch with integrating against someone else’s app: you can’t trust the shapes. A date field might be a plain string instead of a Carbon object; a column you expect might not exist; a relationship might be undefined. The discipline is three habits — never assume, always check, fall back safely — so one surprise from the vendor degrades quietly instead of taking the app down.
The flow of a request
Section titled “The flow of a request”Here’s how a single module actually runs when an event fires — the five roles every Laravel feature plays, entry to action:
graph LR SP["Service Provider<br/>(entry point: registers<br/>the module + listeners)"] L["Listener<br/>(catches the event)"] S["Service<br/>(your business logic<br/>+ API calls)"] CFG["Config<br/>(flags + keys, read first)"] SP --> L L --> S CFG -.->|"gates every step"| SP CFG -.->|"gates every step"| LRead it left to right: the service provider wires the module up at boot, a listener waits for the relevant event, and when it fires the service does the real work. The config sits underneath all of it — every layer checks the enabled flag first, so a single toggle silences the whole chain. (React and Node express the same five roles with hooks/middleware instead of providers/listeners — the discipline travels; only the syntax changes.)
The control model — six levels of “is this on?”
Section titled “The control model — six levels of “is this on?””“Single-toggle control” is the headline, but in a real SaaS you need graduated control, not just one switch. The same feature can be gated at six levels, each answering a different question:
| Level | Where it’s decided | Answers |
|---|---|---|
| Global | .env flag | Is this feature on at all? |
| Feature | Config array | Which sub-features are on? |
| Plan | Middleware | Does this subscription tier get it? |
| User | Database flag | Is this specific user (beta) allowed? |
| Admin | Super-admin check | Bypass — admins always get it |
| Kill switch | .env flag | Emergency off, no deploy |
This is what lets one architecture serve a free-vs-pro pricing model, a gradual percentage rollout, and an instant incident response — all from the same module, no rewiring. The kill switch is the safety net that makes the whole thing fearless: discover a problem, flip one flag, and the feature stops processing immediately while you investigate.
How styling stays modular too
Section titled “How styling stays modular too”The same “isolate, don’t scatter” rule applies to CSS and JS, not just PHP. Each feature keeps its stylesheet with the feature, and a single central loader pulls in only the enabled ones. Two ideas hold it together:
- One loader line. Layouts include a single partial; the loader includes each module’s assets only when that module is enabled. Add a feature → no layout edits. Delete a feature → its CSS vanishes with the folder, no orphans left behind.
- Central design tokens. Every module’s CSS references shared CSS-variable tokens (
--zajmod-primary,--zajmod-bg, …) instead of hardcoding colors. Change a brand color in one tokens file and every feature updates — light and dark mode included, automatically.
The payoff mirrors the code side: adding a brick never forces you to touch the rest of the wall.
Universal vs Laravel-specific
Section titled “Universal vs Laravel-specific”The architecture is a philosophy, not a framework — the principles hold in any stack; only the implementation changes. Knowing which is which keeps you from copying a Laravel detail into a React app where it doesn’t belong:
| Concept | Travels everywhere? | In Laravel it looks like… |
|---|---|---|
| Single-toggle control | Yes | .env flag + config check |
| Feature isolation | Yes | packages/ZajModules/ folder |
| Event-driven integration | Mostly (varies by stack) | Event listeners |
| Defensive coding | Yes | ??, isset(), safe accessors |
| Compliance-ready design | Yes | Per-module consent/audit config |
| Kill switch & rollback | Yes | .env flag + Deployer rollback |
| CSS token system | Yes | Central tokens.css variables |
| Service Provider pattern | No — Laravel | React uses hooks; Node uses middleware |
| Blade views | No — Laravel | React uses components |
The takeaway: the discipline is portable; the paths and primitives are not. Build the next app in a different stack and you keep the four pillars, swap the syntax.
Where each piece is taught and executed
Section titled “Where each piece is taught and executed”This guide is the map. Each system on it has its own home — a handbook chapter that goes deep on the why, or a playbook that walks the how. Use this as your jump table:
| System | Learn the detail | Execute it |
|---|---|---|
| Module vs customization vs schema | Modules vs customizations | Customizations playbook |
| Where ops files live | Three-tier admin model | — |
| Data the vendor doesn’t store | Schema management | Customizations playbook |
| Compliance, secrets, defense-in-depth | Security & compliance | Security & monitoring |
| Shipping a change safely | Deployment concepts | Continuous deploy |
| Surviving a vendor release | Version management | Vendor updates |