Moving on from MVC: CQRS

Moving on from MVC: CQRS

Do you sometimes forget what your models model? Are your controllers getting out of control? Modern MVC frameworks like Ruby on Rails and Laravel have made it extremely easy to get full-fledged web applications production-ready at blazingly fast speeds. With the help of CRUD style resourceful controllers a small team, sometimes consisting of one, can launch a ReSTful web application complete with users, blog posts, comments, and administrative abilities within a matter of hours. It's a beautiful thing when it bears the weight of a project's scope, but we all know projects can quickly grow into codebases that become nightmares to expand and maintain without solid organization. Command Query Responsibility Segregation (CQRS) is one of the patterns we at Grok have used when our MVC based applications evolve into more sophisticated pieces of software.

CQRS is a simple, yet powerful design pattern you can use to keep your models and controllers (and views, if you like to abuse every part of the MVC acronym) dry. The main idea behind it is that all actions being done in those hundreds (thousands?) of lines of code in your controllers and models should be separated into Commands -- actions that write data, and Queries -- actions that read data. Don't ever mix the two, EVER. Doing so would be breaking the one and only rule of CQRS. Commands should always have a void return type, thus altering data but not returning anything, and Queries should always return some type of data without making any changes to the system.

Command Query Separation

There seems to be some debate (what a surprise) between proponents of CQRS over whether or not it requires the use of different models for writing and reading data. Separate models at this layer in your application can help begin to pave the way into other design patterns that further the versatility of your software such as Task-based UI, Event Sourcing, and Domain Objects.

Command Query Separation

Every layer of software adds to the complexity of development and upkeep though, so making use of the same model for reads/writes while still tasking different objects to command and query may be the most viable solution for certain projects:

Command Query Separation

Consider the following code you might see in a Laravel controller:

<?php
class UserController {
    public function index()
    {
        if ($user_preferences = Auth::user()->preferences) {
            $users = User::where('region', $user_preferences->region)
                         ->where('group', $user_preferences->group)
                         ->where('role', $user_preferences->role)
                         ->paginate(20);
        } else {
            $users = User::all()->paginate(20);
        }

        return View::make('users.index')->with('users', $users);
    }

    public function store()
    {
        $input = Input::all();

        $user = User::where('email', $input['email'])->first();

        // Check to see if email exists
        if ($user) {
            $message = 'Email already exists';
            return Redirect::back()->with('message', $message);
        }

        // Validate input
        $user_attributes = array_intersect($input, User::$rules);
        $validator = Validator::make($user, $user_attributes);

        if ($validator->fails()) {
            return Redirect::back()->withErrors($validator);
        }

        // Store User
        $user = new User;

        $user->first_name = $input['first_name'];
        $user->last_name  = $input['last_name'];
        $user->email      = $input['email'];
        $user->password   = Hash::make($input['password']);
        $user->region     = $input['region'];

        if (!$user->save()) {
            $message = 'Unknown Error Occurred';
            return Redirect::back()->with('message', $message);
        }

        return Redirect::action('Controller@index');
    }
}

There isn't a lot of logic being executed here. The index method queries with the User model for users with pagination and optional search preferences if a relational model for search preferences is found. The store method does a couple of basic validation steps and then attempts to create the new user with attributes set in the POST request.

It still amounts to over 50 lines of code! That may not seem like much of a problem to you, but it also does nothing to limit the potential growth of these methods over time. What happens when the user entity doubles in size?

Now lets look at the same controller using separate command and query services as injected dependencies:

<?php
use app\Users\UserCommandService;
use app\Users\UserQueryService;

class UserController {

    public function construct(UserCommandService $user_command_service, UserQueryService $user_query_service)
    {
        $this->user_command_service = $user_command_service;
        $this->user_query_service = $user_query_service;
    }

    public function index()
    {
        $users = $this->user_query_service->getAllUsersWithSearchPreferences();
        return View::make('users.index')->with('users', $users);
    }

    public function store()
    {
        $this->user_command_service->createUserCommand();
        return Redirect::action('Controller@index');
    }
}

Simply moving all logic from the controller to service classes separated by reading from and writing to our storage has trimmed the code down by more than half the amount of lines, and better yet, has helped define a single action being taken in each of our controller actions.

If you search the web for CQRS you will no doubt run into larger software design concepts like Domain Driven Design or Event Sourcing and quickly be tempted to jump into the wormhole of information covering them (or jump off the nearest ledge). Don't do either! At least for the time being. Stick to understanding the simple building block that is CQRS and you will improve your code dramatically.

Sources

Categories: Software Development | Tags: MVC, Object Oriented Programming, Domain Driven Design

Portrait photo for Joseph Villafranca Joseph Villafranca

Joseph is a product of Codeup's first graduating class. His upbringing in web development consisted mostly of LAMP stack technologies with a heavy dose of Javascript and HTML/CSS. He prefers the back-end side of software development and enjoys data design and application architecture way more than a 2nd year coder should. He also holds a BBA from Texas A&M University and loves being a part of the small business tech scene in San Antonio, TX.

Comments


LET US HELP YOU!

We provide a free consultation to discover competitive advantages for your business. Contact us today to schedule an appointment.