Architecture Layers
Understanding the layered architecture that enforces separation of concerns and maintainability across the ERP system.
Layer Overview
AlKareem ERP follows a Domain-Driven Design (DDD) approach with a clean, layered architecture. Each layer has a single responsibility and communicates only with adjacent layers, ensuring loose coupling and high cohesion.
| Layer | Location | Responsibility | Depends On |
|---|---|---|---|
| Routes | routes/{module}/ |
HTTP endpoints, middleware | Livewire |
| Livewire | app/Livewire/{Module}/ |
UI state, form validation | Services |
| Services | app/Services/{Module}/ |
Business logic, transactions | Repositories, ApprovalEngine |
| Repositories | app/Repositories/{Module}/ |
Data access, query building | Models |
| Models | app/Models/{Module}/ |
Schema, relationships, events | Events |
| Events | app/Events/{Module}/ |
Domain event definitions | — |
| Listeners | app/Listeners/{Module}/ |
Side effects (GL posting, stock) | Services |
High-Level Data Flow
flowchart LR
subgraph Presentation["🖥️ Presentation"]
R[Route]
L[Livewire Component]
end
subgraph Business["⚙️ Business Logic"]
S[Service]
AE[Approval Engine]
end
subgraph Data["💾 Data Access"]
RP[Repository]
M[Model]
end
subgraph Events["📡 Events"]
E[Domain Event]
LS[Listeners]
end
R --> L
L -->|"validate & delegate"| S
S -->|"check approval"| AE
S -->|"data operations"| RP
RP --> M
M -->|"fireEvent()"| E
E --> LS
LS -->|"GL, Stock, etc."| S
style Presentation fill:#0f172a,stroke:#38bdf8
style Business fill:#0f172a,stroke:#a78bfa
style Data fill:#0f172a,stroke:#34d399
style Events fill:#0f172a,stroke:#fb923c
Key Principle
Data flows downward through the layers (Route → Livewire → Service → Repository → Model), while events propagate outward to listeners. This ensures that side effects (like GL posting or stock updates) are decoupled from the main business logic.
Routes Layer
Responsibilities
- Define HTTP endpoints
- Apply authentication middleware
- Apply Casbin authorization
- Map to Livewire components
File Location
routes/{module}/*.php
Example: routes/inventory/transfers.php
Example: Transfer Routes
use App\Livewire\Inventory\Form\Transfers\{Index, Create, Edit};
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/transfers', Index::class)
->middleware('casbin:TRANSFERS,VIEW');
Route::get('/transfers/create', Create::class)
->middleware('casbin:TRANSFERS,CREATE');
Route::get('/transfers/{id}/edit', Edit::class)
->middleware('casbin:TRANSFERS,UPDATE');
});
Livewire Layer
Should Do
- UI state management
- Form validation (Laravel rules)
- User interaction handling
- Delegate to Services
- Display feedback (toasts)
Should NOT Do
- Business logic
- Direct database queries
- Event dispatching
- Complex calculations
- Direct Model queries
Example: Create Transfer Component
class Create extends Component
{
use Toast;
public $issue_date;
public $from_warehouse_id;
public $to_warehouse_id;
public $items = [];
protected TransferService $transferService;
public function boot(TransferService $transferService): void
{
$this->transferService = $transferService;
}
protected function rules(): array
{
return [
'issue_date' => 'required|date',
'from_warehouse_id' => 'required|exists:warehouses,id',
'to_warehouse_id' => 'required|different:from_warehouse_id',
'items' => 'required|array|min:1',
];
}
public function save(): void
{
$this->validate();
// ✅ Delegate to Service - no business logic here!
$this->transferService->create([
'issue_date' => $this->issue_date,
'from_warehouse_id' => $this->from_warehouse_id,
'to_warehouse_id' => $this->to_warehouse_id,
'items' => $this->items,
]);
$this->success('Transfer created successfully.');
$this->redirect('/transfers');
}
}
Service Layer
The Heart of Business Logic
Services are where all business logic lives. They coordinate between repositories, handle transactions, integrate with the approval workflow, and control when domain events are fired.
Transaction Management
Wrap operations in DB::transaction()
Approval Integration
Check and create approval requests
Number Generation
Generate document numbers
Example: Transfer Service with Approval
class TransferService
{
public function __construct(
protected TransferRepository $transferRepository,
protected ApprovalEngineService $approvalEngine
) {}
public function create(array $data): Transfer
{
return DB::transaction(function () use ($data) {
// Generate document number
$data['transfer_number'] = $this->generateTransferNumber();
// Create via repository
$transfer = $this->transferRepository->create($data);
// Check if approval is required
if ($setup = $this->approvalEngine->checkSetupExists('CREATE', Transfer::class)) {
// Approval required - create request, DON'T fire event
$this->approvalEngine->createRequest(
$setup->id,
'TRANSFER_CREATE_APPROVAL',
$transfer->id,
);
} else {
// No approval - auto-approve and fire event
$transfer->status = 'APPROVED';
$transfer->save();
$transfer->fireCreateEvent(); // ← Only fire if auto-approved!
}
return $transfer;
});
}
}
Repository Layer
Repositories abstract data access and provide a clean API for querying and persisting entities. They handle eager loading, filtering, pagination, and basic CRUD operations.
Example: Transfer Repository
class TransferRepository
{
public function paginate(array $filter = [], int $perPage = 15)
{
$query = Transfer::with(['fromWarehouse', 'toWarehouse', 'creator'])
->latest();
if (!empty($filter['search'])) {
$query->where('transfer_number', 'like', '%' . $filter['search'] . '%');
}
if (!empty($filter['status'])) {
$query->where('status', $filter['status']);
}
return $query->paginate($perPage);
}
public function find($id): ?Transfer
{
return Transfer::with('items')->find($id);
}
public function create(array $data): Transfer
{
return DB::transaction(function () use ($data) {
$transfer = Transfer::create($data);
if (isset($data['items'])) {
$transfer->items()->createMany($data['items']);
}
return $transfer;
});
}
}
Model Layer
Models define the database schema, relationships, and event firing methods. They implement the
ProvidesApprovalData interface when approval workflow support is needed.
Key Responsibilities
- Define $fillable & $casts
- Relationship definitions
- Event firing methods
- Accessors and mutators
Event Methods
fireCreateEvent()fireUpdateEvent()fireDeleteEvent()
Example: Transfer Model
class Transfer extends Model implements ProvidesApprovalData
{
public const STATUSES = [
'PENDING', 'APPROVED', 'IN_TRANSIT', 'RECEIVED',
'CANCELLED', 'REJECTED', 'AWAITING_UPDATE_APPROVAL',
];
protected $fillable = [
'transfer_number', 'from_warehouse_id', 'to_warehouse_id',
'issue_date', 'status', 'remarks', 'created_by',
];
// Relationships
public function fromWarehouse(): BelongsTo
{
return $this->belongsTo(Warehouse::class, 'from_warehouse_id');
}
public function items(): HasMany
{
return $this->hasMany(TransferItem::class);
}
// Event firing methods
public function fireCreateEvent(): void
{
$this->loadMissing('items');
event(new TransferCreated($this));
}
// Approval interface
public function getApprovalData(): array
{
$this->loadMissing('items');
return array_merge($this->toArray(), [
'items' => $this->items->toArray()
]);
}
}