mk-josias / laravel-distributable-starter
Starter kit for laravel-distributable: a modular monolith deployable as one process or as one process per module.
Package info
github.com/mk-josias/laravel-distributable-starter
Type:project
pkg:composer/mk-josias/laravel-distributable-starter
Requires
- php: ^8.4
- firebase/php-jwt: ^7.0
- laravel/framework: ^13.8
- laravel/octane: ^2.20
- laravel/reverb: ^1.12
- laravel/tinker: ^3.0
- mk-josias/laravel-distributable: ^0.4
- mk-josias/laravel-distributable-rpc-http: ^0.4
- mk-josias/laravel-distributable-stream-redis: ^0.4
- spatie/laravel-data: ^4.0
- spatie/laravel-query-builder: ^6.0 || ^7.0
- wikimedia/composer-merge-plugin: ^2.1
Requires (Dev)
- fakerphp/faker: ^1.23
- larastan/larastan: ^3.12
- laravel/pail: ^1.2.5
- laravel/pao: ^1.0.6
- laravel/pint: ^1.27
- mockery/mockery: ^1.6
- nunomaduro/collision: ^8.6
- phpunit/phpunit: ^12.5.12
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A starter kit for a Laravel project: a Laravel application set up with
laravel-distributable. It comes with three example
modules, iam, analytics and notifications, each with its own database. You can read them to see how a module
is written, then replace them with your own.
composer create-project mk-josias/laravel-distributable-starter my-app
Requires PHP 8.4+, and Redis for the event stream.
create-project asks two questions, through php artisan skeleton:setup:
| Question | Default | What it changes |
|---|---|---|
| Which example modules do you keep? | all | removes analytics or notifications: its folders, its declaration, its tests, its auth.permissions entry and its Docker service. iam always stays: authentication, permissions and the user copies rely on it. |
| How does the gateway validate a token? | signature |
AUTH_TOKEN_VALIDATION_STRATEGY in .env, and a random AUTH_GATEWAY_SECRET. See Authentication. |
Without a terminal (--no-interaction), it keeps everything and signature. It runs once: it then
deletes itself and its line in composer.json, so it can never remove a module that has become
yours.
Running it
cp .env.example .env php artisan key:generate # the RSA pair iam signs tokens with, in storage/jwt-*.key php artisan auth:jwt-keys # migrates each module's database php artisan migrate # the permissions listed in auth.permissions, see Permissions php artisan iam:sync-permissions --prune php artisan serve
composer create-project runs every step but serve itself. The modules use sqlite files by
default (database/iam.sqlite, database/analytics.sqlite, database/notifications.sqlite).
migrate creates them.
Register a user in iam:
curl -X POST http://127.0.0.1:8000/iam/api/v1/register \
-H 'Accept: application/json' -H 'Content-Type: application/json' \
-d '{"name":"<name>","email":"<email>","password":"<password, 8 characters or more>"}'
{"success":true,"data":{"id":1,"name":"<name>"}}
Log in to get a token. It lasts an hour (AUTH_TOKEN_TTL); one session per device (X-Device-Id,
or else the user agent), which a new login there replaces:
curl -X POST http://127.0.0.1:8000/iam/api/v1/login \
-H 'Accept: application/json' -H 'Content-Type: application/json' \
-d '{"email":"<email>","password":"<password>"}'
{"success":true,"data":{"token":"<token>","expires_at":1790003600}}
register accepts 6 requests a minute from one address. login answers a wrong password and an
unknown email with the same 401, and 429 after 5 failures for one email from one address, for a
minute; a successful login clears them.
iam announces the registration with an event. Start the consumer; it serves analytics and
notifications, each with its own cursor:
php artisan stream:consume
analytics now has a signup and notifications a welcome message, and each keeps its own copy of
the user. A module reads only the X-Identity a gateway sends, never the token: without one in
front, sign it yourself with the gateway's secret, then send it:
# the X-Identity of user 1, valid for a minute php artisan tinker --execute='echo Foundation\Iam\Auth\TrustedIdentity::sign( ["sub" => 1], time() + 60, config("auth.gateway.secret"));' curl http://127.0.0.1:8000/analytics/api/v1/metrics \ -H 'Accept: application/json' -H 'X-Identity: <X-Identity>' curl http://127.0.0.1:8000/notifications/api/v1/notifications \ -H 'Accept: application/json' -H 'X-Identity: <X-Identity>'
The X-Identity lasts a minute. The Docker setups put Traefik in front, which does it on every request: there, send the token itself.
php artisan analytics:compute builds the hourly datasets (the scheduler runs it every hour), and
GET /analytics/api/v1/metrics?group=day reads them.
analytics_datasets is partitioned by month on PostgreSQL, MySQL and MariaDB, whichever the
analytics connection uses: php artisan analytics:prune --keep=12 drops the expired months whole.
On SQLite it is a plain table, and pruning deletes the rows.
What is in the skeleton
config/distributable.php declares the modules, and where they run when they run elsewhere;
calls and events keep the package's defaults; each module
declares its handlers in $handlers of its service provider
apps/
├── Iam/ owns the users
├── Analytics/ records signups, computes datasets, keeps a copy of the users
└── Notifications/ a personal inbox, fed by events, keeps a copy of the users
foundation/
├── FoundationServiceProvider.php
├── Common/ base classes every module uses
└── Iam/ what iam shares with the other modules
Where to go next
| You want to | Read |
|---|---|
read the three modules and foundation/Common |
The skeleton |
| see how a request is authenticated, or use your own identity provider | Authentication |
| declare and check permissions | Permissions |
| run one container or one per module | Docker |
| test a module alone | Tests |
| add code, a module or a library | Adding code to a module |
| run a module in its own process | Deploying modules |
License
MIT.