Architecture Overview
This page explains the high-level architecture: the layers, how they connect, and how a request flows through the system.
Table of Contents
- The Three Main Layers
- Project Structure
- Request Flow (Step-by-Step)
- CodeIgniter 4 Lifecycle
- Dependency Direction
- Contracts & Interfaces
- Verifying Your Setup
1. The Three Main Layers
Technical Concept
The architecture is organized into three main layers, each with a specific responsibility:
Simple Analogy
Think of it like a restaurant:
| Layer | Restaurant Analogy | In Code |
|---|---|---|
| Presentation | Waiter: Takes orders, serves food, interacts with customers | Controllers, Views |
| Domain | Chef: Knows recipes, cooking rules, food combinations | Entities, Services, Policies |
| Infrastructure | Pantry/Suppliers: Stores ingredients, gets deliveries | Repositories, Cache, APIs |
The waiter doesn't cook. The chef doesn't serve tables. The pantry doesn't know recipes. Each has one job.
2. Project Structure
Full Directory Layout
What Goes Where?
Common Confusion: Modules vs Domain
- Modules (Presentation): "How the user interacts". If you delete a Module (e.g., Api), the specific interface is gone, but the business logic (Domain) remains safe.
- Domain (Business): "What the business does". If you copy the `Domain` folder to a completely different framework (e.g., Laravel), it should still describe your business rules perfectly.
| I'm Writing... | Put It In... | Example | Why? |
|---|---|---|---|
| A new page/screen | Modules/{Feature}/... | Modules/Admin/User/Controllers/UserController.php | It's part of the UI/Interaction layer. |
| A complex business rule | Domain/{Context}/Services/ | Domain/Order/Services/OrderService.php | To keep it reusable and testable. |
| A data structure | Domain/{Context}/Entities/ | Domain/User/Entities/User.php | To define what a "User" looks like in your code. |
| Database query | Infrastructure/Persistence/ | UserRepository.php | To keep SQL separate from logic. |
FAQ: Clarifications
1. What is a "Service"?
A Service is a class that orchestrates a business operation. It is the "Command Center" for a specific task.
- Controller: "Receive request, validate input, call Service, return View." (The Manager)
- Service: "Check stock, calculate tax, save order, send email." (The Coordinator)
- Repository: "Save this data to the database." (The Storage Worker)
2. Is an Entity the same as a Database Table?
Not exactly.
- Database Table: How data is stored (has foreign keys, IDs, implementation details).
- Entity: How data is used in your code (has methods, logic, consistent state).
Often they look similar (1:1 mapping), but an Entity might have extra methods (e.g., `User::getFullName()`) or combine data from multiple tables.
3. Why use Domain Layer? (Reuse)
By keeping logic in `Domain`, you can reuse it easily:
// In Web Controller
$userService->register($data);
// In API Controller
$userService->register($data);
// In CLI Command
$userService->register($data);The logic (validation, hashing password, sending email) is written once in the Service, not duplicated in 3 controllers.
3. Request Flow (Step-by-Step)
Scenario: User visits /admin/users
Let's trace exactly what happens:
Docker: Seeing This in Action
# Watch PHP logs while making a request
docker-compose logs -f php
# Make a request
curl http://localhost:81/admin/users
# You'll see logs like:
# [INFO] Route matched: Admin\User\Controllers\UserController::index
# [DEBUG] TracingFilter: Started span for GET /admin/users
# [DEBUG] PerformanceFilter: Recorded 85ms for Admin::UserController::index4. CodeIgniter 4 Lifecycle
Understanding the strict lifecycle order helps debugging:
Browser Request
↓
index.php (Public)
↓
Boot Framework
↓
Pre-System Hooks
↓
Routing (file, attributes, or automatic)
↓
Before Filters (Auth, Role, etc.)
↓
Controller
↓
Service
↓
Repository
↓
Infrastructure (DB, Redis, Mail)
↓
Return Response
↓
After Filters
↓
Post-System Hooks
↓
Output to Browser
4. Dependency Direction
Technical Concept
A key rule: dependencies point inward. Outer layers know about inner layers, but not vice versa.
What This Means in Code
// CORRECT: Controller knows about Domain
namespace App\Modules\Admin\User\Controllers;
use App\Domain\User\Services\UserService;
// CORRECT: Domain defines interfaces (doesn't know implementations)
namespace App\Domain\User\Services;
use App\Domain\User\Repositories\UserRepositoryInterface;
// CORRECT: Infrastructure implements Domain interfaces
namespace App\Infrastructure\Persistence;
use App\Domain\User\Repositories\UserRepositoryInterface;
class UserRepository implements UserRepositoryInterface { }
// WRONG: Domain knowing about Infrastructure
namespace App\Domain\User\Services;
use App\Infrastructure\Persistence\UserRepository; // Dependency Violation
// WRONG: Domain knowing about Presentation
namespace App\Domain\User\Entities;
use CodeIgniter\HTTP\Request; // Dependency ViolationWhy This Matters
| Benefit | Example |
|---|---|
| Testability | Test Domain without database (use mock repository) |
| Flexibility | Switch from MySQL to PostgreSQL without changing Domain |
| Clarity | Business rules are in one place, not scattered |
6. Contracts & Interfaces
Technical Concept
We place global interfaces under app/Contracts and domain-specific interfaces in app/Domain/{Context}/Repositories.
Rule: Always program to interfaces, not concrete classes.
// Bad: Coupled to specific implementation
public function __construct(RedisCache $cache) { }
// Good: Coupled to contract (can swap Redis for File/Memcached)
public function __construct(CacheInterface $cache) { }5. Verifying Your Setup
Check Architecture Integrity (Automated Tests)
Your project includes custom spark commands to automatically verify all files adhere to the strict architecture rules:
# Check DDD layer dependencies (Domain vs Infrastructure vs Modules)
docker-compose exec php php spark arch:test
# Check Component guidelines (PHP helpers & JS IIFE namespaces)
docker-compose exec php php spark comp:test
# Check File Naming conventions (PascalCase, snake_case, kebab-case)
docker-compose exec php php spark naming:testCheck Folder Structure
# List main directories
docker-compose exec php ls -la /var/www/html/app/
# Expected output:
# Attributes
# Commands
# Config
# Controllers
# Core
# Domain
# Filters
# Infrastructure
# ModulesCheck Routes Are Scanning
# View all registered routes
docker-compose exec php php spark routes
# Output should include:
# GET / HomeController::index
# GET /admin/dashboard DashboardController::index
# GET /docs DocsController::index
# etc.Check Database Connection
# Run migrations (if not done)
docker-compose exec php php spark migrate
# Check tables
docker-compose exec mysql mysql -u root -p -e "SHOW TABLES;" ci4_databaseCheck Redis Connection
# Ping Redis
docker-compose exec redis redis-cli ping
# Output: PONG