# Boundary Layers — Resources, Jobs, Filters, Controllers & Responses

**NUMBER:** ADR-BOUND-001
**STATUS:** CANONICAL AUTHORITY
**SCOPE:** Every class that formats outputs (Resources), executes asynchronously (Jobs), applies dynamic query conditions (Filters), orchestrates HTTP requests (Controllers), or constructs API responses.
**RULE:** This is the ONLY definitive source for boundary layer architectures. Any other document mentioning this topic is a REFERENCE, not a definition.
**VIOLATION:** Implementing boundary layer patterns based on any other source constitutes an architectural breach.

> **When to use a Query Class vs a Response Builder for reads:** `04_DATABASE_COMMUNICATION.md` → Sections 4.2 and 4.3
> **DTO Taxonomy (what data crosses these boundaries):** `09_DTOS.md`
> **When to create an Action vs calling Service directly:** `10_ACTION_SERVICE_ORCHESTRATION.md`

---

## 1. Philosophy

**Strict Technical Confinement.**

These layers are the system's interface with the external world (HTTP clients, background workers, filesystems). Any business logic inside them is an architectural flaw caught in Code Review. Their sole purpose is mechanical transformation, delegation, or execution of isolated technical tasks.

---

## 2. API Resources

### 2.1 Prohibitions

```text
❌ PROHIBITED: Executing any database queries inside the Resource.
❌ PROHIBITED: Calling any Service or Action from inside the Resource.
❌ PROHIBITED: Performing business calculations (e.g., tax, profit, penalty logic).
❌ PROHIBITED: Using $this->when() for complex conditional logic.
```

### 2.2 Allowed Responsibilities

- Key renaming (e.g., `created_at` → `registeredAt`).
- Format transformation (e.g., `$this->created_at->format('Y-m-d')`).
- Hiding internal fields from the response.
- Reading properties from the underlying Model or DTO.

### 2.3 N+1 Prevention Rule

Never access a relationship directly without confirming it was eager-loaded.

```php
// PROHIBITED — causes N+1 on list endpoints
'customer_name' => $this->customer->name,

// MANDATORY — safe regardless of context
'customer_name' => $this->whenLoaded('customer', fn() => $this->customer->name),
```

**Rule:** The responsibility for Eager Loading belongs to the Query Class or the Controller—NEVER the Resource.

### 2.4 Resource vs Output DTO

The Resource does not know or care whether its underlying object is an Eloquent Model or a DTO. It simply reads public properties.

- **Source is Eloquent Model:** Use Resource directly (`ContractResource::make($contract)`).
- **Source is aggregated DTO:** Resource reads the DTO's public properties (`FinancialSummaryResource::make($dto)`).

### 2.5 Reading from `fromArray()` DTOs (CANONICAL)

When a DTO is built using the magic `fromArray()` method, the Resource reads its properties normally. The magic spread operator `new self(...$data)` guarantees a true object with public properties.

```php
// Inside Resource — Standard property access works because DTO is an object
'id' => $this->resource->id,
'name' => $this->resource->name,
```

**Fallback Rule:** If `fromArray()` cannot be implemented magically (due to type casting or missing keys), and the DTO stores a raw array, the Resource MUST read via array syntax:
```php
// ONLY when DTO strictly holds an internal array and magic construction is impossible
'id' => $this->resource['id'],
```

### 2.6 `make()` vs `collection()` (CANONICAL)

Using the wrong method causes fatal errors or data wrapping corruption.

| Method | Target | Behavior | Use Case |
|:-------|:--------|:----------|:---------|
| `Resource::make($item)` | Single Item or Paginator | Passes `$item` exactly as `$this->resource` | Single Model, Single DTO, **Paginator** |
| `Resource::collection($items)` | Array or Collection | Wraps **every element** in a new Resource instance | Raw Collection, Array of items |

```text
❌ PROHIBITED: Resource::collection($paginator)
   (Reason: It tries to wrap the paginator object itself, not the items inside it. Causes "nextCursor does not exist" errors).

✅ MANDATORY: Resource::make($paginator)
   (Reason: Paginator is a single object. The Resource iterates its internal items correctly).
```

---

## 3. Controller Confinement

The Controller is a thin orchestrator. It translates HTTP requests into application calls, and application results into HTTP responses.

### 3.1 What the Controller DOES

| Responsibility | Example |
|:---|:---|
| Receive validated input from FormRequest | `$request->only(['name', 'phone'])` |
| Route to correct Service (write) or Builder (read) | Based on endpoint type |
| Assemble final response shape | Merge Service output + Resource + extra fields |
| Return via Trait method | `$this->success(data: ..., msg: ...)` |

### 3.2 What the Controller DOES NOT DO

