Developer guides
Architecture
Follow the boundaries between Core, optional capabilities, provider adapters, and presentation.
On this page
A modular Laravel application
Agovena is a modular monolith. Core, enabled Modules, Extensions, and the selected Theme run inside one Laravel application. They are not separately deployed services. The public storefront and customer account use server-rendered views and Livewire; the versioned customer API provides a separate HTTP interface to selected operations.
The dependency constraints are recorded in composer.json: PHP ^8.3, Laravel ^13.8, Livewire ^4.4, and Sanctum ^4.3. Read the lockfile for the exact dependency set of your checkout. Frontend assets are compiled with Vite, while production requests are served by PHP.
Put a change in the right layer
| Layer | Owns | Example | Should not own |
|---|---|---|---|
| Core | Shared commerce models, application services, registries, security, package runtime | Order placement, invoices, payment normalization | A vendor-specific provisioning API |
| Module | An optional domain capability and its persistence | Event tickets | Every provider implementation for that capability |
| Extension | An adapter or contribution to an existing integration point | Payment gateway, shipping carrier, checkout requirement | A second order or invoice domain |
| Theme | Storefront and optionally Admin presentation | Layouts, native CSS, appearance settings, error pages | Authorization, stock reservation, payment state changes |
A product can carry multiple capabilities. Do not add a permanent store-type switch to Core to choose between physical products, digital delivery, and provisioning. Core Physical Commerce and Availability, for example, register the physical and availability product capabilities, keep stock in their own models, and subscribe to order events. See AvailabilityCapability and its runtime tests.
Repository map
agovena-platform/
app/Agovena/ Application services and public integration contracts
app/Http/ API controllers, resources, and middleware
app/Livewire/ Core server-driven screens
app/Models/ Shared persistence models
app/Events/ Core domain events
routes/ Web and versioned API registration
database/ Core migrations, factories, and seeders
themes/default/ Reference storefront and Admin presentation
tests/ Core and package integration coverage
optional-packages/
modules/<id>/ Optional domain packages
extensions/<group>/<id>/ Provider packages
Installed Modules are discovered first from storage/app/packages/modules, then from the configured optional-packages checkout, then from agovena.packages.extra_module_paths. Extensions use the corresponding extensions roots. The first discovered manifest with a given ID wins, so an installed copy can shadow a sibling development copy. Do not edit a sibling package and assume it is the copy the application is using.
For local package work, point Core at the sibling repository:
AGOVENA_OPTIONAL_PACKAGES_PATH=../optional-packages
This is discovery configuration, not package activation. A discovered package still needs installation and enablement. See Modules and Extensions.
Request and runtime flow
bootstrap/app.php registers the web, API, console, and health routes. It adds installation checks, locale handling, security headers, stateful Sanctum support, and the API IP policy. API exceptions are rendered as JSON rather than redirected to login.
AgovenaServiceProvider wires application services and registries. Outside unit tests, it boots enabled Modules before enabled Extensions. It then resolves the active Theme and registers the theme:: view namespace. An Admin surface can fall back to Default when the active Theme does not advertise Admin support.
A typical API request moves through:
- Route middleware for authentication, token ability, and throttling where applicable.
- A controller that validates input and resolves the authenticated customer.
- An application service that applies domain rules and owns persistence changes.
- A resource or explicit response serializer that selects the public fields.
The current v1 controllers validate through Request::validate(). Do not look for a separate API FormRequest class or infer accepted input from a model's fillable fields. Controllers, called services, and resources together define the contract.
Commerce and side effects
PlaceOrder coordinates configured cart lines, address data, pricing, shipping, discounts, taxes, customer properties, and idempotency. Clients submit selections and quantities, not authoritative totals. The API controller exposes only part of the service's input contract; a service parameter is not automatically an API field.
Payment initiation and payment confirmation are separate operations. HandlePaymentWebhook verifies the adapter's incoming message, persists an idempotent event, and applies normalized payment status. A browser redirect must not be treated as payment confirmation.
Internal event listeners implement domain consequences. Outbound HTTP webhooks are a distinct, queued delivery mechanism with an explicit event catalog. Do not expose every internal event as a public webhook by assumption. See Events and webhooks.
Boundaries to preserve
- Reuse application services from both Livewire and API surfaces instead of duplicating financial behavior.
- Scope customer-owned data in the query or controller. A token ability does not grant access to another customer's records.
- Register capabilities, navigation, providers, and requirements through the published context objects.
- Keep secrets out of resources, URLs, logs, Theme settings, and public assets.
- Test the disabled-package case as well as the enabled case. Optional routes are absent when their Module is not enabled.
- Treat v1 as an early, versioned interface, not a frozen compatibility guarantee.
Start with the API overview for integration work or the contribution workflow for a source change.