DEV Community

Antony Oduor
Antony Oduor

Posted on

The 600-Line Controller That Changed How I Think About Code

refactoring for scalability

Recently, I kicked off work on a new application. While I cannot share all the details just yet, I can talk about how the project turned into a crash course in scalable software design—and how that journey ended up as a talk at the Kisumu Gophers Meetup.

The Stack (and the Problem)

The stack:
Laravel + React + Inertia.js

The challenge:
A 600-line controller packed with tangled business logic, database calls, and view rendering code—all in one place.

Disclaimer
Since I do not know yet what I am allowed to share from the project, I will use a very simplistic example (the boring posts from a blog. I could have gone the Todo way but we could always aim higher) but do not let that fool you—the example will still be valid.

Here is an example snippet from a controller:

// Inside PostsController.php
public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required',
        'content' => 'required',
        'image' => 'nullable|image',
    ]);

    $post = new Post();
    $post->title = $validated['title'];
    $post->content = $validated['content'];

    if ($request->hasFile('image')) {
        $path = $request->file('image')->store('images');
        $post->image_path = $path;
    }

    $post->user_id = auth()->id();
    $post->save();

    ActivityLogger::log("Post created by user " . auth()->user()->name);

    return redirect()->route('posts.index')->with('success', 'Post created!');
}
Enter fullscreen mode Exit fullscreen mode

It works. But it is doing way too much.

The controller handles validation, database interaction, file uploads, and logging—all responsibilities that should be separated.

Seeing the Problem Clearly

That controller was only the beginning, but it was a warning sign. If we kept building like this, the app would become a nightmare to maintain.

I had read about Domain-Driven Design (DDD) before, but honestly? I had not truly practiced it. Like many developers, I had leaned on the framework to handle most of the application structure. Laravel is powerful, but it does not stop you from writing tightly coupled spaghetti code.

That is when we hit pause. As a team, we asked: What if we treated Laravel as just a delivery mechanism? What if we moved the actual business logic into its own clean layer?

Spoiler: That decision changed everything.

Our Refactoring Strategy

We introduced a /src directory to contain our domain logic. Each domain had its own folder with services, actions, policies, and contracts. Controllers became light, focused, and boring—in the best way.

The cleaner and clearer separation of concerns:

// PostsController.php
public function store(CreatePostRequest $request)
{
    $this->postService->createPost($request->validated(), $request->file('image'));
    return redirect()->route('posts.index')->with('success', 'Post created!');
}
Enter fullscreen mode Exit fullscreen mode
// src/Posts/Services/PostService.php
public function createPost(array $data, ?UploadedFile $image = null): void
{
    $post = new Post([
        'title' => $data['title'],
        'content' => $data['content'],
        'user_id' => auth()->id(),
    ]);

    if ($image) {
        $post->image_path = $this->uploadService->handle($image);
    }

    $post->save();

    $this->activityLogger->log("Post created by user " . auth()->user()->name);
}
Enter fullscreen mode Exit fullscreen mode

Each class does one thing. That means:

  • Predictable, isolated changes
  • Easier debugging
  • Rapid, low-risk feature additions

Need to add file uploads for another domain? Reuse the upload service—no cross-domain contamination.

Borrowing from DDD (Without Going Full DDD)

We did not follow DDD by the book. There were no aggregates or value objects (yet). But we embraced the principles:

  • Clear separation between infrastructure and domain
  • Thin controllers
  • Explicit service layers

It was not just an academic exercise. The app instantly became easier to work with. There was a point where we realized we needed three more developers, and onboarding the new team members became significantly smoother.

Sharing the Journey

This experience left such an impression that I decided to share it with my local community—the Kisumu Gophers Meetup. Although it is a Go-focused group, my talk was not about PHP or Laravel. It was about architecture, mindset, and patterns that transcend languages.

I focused on the Repository Pattern as an entry point for scalable thinking. And to my surprise, many developers in the room had never encountered these ideas. What I assumed was common knowledge turned out to be eye-opening.

Here is a simplified example I shared:

interface PostRepositoryInterface
{
    public function create(array $data): Post;
}

class EloquentPostRepository implements PostRepositoryInterface
{
    public function create(array $data): Post
    {
        return Post::create($data);
    }
}
Enter fullscreen mode Exit fullscreen mode

Even abstracting the data layer like this helps you think in terms of contracts, not implementations—making your app more adaptable in the long run. Depending on an interface allows you the freedom to have different implementations that can be switched depending on the context.

Final Thoughts

This whole experience reminded me that real-world experience is often the best teacher. You can read all the books and blog posts you want—but it is when you are knee-deep in messy code, trying to make sense of it all, that the lessons really stick.

And the best part? Sharing those lessons helps the entire community grow.

Whether you are writing Laravel or Go, it pays to slow down, clean up, and think about the long-term health of your code. In this age of AI, it is easy to say I will let AI do the whole work. But at some point, the AI loses the context of an awfully long feature, and then you are left with a huge jumbled mess that you have to walk through.


💬 Have you had your own "refactor epiphany"? I would love to hear how you approach controller cleanup and scalable design in your stack.

Top comments (0)