# Routing & Service Providers

**NUMBER:** ADR-ROUT-001
**STATUS**: CANONICAL AUTHORITY
**SCOPE:** All `routes/` files, all `*ServiceProvider.php` classes, and routing configuration files in this project.
**RULE:** This is the ONLY definitive source for route loading architecture and ServiceProvider creation rules. Any other document mentioning this topic is a REFERENCE, not a definition.
**VIOLATION:** Implementing routing or provider patterns based on any other source constitutes an architectural breach.

> **Security constraints (Rate limiting, Token handling, Auth middleware):** `00_CONSTITUTION_INDEX.md` → Security Mandates
> **Directory layout and domain routing structure:** `00_CONSTITUTION_INDEX.md` → Project Structure

---

## 1. Philosophy

**Abstraction On Demand.**

A Domain is a folder containing code, not a class that needs to be registered in the framework's service container. Routing is a purely technical operation (mapping HTTP to Controllers) that must be managed from one central place, leveraging filesystem conventions to load Domain route files automatically.

---

## 2. Absolute Prohibitions

```text
❌ PROHIBITED: Creating a ServiceProvider for a Domain if its ONLY function is loadRoutesFrom.
❌ PROHIBITED: Repeating the global Prefix (e.g., api/v1) or global Middlewares (e.g., auth:admin) inside individual Domain route files.
❌ PROHIBITED: Manually registering empty ServiceProviders in bootstrap/providers.php.
❌ PROHIBITED: Using Auto-Discovery (in composer.json) to register Domain ServiceProviders.
```

---

## 3. Centralized Routing Mechanism

Instead of creating a ServiceProvider per domain just to load routes, a single intelligent file handles the entire workload.

### 3.1 Rules

1. `routes/api.php` is the SOLE manager for loading Domain routes.
2. Global Prefixes and Global Middlewares are applied exactly once in this file.
3. It iterates over the `Domains` directory and dynamically requires any file matching `*/Routes/v1/api.php`.
4. The Domain route file is "dumb" — it knows nothing about the global prefix or global authentication.

### 3.2 Canonical Implementation: `routes/api.php`

```php
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Route;

Route::prefix('v1')
    ->middleware(['auth:admin', 'throttle:api'])
    ->group(function () {

        $domainsPath = base_path('app/Domains');
        
        foreach (File::directories($domainsPath) as $domainPath) {
            $routeFile = $domainPath . '/Routes/v1/api.php';
            
            if (File::exists($routeFile)) {
                require $routeFile;
            }
        }
    });
```

### 3.3 Canonical Implementation: Domain Route File

Notice how the file contains zero repetitive configuration:

```php
use App\Domains\Agent\Http\Controllers\AgentController;
use Illuminate\Support\Facades\Route;

Route::apiResource('agents', AgentController::class);
Route::get('agents/{agent}/financial-summary', [AgentController::class, 'financialSummary']);
```

---

## 4. Route Parameter Constraints (CANONICAL)

When using Route Model Binding with numeric database IDs (e.g., `BIGSERIAL`), Laravel's router must be explicitly constrained to prevent unnecessary regex checks for slugs or strings.

**Rule:** `->whereNumber('param')` is MANDATORY for all numeric route parameters.

```php
// MANDATORY
Route::get('agents/{agent}', [AgentController::class, 'show'])
    ->whereNumber('agent');

Route::put('agents/{agent}', [AgentController::class, 'update'])
    ->whereNumber('agent');

// Using apiResource (apply to specific methods)
Route::apiResource('agents', AgentController::class)
    ->whereNumber('agent');
```

```text
❌ PROHIBITED: Omitting ->whereNumber() for integer IDs. 
   (Reason: Allows Laravel to attempt string/slug matching, degrading routing performance).
```

---

## 5. ServiceProvider Creation Rule ("When")

A `ServiceProvider` for a Domain is created and registered **ONLY** when the Domain needs to register framework components that cannot be registered from any other location.

### Permitted Reasons to Create a ServiceProvider

| Permitted | Why |
|:---|:---|
| Registering Event Listeners specific to this Domain | Cannot be done centrally without coupling |
| Registering Artisan Commands specific to this Domain | Requires console detection context |
| Registering View Composers | Tightly bound to the Domain's views |
| Loading Domain-specific Config files | Requires path resolution |

### Prohibited Reasons to Create a ServiceProvider

| Prohibited | Why |
|:---|:---|
| Loading Routes | Handled centrally by `routes/api.php` |
| "Preparing" for future needs | YAGNI |
| Registering anything that can be done centrally | Creates unnecessary files |

### Justified Example

```php
namespace App\Domains\Customer;

use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use App\Domains\Customer\Listeners\SendWelcomeEmailListener;
use App\Domains\Customer\Events\CustomerCreated;

class CustomerServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::listen(
            CustomerCreated::class,
            [SendWelcomeEmailListener::class, 'handle']
        );
        
        if ($this->app->runningInConsole()) {
            $this->commands([
                \App\Domains\Customer\Console\Commands\RefreshCustomerListing::class,
            ]);
        }
    }
}
```

---

## 6. Bootstrap Providers Structure

After applying this constitution, `bootstrap/providers.php` must remain minimal and contain only what is strictly necessary.

```php
return [
    App\Providers\AppServiceProvider::class,
    
    // Domain Providers (ONLY those containing Listeners/Commands)
    App\Domains\Customer\CustomerServiceProvider::class,
    
    // All other Domains DO NOT get a ServiceProvider
];
```

---

## 7. Edge Cases

### 7.1 Excluding a Domain from Dynamic Loading

If a specific Domain requires a different routing strategy or should not be loaded dynamically, use a simple exclusion array in `routes/api.php`:

```php
$excludeDomains = ['Admin']; 

foreach (File::directories($domainsPath) as $domainPath) {
    $domainName = basename($domainPath);
    if (in_array($domainName, $excludeDomains)) continue;
    
    // ... rest of loading logic
}
```

### 7.2 Route Loading Order

Because the dynamic loader uses `File::directories`, alphabetical order is not strictly guaranteed. This is acceptable and by design: route loading order in Laravel does not matter as long as there are no route name conflicts.

---

## 8. Decision Cheat Sheet

1. Am I creating a new Domain and need to register its routes?
   └─ YES → Create `Domains/{Name}/Routes/v1/api.php`. Do NOT create a ServiceProvider.

2. Am I adding a global middleware (like Auth)?
   └─ YES → Add it ONCE in `routes/api.php`.

3. Am I defining a route with a numeric ID parameter?
   └─ YES → Add `->whereNumber('param')`.

4. Am I adding a Domain-specific Event Listener?
   └─ YES → Create a ServiceProvider for that Domain.

5. Does my Domain only have Controllers, Services, and Routes?
   └─ YES → It DOES NOT need a ServiceProvider.