Domain-Driven Design
The starter loosely follows Domain-Driven Design (DDD) principles to organize code in a scalable, maintainable way. It is structured around business domains rather than technical layers.
Directory Structure
Section titled “Directory Structure”Directoryapp
DirectoryConsole
DirectoryCommands/
- …
DirectoryDomains
DirectoryAuth
DirectoryActions/
- …
DirectoryEnums/
- …
DirectoryHttp/
- …
DirectoryModels/
- …
DirectoryPolicies/
- …
DirectorySeeders/
- …
- …
DirectoryCore
DirectoryEnums/
- …
DirectoryModels/
- …
DirectoryServices/
- …
- …
DirectoryUser
DirectoryActions/
- …
DirectoryEnums/
- …
DirectoryModels/
- …
DirectoryPolicies/
- …
DirectoryQueryBuilders/
- …
- …
DirectoryFilament
DirectoryResources/
- …
- …
DirectoryHttp/
DirectoryControllers/
- …
DirectoryMiddleware/
- …
DirectoryRequests/
- …
DirectoryProviders/
- …
DirectoryView/
DirectoryComponents/
- …
Domain Organization
Section titled “Domain Organization”Core Domain
Section titled “Core Domain”The Core domain contains foundational functionality that applies across the entire application:
Location: app/Domains/Core/
Responsibilities:
- Base models and concerns
- Core enums (External Services, API Request Failures)
- Core services (DateTime formatting, Configuration validation)
- Shared utilities and helpers
When to add code to Core:
- Functionality needed by multiple domains
- Infrastructure concerns (logging, caching, etc.)
- Shared enums
- Base classes and traits
- Framework extensions
Auth Domain
Section titled “Auth Domain”The Auth domain handles everything related to authentication and authorization:
Location: app/Domains/Auth/
Responsibilities:
- Authentication (WebSSO, Access Tokens, local login challenges)
- Authorization (roles, permissions, scopes)
- Access token management and expiration
- Login code generation and validation
- Impersonation functionality
- API request logging
- Role and permission seeding
User Domain
Section titled “User Domain”The User domain handles everything related to user data and management:
Location: app/Domains/User/
Responsibilities:
- User management and profile data
- User segmentation and affiliation
- Directory integration (searching, syncing)
- Audit logging of user actions
- User login tracking
- Wildcard photo management
Component Types
Section titled “Component Types”Actions
Section titled “Actions”Purpose: Encapsulate single-responsibility business operations
Location: app/Domains/{Domain}/Actions/
Characteristics:
- Single method (usually
__invoke()orhandle()) - Focused on one specific operation
- Easily testable
- Can be injected into controllers or called from other actions
Example:
namespace App\Domains\User\Actions;
class DetermineUserSegment{ public function __invoke(User $user): UserSegmentEnum { // Business logic to determine user segment }}When to use Actions:
- Complex business logic that doesn’t belong in a model
- Operations that span multiple models
- Reusable operations called from multiple places
- Operations that need to be tested in isolation
Purpose: Define sets of related constants with behavior
Location: app/Domains/{Domain}/Enums/
Characteristics:
- Backed by strings or integers
- Can have methods for labels, descriptions, behavior
- Type-safe throughout the application
Example:
namespace App\Domains\Auth\Enums;
use Filament\Support\Contracts\HasLabel;
enum AuthTypeEnum: string implements HasLabel{ case SSO = 'sso'; case LOCAL = 'local'; case API = 'api';
public function getLabel(): string { return match ($this) { self::SSO => 'NetID', self::LOCAL => 'Verification Code', self::API => 'API', }; }}Models
Section titled “Models”Purpose: Represent database tables and their relationships
Location: app/Domains/{Domain}/Models/
Characteristics:
- Extend
BaseModel - Include relationships, scopes, and accessors
Base Model:
All models should extend BaseModel which provides:
- Automatic audit logging
- Shared scopes and utilities
Services
Section titled “Services”Purpose: Complex operations or integrations with external systems
Location: app/Domains/{Domain}/Services/
Characteristics:
- More complex than Actions
- Often stateful
- Handle external integrations
- May coordinate multiple Actions
When to use Services:
- External API integrations
- Complex calculations or transformations
- Operations requiring significant setup
Query Builders
Section titled “Query Builders”Purpose: Encapsulate complex queries and scopes
Location: app/Domains/{Domain}/QueryBuilders/
Characteristics:
- Extend Eloquent’s query builder
- Provide fluent, chainable query methods
- Keep query logic out of controllers or models
Example:
namespace App\Domains\User\QueryBuilders;
use App\Domains\Auth\Enums\AuthTypeEnum;use Illuminate\Database\Eloquent\Builder;
/** * @template TModel of User * * @extends Builder<TModel> */class UserBuilder extends Builder{ public function sso(): self { return $this->where('auth_type', AuthTypeEnum::SSO); }
public function whereEmailEquals(string $email): self { $normalized = strtolower(trim($email));
return $this->where('email', 'ilike', $normalized); }}Adding New Domains
Section titled “Adding New Domains”As your application grows, you may want to add new domains. For example, if building a course management system, you might add:
Directoryapp/Domains/Course/
DirectoryActions/
- EnrollStudent.php
- UnenrollStudent.php
DirectoryEnums/
- CourseStatusEnum.php
DirectoryModels/
- Course.php
- Enrollment.php
- Section.php
DirectoryServices/
- CourseRegistrationService.php
Guidelines for new domains:
- Domain should represent a business concept - Not a technical layer
- Domain should have clear boundaries - Minimal coupling with other domains
- Domain should be cohesive - All code in domain relates to same concept