Why a monorepo?

Exowpee uses a monorepo with Laravel packages to organize domain logic. Instead of putting everything in app/, 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 root composer.json:
Each package has its own composer.json with a ServiceProvider that Laravel auto-discovers.

Package anatomy

Every package follows a standard structure:

What the ServiceProvider does

Each package’s ServiceProvider 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

Example: The Order package needs to check stock from the Catalog package:
The binding happens in the ServiceProvider:

Why this pattern?

  • Packages never directly use models or services from other packages
  • You can swap implementations without changing consumers
  • Testing is easier — mock the interface

Package dependencies

The shared package is the base dependency — it provides:
  • KsuidTrait — KSUID primary key generation
  • HashPasswordTrait — Password hashing
  • OptimisticLockingTrait — Concurrency control
  • BeforePolicyFilterTrait — Pre-authorization checks
  • AuditableTrait — Activity logging (wraps Spatie’s LogsActivity)
  • ExowpeeCompanyCachableTrait — Company-level caching behavior
  • ExowpeeOrganizationCachableTrait — Organization-level caching behavior
  • WorkingHourTrait — Working hour management for companies
  • RestApiFormRequest — Base form request with JSON error handling
  • Helper functions (isKsuid(), uploadFile2(), toMinorUnit(), etc.)

Adding a new package

See the Package Development guide for step-by-step instructions on creating a new package.