```text
❌ Execute DB queries.
❌ Validate business rules.
❌ Perform calculations.
❌ Transform data without a Resource.
❌ Contain conditional business logic (if/else determining domain state).
❌ Use inline response()->json().
❌ Use $request->validated() (See Section 3.4).
```

### 3.3 Response Construction (CANONICAL)

All Controllers extend a centralized Base Controller that uses the `HasApiResponse` Trait. Responses are NEVER built using raw helpers or facades directly in the Controller.

```php
// MANDATORY: Via Trait in Controller
return $this->success(data: $result, msg: __('success/general.operation_completed'));

// PROHIBITED
return ApiResponse::success($result, 'Operation completed');
return response()->json(['success' => true, ...]);
```

### 3.4 Input Extraction Rules (CANONICAL)

```text
❌ PROHIBITED: $request->validated()
   (Reason: Returns all fields defined in rules(), including ones the DTO doesn't need. Breaks precise data flow).

✅ MANDATORY: $request->only(['field1', 'field2'])
   (Use for: Building DTOs. Returns ONLY what was sent in the payload).

✅ PERMITTED: $request->input('field')
   (Use for: Passing single primitive parameters to Queries/Services).
```

---

## 4. Response Architecture

### 4.1 Unified Response Format

All API responses MUST follow this exact structure:

```json
{
    "success": true,
    "message": "string",
    "data": {},
    "meta": {},
    "errors": {}
}
```

| Field | Presence Rule |
|:---|:---|
| `success` | Always present (boolean) |
| `message` | Always present (string) |
| `data` | Present on success, `null` on error |
| `meta` | Present ONLY for paginated responses |
| `errors` | Present ONLY on error responses (e.g., 422) |

### 4.2 ApiResponseFormatter (Single Owner)

Response formatting is handled by ONE class only: `ApiResponseFormatter`.

**Rules:**
1. Contains internal `_response()` method and two public methods: `success()` and `error()`.
2. **NO semantic shortcuts** — no `forbidden()`, `notFound()`, `unprocessable()`.
3. Registered as a Singleton and accessed via `HasApiResponse` trait in Controllers.

### 4.3 CursorMeta Resource (Standalone Utility)

Pagination metadata is extracted using a dedicated API Resource class, NOT a global helper.

```
Controller → CursorMeta::make($paginator) → JSON meta object
         → $this->success(data: ..., meta: CursorMeta::make($paginator)->resolve())
```

**Location:** `App\Http\Resources\CursorMeta.php` (Shared across all domains).

```text
❌ PROHIBITED: Using a global helper like PaginationHelper::cursorMeta().
✅ MANDATORY: Using the CursorMeta Resource class.
```

---

## 5. Queued Jobs

### 5.1 Prohibitions

```text
❌ PROHIBITED: Orchestrating complex business flows (e.g., create contract + deduct balance + send email in one Job).
❌ PROHIBITED: Throwing Business Exceptions that stop the user (the user left the page; errors go to logs only).
❌ PROHIBITED: Passing non-serializable objects (Request instances, Closures, Eloquent Models with unserialized relations).
```

### 5.2 Job vs Event/Listener

| Use Case | Pattern | Why |
|:---|:---|:---|
| "Something happened, attach reactions later" | Event/Listener | Multiple listeners can be added later without touching the source |
| "Specific background task with no event semantics" | Job directly | No announcement needed—just execute the task (e.g., RefreshMaterializedViewJob) |

### 5.3 State Isolation

The Job runs in a separate process. Pass IDs or DTOs—NOT Eloquent Models (which may carry stale data between dispatch and handle).

```php
// RISKY — model data may change between dispatch and handle
public function __construct(public Contract $contract) {}

// MANDATORY — Job rebuilds fresh context from database
public function __construct(public int $contractId) {}
```

### 5.4 Failure Handling

| Operation Type | `tries` | `backoff` | Rationale |
|:---|:---|:---|:---|
| Internal database writes | 1 | — | Failure means structural issue; retry causes data corruption |
| External API calls | 3 | `[30, 60, 120]` | Network may recover; retry is safe |

---

## 6. Query Filters

### 6.1 Prohibitions

```text
❌ PROHIBITED: Returning data (The Filter returns Builder, NEVER Collection).
❌ PROHIBITED: Write operations (Insert/Update/Delete).
❌ PROHIBITED: Calling other Filters (Orchestration belongs to Pipeline).
❌ PROHIBITED: Placing Domain-specific Filters in a Shared location.
```

### 6.2 Pipeline Pattern

Filters are components used EXCLUSIVELY inside Query Classes via Laravel's `Pipeline`.

