A minimal, production-friendly backend that accepts unique leads, stores them in MySQL, and asynchronously notifies a local mock endpoint via an Outbox + Worker pattern. Includes API-key auth, JSON logs (PII-masked, request-ID propagation, daily rotation), and a test suite.
- Leads API (create/read) with validation and duplicate protection
- Outbox Pattern: non-blocking notifications + worker retries (1s → 2s → 4s; max 3)
- Local Mock /mock/notify with toggleable outcomes (off|always|random)
- Security: protect endpoints with X-API-KEY
- Logs: JSON lines, daily folders, PII masking, request-ID propagation
- Feature flags from env (FEATURE_ASYNC_NOTIFY, FEATURE_CIRCUIT_BREAKER)
- Circuit breaker (simple, cache-backed) around notify sending
- Tests: unit + feature (composer test)
- CLI: scripts to send sample leads; curl/Postman included
.
├─ app/
│ ├─ Console/
│ │ ├─ Commands/
│ │ │ ├─ ProcessOutbox.php # worker (watch mode + graceful stop-file)
│ │ │ └─ StopOutbox.php # stop worker process
│ │ └─ Kernel.php
│ ├─ Events/
│ ├─ Exceptions/
│ │ ├─ Handler.php
│ │ └─ CircuitOpenException.php
│ ├─ Helpers/
│ │ ├─ Flags.php # env feature flag helper
│ │ └─ SimpleCircuitBreaker.php # tiny cache-backed circuit breaker
│ ├─ Http/
│ │ ├─ Controllers/
│ │ │ ├─ LeadController.php # /leads endpoints
│ │ │ ├─ MockController.php # /mock/notify
│ │ │ └─ HealthController.php # /health
│ │ └─ Middleware/
│ │ ├─ ApiKeyMiddleware.php # X-API-KEY guard
│ │ ├─ RequestIdMiddleware.php # request ID propagation
│ │ └─ AccessLogMiddleware.php # JSON access logs + PII masking
│ ├─ Jobs/
│ │ └─ NotifyLeadJob.php # calls /mock/notify (with circuit breaker)
│ ├─ Listeners/
│ ├─ Models/
│ │ ├─ Lead.php
│ │ └─ OutboxJob.php
│ ├─ Providers/
│ │ ├─ AppServiceProvider.php
│ │ ├─ AuthServiceProvider.php
│ │ └─ EventServiceProvider.php
│ ├─ Repositories/
│ │ ├─ LeadDispatcher.php # DB create/show for leads
│ │ └─ OutboxDispatcher.php # enqueue + fetch due outbox jobs
│ └─ Services/
│ ├─ HealthService.php # getting health
│ ├─ LeadService.php # Lead store/show
│ ├─ MockService.php # mock notify
│ └─ OutboxService.php # retries, backoff, logging
├─ bootstrap/
│ ├─ app.php
├─ config/
│ └─ jwt.php
├─ database/
│ ├─ factories/
│ ├─ migrations/ # leads + outbox_jobs
│ └─ seeders/ # leads seeder
├─ logs/ # rotated JSON logs (app.log)
│ └─ YYYY-MM-DD/
│ └─ app.log
├─ public/
│ ├─ index.php
│ └─ .htaccess # optional (Apache)
├─ resources/
├─ routes/
│ └─ web.php # /health, /leads, /mock/notify
├─ scripts/
│ ├─ send_leads.php # PHP CLI sender
│ ├─ send_leads.ps1 # PowerShell sender
│ └─ send_leads.sh # Bash sender
├─ storage/
├─ tests/
│ ├─ Feature/
│ │ ├─ FakeNotifyLeadJob.php
│ │ ├─ HealthTest.php
│ │ └─ LeadApiTest.php
│ └─ Unit/
│ └─ OutboxServiceTest.php
├─ .env.example
├─ .env # not committed
├─ composer.json
├─ Lead_API.postman_collection.json # postman collection exported
├─ phpunit.xml
└─ README.md
- PHP 8.1+ with pdo_mysql, mbstring, json, curl
- MySQL 8.x (or MariaDB ≥10.4)
- Compose
-
Create databases and user
- Create a new database named MyDB in MySQL.
- Configure a dedicated user account with appropriate permissions for this database. (For initial testing, the default MySQL user root with no password can be used, but it is strongly recommended to create a separate user with a secure password for production environments.)
-
Run Command
# Install
composer install# Environment
cp .env.example .env# Migrate
php artisan migrate# Run the server (Lumen has no artisan serve)
php -S 127.0.0.1:8000 -t public# Health check
curl http://127.0.0.1:8000/health- All endpoints except /health and /mock/notify require:
X-API-KEY: <your .env API_KEY>- For traceability, you can pass a request id:
X-Request-Id: <any-unique-id>- POST /leads enqueues an outbox_jobs row and returns immediately.
- The worker processes due jobs, calling /mock/notify.
- Retries with backoff 1s → 2s → 4s - max 3 attempts - failed jobs kept with last_error.
Run once:
php artisan outbox:processRun continuously with graceful stop:
php artisan outbox:process --watch --sleep=2
# in another terminal:
php artisan outbox:stop- Bash scripts/send_leads.sh
chmod +x scripts/send_leads.sh
API_KEY=apikeysupersecret0925 HOST=http://127.0.0.1:8000 ./scripts/send_leads.sh 3- PowerShell scripts/send_leads.ps1
$env:HOST="http://127.0.0.1:8000"
$env:API_KEY="apikeysupersecret0925"
.\scripts\send_leads.ps1 -Count 3- PHP CLI
php scripts/send_leads.php 3
# or set envs:
HOST=http://127.0.0.1:8000 API_KEY=apikeysupersecret0925 php scripts/send_leads.php 3- Or Singl curl
curl -X POST http://127.0.0.1:8000/leads \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "X-API-KEY: apikeysupersecret0925" \
-H "X-Request-Id: demo-abc-123" \
-d '{"email":"user+'$(date +%s)'@example.com","first_name":"John","last_name":"Doe","phone":"+12025550123","utm_source":"curl"}'- JSON logs written to: logs/YYYY-MM-DD/app.log
- Each line includes: timestamp, level, request_id, route, status, message
- Access logs mask email/phone
- Request ID flows HTTP → outbox payload (_rid) → worker → mock (searchable across logs)
-
FEATURE_ASYNC_NOTIFY=off → disables enqueue (synchronous no-op)
-
FEATURE_CIRCUIT_BREAKER=on → enables simple breaker around notify
- Opens after CB_FAIL_THRESHOLD consecutive failures
- Stays open CB_OPEN_SECONDS seconds, then allows a trial
- Success resets; failure re-opens
To simulate failures:
MOCK_NOTIFY_FAIL_MODE=alwaysThen flip back to off to see recovery.
Run all tests:
composer test