Move beyond "Fat Controllers" and embrace Domain-Driven Design (DDD). Learn how to identify business boundaries and scale your Laravel SaaS architecture.
Previously in this course, we discussed the foundational patterns for building clean applications. In this lesson, we shift our perspective from the standard Model-View-Controller (MVC) pattern to Domain-Driven Design (DDD). This transition is the primary catalyst for scaling a high-traffic SaaS platform without drowning in technical debt.
In standard Laravel development, we often organize by technical role: app/Models, app/Http/Controllers, app/Jobs. This is fine for small projects, but as your SaaS grows, your UserController becomes a dumping ground for logic related to authentication, billing, team management, and notification preferences. You end up with "Fat Controllers" that are impossible to test in isolation.
MVC focuses on the request/response cycle. DDD focuses on the business problem.
In DDD, we organize by domain capability. Instead of a UserController that handles everything a user does, we define bounded contexts like Billing, Identity, and Collaboration. The code lives where the business logic resides, not where the framework tells us to put it.
Before refactoring your SaaS, you must identify your domains. A domain is a specific sphere of knowledge or activity. If you look at your User model and see methods like charge(), inviteTeamMember(), and generateReport(), you have a boundary violation.
To identify boundaries in your existing codebase, map your features to these categories:
Imagine a standard SubscriptionController. It currently handles Stripe webhooks, database updates, and email notifications.
The MVC "Fat" approach:
PHPpublic function store(Request $request) { $user = auth()->user(); #6A9955">// Logic for Stripe, Database, and Email mixed together $stripe = new StripeClient(config('services.stripe.key')); $stripe->subscriptions->create([...]); $user->update(['status' => 'active']); Mail::to($user)->send(new WelcomeEmail()); }
The DDD "Domain" approach:
We extract this into a Billing domain. We create an Action class that handles the domain logic, independent of the HTTP layer. As we explored in Architecting for Maintainability: Refactoring Laravel Controllers, isolating this logic allows us to reuse it in CLI commands or queued jobs without duplicating code.
PHP#6A9955">// app/Domains/Billing/Actions/CreateSubscriptionAction.php public function execute(User $user, array $data): Subscription { #6A9955">// Domain logic only $stripeSubscription = $this->stripeGateway->create($user, $data); return $user->subscriptions()->create([...]); }
By shifting the logic here, the controller becomes a thin transport layer. It only parses the request and calls the Domain Action.
app/Domains/{Name}/Actions rather than grouped by file type.Service class and leave your models empty. Your Eloquent models should still encapsulate state-related logic (e.g., isExpired()), while complex business processes belong in Actions or Services.Subscription but the business calls it a Membership, rename your classes to match the business language.Transitioning to DDD isn't about ditching Laravel's MVC; it's about evolving how you organize your application logic. By moving from technical groupings to business domains, you create a codebase that is easier to test, maintain, and scale. We've started this shift by identifying domain boundaries, a concept we'll deepen as we look at Implementing the Service Layer in Laravel for Maintainable Code.
Up next: Defining Bounded Contexts — we will map out subdomains and define clear boundaries for Users and Billing.
Master Action Classes to remove business logic from your controllers. Learn to build testable, single-purpose classes for a scalable Laravel architecture.
Read moreMaster advanced Eloquent scopes to encapsulate complex business logic, chain query filters, and maintain clean, expressive models in your Laravel SaaS platform.
Custom Middleware Development
Database Connection Pooling
Handling Large Data Exports
Security Header Configuration
Database Sharding Concepts
Real-time Data Synchronization
Database Deadlock Prevention
Managing Third-Party API Integrations