```php
// Inside a Query Class
$pipes = [
    new CustomerOwnershipFilter($customerId),
];

if ($status) {
    $pipes[] = new ContractStatusFilter($status);
}

return Pipeline::send(Contract::query())
    ->through($pipes)
    ->thenReturn()
    ->get();
```

### 6.3 Filter Location

| Filter Type | Location | Example |
|:---|:---|:---|
| Generic (date range, text search) | `Shared/Filters/` | `DateRangeFilter`, `SearchFilter` |
| Domain-specific (agent contracts) | `Domains/{Domain}/Filters/` | `AgentContractFilter` |

### 6.4 Controller Isolation

The Controller NEVER instantiates or references Filters. It passes raw values to the Query Class, which constructs the Filter pipeline internally.

---

## 7. Localization Confinement

### 7.1 Folder Structure

Translations are organized by context folders, not flat files.

```
lang/
├── ar/
│   ├── errors/       ← client.php, contract.php, payment.php
│   ├── success/      ← customer.php, contract.php
│   └── validation.php
└── en/
    └── (mirror structure)
```

### 7.2 Key Naming Convention

- **Forward slash `/`** for folder paths: `__('errors/client.phone_unique')`
- **Dots `.`** ONLY for nested keys within the SAME file: `__('validation.max.string')`

### 7.3 Execution Rule

Translation keys are resolved server-side before sending the response. The API consumer receives translated text, never raw keys.

---

## 8. File Handling Confinement

### 8.1 Centralized FileService

All file operations MUST go through the `FileService` Facade. NEVER use direct Storage facade or URL helpers.

**Usage:** `FileService::upload($file, 'avatars')`, `FileService::url($path)`, `FileService::delete($path)`, `FileService::replace($file, 'avatars', $oldPath)`

### 8.2 Data Flow Across Layers (CANONICAL)

The file upload is an HTTP concern. It MUST be resolved in the Controller before crossing into the DTO.

```text
Upload Flow:
├─ Controller: $path = FileService::upload($request->file('avatar'), 'avatars');
├─ DTO: Receives ?string $avatar (The string path, NOT UploadedFile)
├─ DTO::toModelArray(): Includes ['avatar' => $this->avatar]
└─ Service: Knows nothing about files, just saves the string path to DB.

Replace Flow:
├─ Controller: $path = FileService::replace($file, 'avatars', $oldPath);
├─ DTO: Receives ?string $avatar
└─ Service: Saves the new string path.

URL Generation Flow:
├─ Controller/Resource: FileService::url($model->avatar)
└─ Service: NEVER calls FileService::url()
```

### 8.3 Prohibitions

```text
❌ PROHIBITED: Passing UploadedFile instances into DTOs.
❌ PROHIBITED: Calling FileService::upload() or ::replace() inside Services or DTOs.
❌ PROHIBITED: Calling FileService::url() inside Services or DTOs.
```

### 8.4 Naming Convention

**Format:** `{Y-m-d}_{25-char-random}.{extension}`
- Date from server (not client).
- Random string: exactly 25 alphanumeric characters.
- Original filename is NEVER used (security + uniqueness).

### 8.5 API Response Rules

| Rule | Detail |
|:---|:---|
| Database stores | Relative path only (e.g., `avatars/2024-01-15_abc.jpg`) |
| API returns | Full URL only (e.g., `avatar_url`) |
| Null handling | If no file: return `null` for the URL field |
| URL generation | ALWAYS via `FileService::url()` — never `asset()` or `Storage::url()` directly |

---

## 9. Decision Cheat Sheet

**Resources:**
```text
Need to transform data for the API?
├─ Data is a single Model? → Resource reads the Model directly.
├─ Data is an aggregated report? → Create an Output DTO first, then Resource reads the DTO.
├─ Data is a Paginator? → Use Resource::make($paginator) — NEVER ::collection().
└─ Am I calling a DB query inside the Resource? → ERROR. Move to Query Class or Builder.
```

**Jobs:**
```text
Must this complete synchronously for the request to succeed?
└─ YES → Do NOT use a Job. Put it in the Action.

Is this a reaction to an event? → Use Event/Listener.
Is this an isolated background maintenance task? → Use Job directly.
```

**Filters:**
```text
Is the WHERE condition driven by dynamic user input? → Filter Class with Pipeline.
Is the WHERE condition always the same (e.g., active contracts only)? → Model Scope.
Am I passing a Filter instance from the Controller? → ERROR. Controller passes raw values to Query Class.
```

**Controllers:**
```text
Am I executing a DB query? → ERROR. Belongs in Scope, Query Class, or Builder.
Am I calculating a business value? → ERROR. Belongs in Service.
Am I using $request->validated()? → ERROR. Use $request->only().
Am I merging Service output with a Resource? → CORRECT. Return via $this->success().