architecture for laravel

Architecture that stays
readable as you scale.

Lucid organizes Laravel applications into named, testable units—Features, Jobs, Operations, and Domains. Every piece of logic has a single canonical location, so your team always knows where to look and exactly what to change.

Features
Jobs
Operations
Domains
Services

the problem

Laravel is excellent.
But growth creates fog.

Without a structural convention, even the cleanest Laravel codebase becomes hard to navigate after a few months of team work. Logic drifts into controllers, models, and ad-hoc helpers—then stays there forever.

Controllers that keep growing

Validation, authorization, business logic, notifications, and redirects accumulate in controller methods. Reviewing a 400-line controller means reading every line before touching a thing.

Logic with no canonical home

Is it in the controller, the model, a request class, or a helper? There’s no convention, so every codebase differs. Searching for business logic becomes an archaeology project.

Onboarding takes months

Every project is a snowflake. New developers spend weeks learning unwritten conventions—the same questions surfacing for every new teammate who joins the team.

the solution

Every unit of logic
has a name and a home.

Lucid introduces a small vocabulary of named units that every team member learns once and applies everywhere. When you know the name of what you’re looking for, you know exactly where to find it.

Built on proven patterns—Command Bus for dispatching units of work, Domain-Driven Design for organizing them by topic, and Service-Oriented Architecture for separating application areas—Lucid makes these patterns concrete and approachable for any Laravel developer, without requiring deep expertise to apply them.

Feature

One class per user-facing action. Thin, readable, and composed of jobs.

PublishArticleFeature

Job

Single-responsibility. Reusable across features and operations.

SaveArticleJob

Operation

A reusable workflow that composes jobs from multiple domains.

NotifySubscribersOperation

Domain

Topic-based grouping of jobs, models, and exceptions.

app/Domains/Article

Service

An application area (API, Web, Forum) with its own features and routes.

app/Services/Api

before & after

See what changes

The controller stays permanently thin. All business logic lives in a Feature, composed from small, focused, independently testable Jobs.

ArticleController.php before
public function store(Request $request)
{
    // Validate input
    $request->validate([
        'title' => 'required|max:255',
        'body'  => 'required|min:100',
    ]);

    // Build a unique slug
    $slug = Str::slug($request->title);
    if (Article::where('slug', $slug)->exists()) {
        $slug .= '-' . time();
    }

    // Persist the article
    $article = Article::create([
        'user_id' => Auth::id(),
        'title'   => $request->title,
        'slug'    => $slug,
        'body'    => $request->body,
    ]);

    // Notify subscribers
    Notification::send(
        User::whereHas('subscriptions',
            fn($q) => $q->where('author_id', Auth::id()))->get(),
        new NewArticleNotification($article)
    );

    // Bust article cache
    Cache::tags(['articles'])->flush();

    return redirect()->route(
        'articles.show', $article
    );
}
ArticleController.php after
public function store(Request $request)
{
    return $this->serve(PublishArticleFeature::class);
}
PublishArticleFeature.php Feature
public function handle(PublishArticle $request)
{
    $article = $this->run(SaveArticleJob::class, [
        'title' => $request->title,
        'body'  => $request->body,
        'user'  => Auth::user(),
    ]);

    $this->run(NotifySubscribersOperation::class, [
        'authorId' => Auth::id(),
    ]);

    return $this->run(RedirectToArticleJob::class, [
        'article' => $article,
    ]);
}

overhead reduced

Less friction. More flow.

Structure is not overhead—lack of structure is. Lucid pays for itself in the first code review.

Faster code reviews

Features read like plain-English specifications. Reviewers understand intent from the Feature class, then drill into individual Jobs only when they need detail—instead of reading 400 lines end-to-end.

Shorter onboarding

New team members learn Lucid's vocabulary once from the documentation, then apply it everywhere. No unwritten, project-specific conventions to discover over weeks of stumbling.

Instant navigation

Looking for the code that saves a user? It’s in Domains/User/Jobs/SaveUserJob. Always. The convention eliminates the mental cost of hunting for code in unfamiliar directories.

Contained blast radius

Jobs are isolated by design. When you change a job, you know exactly where it’s called. Tests are scoped to individual units, so failures point precisely at what broke.

Maximum reuse

Jobs live in domains, not inside features. SaveUserJob is called from any feature that needs to save a user. Write it once, test it once, use it everywhere.

Scales without rewriting

Start with Micro for a single-purpose API. Expand to Monolith with multiple services as requirements grow. The structure adapts—no rewrite required.

Ready to build
with clarity?

The documentation walks you through setting up Lucid in a new or existing Laravel project in minutes. Micro or Monolith—your choice.