File Structure & Monolithic Domain-Driven Design
Architecture: Modular Monolith with Domain-Driven Design
Principle: Each domain owns its directory, contracts, models, and services
Scale: 14 domains, ~500K lines of code, single PostgreSQL instance
📁 Root Directory Structure
loreax-core/
├── app/ # Application code (Laravel)
├── bootstrap/ # Framework bootstrap files
├── config/ # Configuration files
├── database/ # Migrations, factories, seeders
├── docs/ # Documentation
├── http/ # Bruno API testing files
├── public/ # Web root
├── resources/ # Views, CSS, JavaScript
├── routes/ # API/web routes
├── scripts/ # Utility scripts
├── storage/ # Logs, cache, session files
├── tests/ # Test suites
├── vendor/ # Composer dependencies
├── .env # Environment configuration (git-ignored)
├── .env.example # Environment template
├── .gitignore # Git ignore rules
├── .gitattributes # Git attributes
├── artisan # Laravel CLI
├── composer.json # PHP dependencies
├── composer.lock # Locked versions (git-tracked)
├── package.json # Node dependencies
├── package-lock.json # Locked versions (git-tracked)
├── phpunit.xml # PHPUnit configuration
├── phpstan.neon # Larastan configuration
├── pint.json # Pint linter configuration
├── rector.php # Rector refactoring rules
├── docker-compose.yml # Docker services
├── docker-compose.testing.yml # Docker for testing
├── AGENTS.md # 🤖 AI agent instructions
├── CLAUDE.md # 👨💻 Developer + Claude guide
├── IMPLEMENTATION_PLAN.md # ~180 implementation chunks
├── IMPLEMENTATION_GUIDE_MVP_2_GUIDE.md
├── LEDGER_VISUAL_GUIDE.md # Ledger system overview
├── PHASE_*.md # Phase-specific guides
├── README.md # Main documentation
└── vite.config.js # Vite asset bundler
🏗️ Application Directory (app/)
The heart of Loreax. One directory per domain (bounded context).
app/
├── Core/ # ⭐ Core Infrastructure (cross-cutting)
│ ├── README.md
│ ├── Contracts/
│ │ ├── IPlatformSettings.php
│ │ ├── IFeatureFlags.php
│ │ ├── ILedgerService.php
│ │ ├── IRequestLogger.php
│ │ └── ... (other service contracts)
│ │
│ ├── Authorization/
│ │ ├── Scope.php # Enum of all admin scopes
│ │ ├── ScopeGroup.php # Enum grouping scopes
│ │ └── Policies/ # Authorization policies
│ │
│ ├── Http/
│ │ ├── Controllers/ # Base controller
│ │ ├── Middleware/
│ │ │ ├── AssignRequestId.php
│ │ │ ├── AssignTraceId.php
│ │ │ ├── AssignConversationId.php
│ │ │ ├── RateLimitByType.php
│ │ │ └── ResolveRealm.php
│ │ ├── Requests/ # Form requests for validation
│ │ └── Responses/
│ │ └── ApiResponse.php # Response builder
│ │
│ ├── Infrastructure/
│ │ ├── Logging/
│ │ │ ├── RequestLogger.php # Logs all requests to MongoDB
│ │ │ └── RequestPayloadRedactor.php
│ │ ├── Storage/
│ │ │ └── StorageManager.php
│ │ ├── Media/
│ │ │ └── MediaProcessor.php
│ │ └── Payments/
│ │ └── PaymentProvider.php
│ │
│ ├── Exceptions/
│ │ ├── DomainException.php # Base for all domain exceptions
│ │ ├── ErrorCode.php # Enum of error codes
│ │ └── ExceptionResponseRenderer.php
│ │
│ ├── Support/
│ │ ├── Enums/
│ │ │ └── LedgerAccount.php
│ │ ├── ValueObjects/
│ │ │ └── Money.php
│ │ └── Helpers/
│ │
│ ├── Services/
│ │ ├── PlatformSettingsService.php # Platform config
│ │ ├── FeatureFlagsService.php # Feature toggles
│ │ └── LedgerService.php # Ledger posting
│ │
│ └── Providers/
│ ├── CoreServiceProvider.php
│ └── RouteServiceProvider.php
│
│
├── Identity/ # 👤 User & Authentication
│ ├── README.md
│ ├── Models/
│ │ ├── User.php # User model, realms (user/admin)
│ │ ├── AdminUser.php # Admin-specific model
│ │ └── MfaChallenge.php
│ │
│ ├── Contracts/
│ │ ├── IMfaProvider.php # MFA interface
│ │ └── IIdentityService.php
│ │
│ ├── Actions/
│ │ ├── RegisterUserAction.php
│ │ ├── LoginUserAction.php
│ │ ├── EnableMfaAction.php
│ │ └── ConfirmMfaAction.php
│ │
│ ├── Services/
│ │ ├── TotpMfaProvider.php # Google Authenticator (TOTP)
│ │ └── EmailOtpMfaProvider.php
│ │
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── RegisterController.php
│ │ │ ├── LoginController.php
│ │ │ └── MfaController.php
│ │ ├── Requests/
│ │ │ ├── RegisterRequest.php
│ │ │ └── LoginRequest.php
│ │ └── Resources/
│ │ └── UserResource.php
│ │
│ ├── Exceptions/
│ │ ├── EmailAlreadyRegisteredException.php
│ │ ├── InvalidCredentialsException.php
│ │ └── MfaCodeInvalidException.php
│ │
│ ├── Events/
│ │ ├── UserRegistered.php
│ │ ├── UserLoggedIn.php
│ │ └── MfaEnabled.php
│ │
│ ├── Listeners/
│ │ └── SendWelcomeEmail.php
│ │
│ └── Database/
│ └── factories/
│ └── UserFactory.php
│
│
├── Ledger/ # 💰 Double-Entry Ledger
│ ├── README.md
│ ├── Models/
│ │ ├── LedgerAccount.php
│ │ ├── LedgerTransaction.php
│ │ ├── LedgerEntry.php
│ │ └── Wallet.php
│ │
│ ├── Contracts/
│ │ ├── ILedgerService.php
│ │ └── IWalletService.php
│ │
│ ├── Enums/
│ │ ├── LedgerTransactionType.php
│ │ ├── LedgerAccountType.php
│ │ └── AccountHierarchy.php
│ │
│ ├── Services/
│ │ └── LedgerService.php # Core posting logic
│ │
│ ├── Actions/
│ │ ├── PostTransactionAction.php
│ │ └── ReconcileAccountsAction.php
│ │
│ └── Exceptions/
│ ├── LedgerImbalanceException.php
│ └── InvalidAccountException.php
│
│
├── Payments/ # 💸 MPESA & Withdrawals
│ ├── README.md
│ ├── Models/
│ │ ├── WithdrawalRequest.php
│ │ ├── WithdrawalSettlement.php
│ │ └── MpesaCallback.php
│ │
│ ├── Contracts/
│ │ ├── IPaymentProvider.php # MPESA interface
│ │ └── IWithdrawalService.php
│ │
│ ├── Enums/
│ │ ├── WithdrawalStatus.php
│ │ └── SettlementBatch.php
│ │
│ ├── Actions/
│ │ ├── RequestWithdrawalAction.php
│ │ ├── ApproveWithdrawalAction.php
│ │ └── ProcessPaymentAction.php
│ │
│ ├── Services/
│ │ ├── MpesaPaymentProvider.php # MPESA integration
│ │ └── WithdrawalService.php
│ │
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── WithdrawalController.php
│ │ │ └── CallbackController.php # MPESA callbacks
│ │ └── Requests/
│ │ └── WithdrawalRequest.php
│ │
│ └── Exceptions/
│ ├── InsufficientFundsException.php
│ └── PaymentGatewayException.php
│
│
├── Content/ # 📝 Posts & Media
│ ├── README.md
│ ├── Models/
│ │ ├── Post.php # Core post model
│ │ ├── Media.php # Attachments
│ │ ├── Poll.php # Poll posts
│ │ └── Livestream.php # Live streaming
│ │
│ ├── Enums/
│ │ ├── PostStatus.php # draft, published, archived
│ │ ├── PostType.php # text, image, video, audio, poll
│ │ └── MediaType.php
│ │
│ ├── Observers/
│ │ └── PostObserver.php # Hooks for post lifecycle
│ │
│ ├── Actions/
│ │ ├── CreatePostAction.php
│ │ ├── PublishPostAction.php
│ │ └── DeletePostAction.php
│ │
│ ├── Http/
│ │ ├── Controllers/
│ │ │ └── PostController.php
│ │ └── Resources/
│ │ └── PostResource.php
│ │
│ └── Exceptions/
│ └── PostNotFoundException.php
│
│
├── Access/ # 🔐 Access Rules & Purchases
│ ├── README.md
│ ├── Models/
│ │ ├── AccessRule.php # Gating rules (tier, purchase)
│ │ └── PostPurchase.php # Purchase record
│ │
│ ├── Contracts/
│ │ └── IAccessService.php # Visibility enforcement
│ │
│ ├── Enums/
│ │ └── AccessType.php # public, tier, purchase, exclusive
│ │
│ ├── Actions/
│ │ ├── CreateAccessRuleAction.php
│ │ └── PurchasePostAction.php
│ │
│ ├── Services/
│ │ └── AccessService.php # canView() logic
│ │
│ └── Exceptions/
│ ├── PostAlreadyPurchasedException.php
│ └── AccessDeniedException.php
│
│
├── Monetization/ # 💎 Tiers & Subscriptions
│ ├── README.md
│ ├── Models/
│ │ ├── SubscriptionTier.php
│ │ └── SubscriptionMember.php
│ │
│ ├── Actions/
│ │ ├── CreateTierAction.php
│ │ └── SubscribeTierAction.php
│ │
│ └── Services/
│ └── TierVerificationService.php
│
│
├── Discovery/ # 🔍 Timelines & Search
│ ├── README.md
│ ├── Contracts/
│ │ ├── IRankingService.php
│ │ └── ISearchService.php
│ │
│ ├── Services/
│ │ ├── TimelineService.php # Home, explore feeds
│ │ ├── SearchService.php # Full-text search
│ │ └── RankingService.php # Ranking algorithm
│ │
│ ├── Http/
│ │ ├── Controllers/
│ │ │ └── DiscoveryController.php
│ │ └── Resources/
│ │ └── DiscoveryResultResource.php
│ │
│ └── Data/
│ └── DiscoveryResultData.php
│
│
├── Social/ # 👥 Follows, Likes, Shares
│ ├── README.md
│ ├── Models/
│ │ ├── Follow.php
│ │ ├── Like.php
│ │ └── Share.php
│ │
│ ├── Actions/
│ │ ├── FollowCreatorAction.php
│ │ └── LikePostAction.php
│ │
│ └── Http/
│ ├── Controllers/
│ └── Resources/
│
│
├── FanClub/ # 🎪 Creator Communities
│ ├── README.md
│ ├── Models/
│ │ ├── FanClub.php
│ │ └── FanClubMember.php
│ │
│ ├── Actions/
│ │ ├── CreateFanClubAction.php
│ │ └── JoinFanClubAction.php
│ │
│ └── Http/
│ ├── Controllers/
│ └── Resources/
│
│
├── Moderation/ # 🛡️ Content Moderation
│ ├── README.md
│ ├── Models/
│ │ ├── Report.php
│ │ ├── Violation.php
│ │ └── ModerationQueue.php
│ │
│ ├── Contracts/
│ │ └── IModerationService.php
│ │
│ ├── Services/
│ │ └── AiPrescreenService.php # AI content filtering
│ │
│ └── Actions/
│ ├── ReportContentAction.php
│ └── SuspendAccountAction.php
│
│
├── Promotions/ # 🎉 Referrals & Campaigns
│ ├── README.md
│ ├── Models/
│ │ ├── ReferralCode.php
│ │ └── ReferralReward.php
│ │
│ └── Actions/
│ └── GenerateReferralCodeAction.php
│
│
├── Notifications/ # 📬 Email & Push
│ ├── README.md
│ ├── Models/
│ │ └── NotificationLog.php
│ │
│ ├── Mails/
│ │ ├── WelcomeEmail.php
│ │ └── PurchaseConfirmationEmail.php
│ │
│ ├── Notifications/
│ │ └── OrderShipped.php
│ │
│ └── Actions/
│ └── SendNotificationAction.php
│
│
└── AdminBackoffice/ # 👨💼 Admin Panel (Filament)
├── README.md
└── Resources/
├── UserResource.php
├── PostResource.php
├── WithdrawalRequestResource.php
└── PlatformSettingsResource.php
🗂️ Supporting Directories
database/
database/
├── migrations/ # Schema changes
│ ├── 2026_01_01_000001_create_users_table.php
│ ├── 2026_01_01_000002_create_posts_table.php
│ └── ... (one file per major feature)
│
├── factories/
│ ├── Identity/
│ │ └── UserFactory.php
│ ├── Content/
│ │ └── PostFactory.php
│ └── ... (data generation for tests)
│
└── seeders/
├── DatabaseSeeder.php # Master seeder
├── IdentitySeeder.php # Create test users
└── ... (one per domain)
tests/
tests/
├── Unit/ # Pure logic, no DB/HTTP
│ ├── Core/
│ │ └── Exceptions/
│ ├── Identity/
│ │ └── Actions/
│ ├── Ledger/
│ │ └── Services/
│ └── ...
│
├── Feature/ # Full HTTP + DB
│ ├── Identity/
│ │ ├── LoginTest.php
│ │ └── MfaTest.php
│ ├── Payments/
│ │ └── WithdrawalTest.php
│ └── ...
│
├── Contract/ # Interface conformance
│ ├── Payments/
│ │ └── MpesaProviderContractTest.php
│ └── ...
│
├── Invariant/ # Financial integrity
│ ├── Ledger/
│ │ └── LedgerInvariantTest.php
│ └── ...
│
├── TestCase.php # Base test class
├── Support/
│ ├── Factories/ # Test data factories
│ └── Mocks/ # Mock implementations
│
└── Feature/
└── ... (HTTP tests organized by domain)
config/
config/
├── app.php # Application config
├── database.php # Database connections
├── cache.php # Cache stores
├── queue.php # Job queue
├── logging.php # Log channels
├── mail.php # Email service
├── filesystems.php # Storage disks
├── permission.php # Spatie permissions
├── platform_settings.php # Platform config defaults
├── rate_limits.php # Rate limit rules
├── request_logger.php # MongoDB logging config
├── services.php # Third-party APIs
├── roles.php # Admin role definitions
└── ... (one file per major subsystem)
routes/
routes/
├── api.php # API routes (/api/v1/...)
│ ├── POST /auth/register → RegisterUserController
│ ├── POST /auth/login → LoginUserController
│ ├── GET /timeline/home → DiscoveryController@home
│ ├── POST /posts → PostController@store
│ ├── POST /purchases → PurchaseController@store
│ └── ... (organized by domain)
│
├── web.php # Web routes (views, redirects)
└── console.php # Artisan commands
🎯 Domain Boundaries
Each domain is self-contained with:
- Models — Domain entities (e.g., User, Post, Transaction)
- Contracts — Interfaces defining public API (e.g.,
ILedgerService) - Actions — Business logic (e.g.,
PostTransactionAction) - Services — Implementations of contracts
- Exceptions — Domain-specific errors
- Events — Things that happened (e.g.,
UserRegistered) - HTTP Layer — Controllers, Requests, Resources
- Database — Migrations, factories, seeders
Domain Dependencies
Core (no dependencies)
├── Identity (depends on Core)
├── Ledger (depends on Core)
└── Payments (depends on Core, Ledger)
└── Access (depends on Core, Content, Ledger)
├── Monetization (depends on Core, Access)
└── Discovery (depends on Core, Content, Access, Social)
Rule: Higher-level domains depend on lower-level (Core), but NOT vice versa.
🎓 Key Patterns
Action Pattern
Every business operation = one Action class:
// app/Identity/Actions/RegisterUserAction.php
final class RegisterUserAction
{
use AsAction;
/**
* @throws EmailAlreadyRegisteredException
*/
public function run(RegisterUserData $data): User
{
// Validation
if (User::where('email', $data->email)->exists()) {
throw new EmailAlreadyRegisteredException($data->email);
}
// Business logic in transaction
return DB::transaction(function () use ($data): User {
$user = User::create([
'email' => $data->email,
'password' => bcrypt($data->password),
]);
// Side effects after commit
DB::afterCommit(fn () => UserRegistered::dispatch($user));
return $user;
});
}
}
Controller Pattern
Thin I/O adapter:
// app/Identity/Http/Controllers/RegisterUserController.php
final class RegisterUserController extends Controller
{
public function __construct(
private readonly RegisterUserAction $registerUser,
) {}
#[OA\Post(...)]
public function __invoke(RegisterUserRequest $request): JsonResponse
{
$data = RegisterUserData::from($request->validated());
$user = $this->registerUser->run($data);
return UserResource::make($user)
->response()
->setStatusCode(201);
}
}
📝 Documentation Files
Each domain has its own documentation:
app/Domain/README.md # What the domain owns
docs/wikis/domain/README.md # How to use the domain
docs/wikis/04-DOMAINS/DOMAIN.md # Detailed engineering docs
🔗 Directory Symlinks & Aliases
To improve IDE navigation, you can set up aliases in phpstan.neon:
paths:
- app/
- tests/
services:
myService: App\Core\Services\MyService
parameters:
bootstrapFiles:
- bootstrap/app.php
📊 Directory Statistics
Total files: ~1,200
Lines of code: ~500,000
- Production code: ~350,000 (70%)
- Test code: ~150,000 (30%)
Database tables: ~50
Models: ~40
Actions: ~80
Services: ~60
Controllers: ~30
Test files: ~200
Largest domains:
1. Content ~20K lines
2. Discovery ~15K lines
3. Ledger ~12K lines
4. Payments ~10K lines
5. Identity ~8K lines
🎯 Naming Conventions
Classes
Models: Singular (User, Post, LedgerTransaction)
Services: Ends with Service (UserService, LedgerService)
Interfaces: I-prefixed (IUserService, ILedgerService)
Actions: Verb+Noun+Action (RegisterUserAction, PostTransactionAction)
Controllers: Singular+Controller (UserController, PostController)
Requests: Singular+Request (RegisterUserRequest, StorePostRequest)
Resources: Singular+Resource (UserResource, PostResource)
Exceptions: Descriptive+Exception (UserNotFoundException, InsufficientFundsException)
Events: Noun+Past tense (UserRegistered, PostPublished)
Jobs: Noun+Action (SendWelcomeEmail, ProcessMedia)
Policies: Noun+Policy (PostPolicy, WithdrawalPolicy)
Files
Migrations: YYYY_MM_DD_HHMMSS_description.php
Factories: ModelFactory.php
Seeders: DomainSeeder.php
Tests: FeatureTest.php or ActionTest.php
Routes: Defined in routes/api.php with comments
🚀 Adding a New Domain
-
Create directory:
app/NewDomain/ -
Add README:
app/NewDomain/README.md -
Create structure:
Models/— Eloquent modelsContracts/— InterfacesActions/— Business logicServices/— ImplementationsExceptions/— Domain exceptionsHttp/— Controllers, requests, resourcesEvents/— Domain eventsEnums/— Enums for domain concepts
-
Write tests:
tests/Unit/NewDomain/— Logic teststests/Feature/NewDomain/— HTTP tests
-
Register service provider (if needed):
- Add to
config/app.php→providers[]
- Add to
-
Document:
- Write domain README explaining purpose, entities, contracts
- Add wiki:
docs/wikis/04-DOMAINS/NEWDOMAIN.md
📞 Support
- Questions: Review domain README or wiki
- Structure issues: Check
AGENTS.md§9 - File location: Search existing domains for similar patterns
- Slack: #loreax-dev
Last Updated: April 25, 2026