God class / fat model — a class doing too many things (SRP violation)
Beginner5 min read·eng-16-008
interviewsolid
Concept
God class / Fat model — a class that has grown so large that it does too many things, knows too much, and depends on too many other parts of the system. A violation of the Single Responsibility Principle.
"God class": The name implies the class knows everything and controls everything — it IS the system. "God" because it has too much power.
"Fat model" (Laravel-specific): The Laravel community often talks about "fat controllers vs thin controllers" and "fat models." A fat model is an Eloquent model that accumulates business logic, relations, scopes, helper methods, event handlers, and API formatting — all in one file.
What accumulates in a god class:
- Business logic that belongs in services or use cases.
- Database queries that belong in repositories.
- Formatting/presentation logic that belongs in transformers or API resources.
- Validation that belongs in form requests.
- External API calls that belong in adapter classes.
Warning signs:
- Model file > 200-300 lines.
- Model has methods like
sendEmail(),calculateDiscount(),notifySlack(). - Every time something changes in the system, this class needs to change.
- Hard to test: instantiating the class requires mocking 10 dependencies.
Solutions:
- Service classes: Move business logic to
OrderService,PaymentService. - Action classes: One class per use case.
PlaceOrderAction,CancelOrderAction. - Traits: Extract related groups of methods if the model is the right home for them.
- Repository pattern: Move query logic out.
- API Resources / Transformers: Move response formatting out.
Code Example
php
<?php
// ❌ GOD CLASS / FAT MODEL — User.php with too many responsibilities
class User extends \Illuminate\Database\Eloquent\Model
{
// Relations (OK)
public function orders() { return $this->hasMany(Order::class); }
// ❌ Business logic (belongs in UserService or action)
public function upgradeToPremiun(string $planId): void
{
$this->subscription_plan = $planId;
$this->subscription_started_at = now();
$this->save();
Mail::to($this)->send(new WelcomePremiumMail($this)); // side effect!
Slack::notify('New premium user: ' . $this->email); // side effect!
Cache::tags(['users'])->flush(); // side effect!
}
// ❌ External API call (belongs in an adapter)
public function syncWithCRM(): array
{
return Http::post('https://crm.example.com/users', $this->toArray())->json();
}
// ❌ Complex query (belongs in UserRepository or scope)
public static function getTopSpendersForMonth(Carbon $month): Collection
{
return static::join('orders', 'users.id', '=', 'orders.user_id')
->whereMonth('orders.created_at', $month)
->selectRaw('users.*, SUM(orders.total) as spend')
->groupBy('users.id')
->orderByDesc('spend')
->limit(10)
->get();
}
// 400 more lines...
}
// ✅ THIN MODEL — model contains only model concerns
class User extends \Illuminate\Database\Eloquent\Model
{
protected $fillable = ['name', 'email', 'subscription_plan'];
protected $casts = ['subscription_started_at' => 'datetime'];
public function orders(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Order::class);
}
public function isPremium(): bool { return $this->subscription_plan !== null; }
}
// ✅ SERVICE — business logic extracted
class UpgradeUserService
{
public function __construct(
private readonly SlackNotifier $slack,
private readonly CrmAdapter $crm,
) {}
public function upgrade(User $user, string $planId): void
{
$user->update(['subscription_plan' => $planId, 'subscription_started_at' => now()]);
Mail::to($user)->send(new WelcomePremiumMail($user));
$this->slack->notify('New premium user: ' . $user->email);
Cache::tags(['users'])->flush();
}
}
// ✅ REPOSITORY — query logic extracted
class UserRepository
{
public function topSpendersForMonth(\Carbon\Carbon $month): \Illuminate\Support\Collection
{
return User::join('orders', 'users.id', '=', 'orders.user_id')
->whereMonth('orders.created_at', $month)
->selectRaw('users.*, SUM(orders.total) as spend')
->groupBy('users.id')
->orderByDesc('spend')
->limit(10)
->get();
}
}