0

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