Why a monorepo?
Exowpee uses a monorepo with Laravel packages to organize domain logic. Instead of putting everything inapp/, each business domain lives in its own package under packages/.
Benefits:
- Isolation — Each package has its own models, routes, migrations, and tests
- Reusability — A package can theoretically be extracted to its own repository
- Clear boundaries — Inter-package communication happens through interfaces and contracts
- Independent testing — You can test one package without loading others
- Team scalability — Different developers can own different packages
How packages are registered
Packages are registered as Composer path repositories in the rootcomposer.json:
composer.json with a ServiceProvider that Laravel auto-discovers.
Package anatomy
Every package follows a standard structure:What the ServiceProvider does
Each package’sServiceProvider is the entry point that registers everything with Laravel:
Inter-package communication
Packages communicate through interfaces (contracts), never by directly importing another package’s internals.Pattern: Contract-based communication
Why this pattern?
- Packages never directly
usemodels or services from other packages - You can swap implementations without changing consumers
- Testing is easier — mock the interface
Package dependencies
Theshared package is the base dependency — it provides:
KsuidTrait— KSUID primary key generationHashPasswordTrait— Password hashingOptimisticLockingTrait— Concurrency controlBeforePolicyFilterTrait— Pre-authorization checksAuditableTrait— Activity logging (wraps Spatie’sLogsActivity)ExowpeeCompanyCachableTrait— Company-level caching behaviorExowpeeOrganizationCachableTrait— Organization-level caching behaviorWorkingHourTrait— Working hour management for companiesRestApiFormRequest— Base form request with JSON error handling- Helper functions (
isKsuid(),uploadFile2(),toMinorUnit(), etc.)