Skip to content

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.

  • 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/

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

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

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

Purpose: Encapsulate single-responsibility business operations

Location: app/Domains/{Domain}/Actions/

Characteristics:

  • Single method (usually __invoke() or handle())
  • 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',
};
}
}

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

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

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);
}
}

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:

  1. Domain should represent a business concept - Not a technical layer
  2. Domain should have clear boundaries - Minimal coupling with other domains
  3. Domain should be cohesive - All code in domain relates to same concept