Mvc – Is Model a better place to set HTTP status code

controllerhttp-responsemvc

In MVC usually the controller sets whatever needs to be sent back to the client/View, including HTTP status code, e.g.:

class Controller {

    public function get(Request req, Response resp) {

        if (this.model.get(req.getId()) {
            resp.setStatusCode(Response::HTTP_OK)

        } else {
            resp.setStatusCode(Response::HTTP_NOT_FOUND)
        }
    }
}

There are a few problems in doing it this way:

  • all controller methods become littered with if/else for setting different HTTP status code
  • I've encountered a problem where someone has a Model::createOrGet(), which just return Model. The controller doesn't know if one has been created or fetch from store.
  • setting HTTP status code code becomes duplicated between controllers.

Would it be better to make Model pass HTTP status code back to the controller, e.g.:

class Controller {

    public function get(Request req, Response resp) {
        cart := this.model.getOrCreate(req.getCustomerId()
        resp.setStatusCode(this.model.getStatusCode())

        if (cart) {
            return cart
        }
    }

    public function create(Request req, Response resp) {
        cart := this.model.getOrCreate(req.getCustomerId()
        resp.setStatusCode(this.model.getStatusCode())

        if (cart) {
            return cart
        }
    }

    public function put(Customer c, array data) {
        // this can result different success/error status codes
        this.model.processPutDataForCustomer(c, data)
        resp.setStatusCode(this.model.getStatusCode())
    }
}

Basically this.model sets the last status to the appropriate HTTP status code which can be one of the success, client error or server error codes and the controller can just get that status code.

Is this good or is there a better way for this common problem? Isn't it logical for web application to know HTTP status code deeper than the controller?

I'd really like to see example for multiple possible HTTP status code to be returned by the controller, something like a PUT request that can be one of "Bad Request", "Conflict", "Not Found", "Created" and "No Content".

Best Answer

The Controller is responsible for handling the HTTP request and knowing what to do. Setting the HTTP status code in the Controller is the right place to do this.

There are a few problems in doing it this way:

  • all controller methods become littered with if/else for setting different HTTP status code

That's where this code was meant to go.

  • I've encountered a problem where someone has a Model::createOrGet(), which just return Model. The controller doesn't know if one has been created or fetch from store.

If the Controller needs to know whether or not something was created, then the Controller shouldn't be calling Model::getOrCreate(...).

  • setting HTTP status code code becomes duplicated between controllers.

If the model set the HTTP status code then it's duplicated between models, so this doesn't really buy you anything either.

This next suggestion will largely depend on the framework you are using, but most frameworks default to a 200 OK response, leaving you to just deal with the negative cases (404 NOT FOUND, etc).

But again, this is precisely what the Controller is meant to do.

Yes, it seems like you are duplicating code, but you are not duplicating business logic. That's the kind of duplication you want to avoid. Duplicating HTTP status codes isn't really what I'd call "duplication." It's part of the Controller's job.

imel96 commented:

I know setting some status code is Controller's job. But a lot of HTTP status codes are part of business logic and business logic should go in Model, right?

HTTP status codes are only a part of Business Logic if the application you are writing is a web server. If not, an HTTP status code is an artifact of your application being implemented as a web application. Imagine if you needed to take your business logic and reuse it in the context of an installed GUI application. Do HTTP status codes make sense then? They don't. An HTTP status code is not business logic. It is application flow control. The Controller is responsible for flow control.

Business Logic is Why and your Controller should know What to do when something happens. Controllers don't know Why something happens, because that's the responsibility of Business Logic.

I also find some Model throwing exceptions with HTTP status code in it and then Controller copy that to Response object. That also indicates to me that Model really wants to return HTTP status code, and without exception (like when using Go), it'd be just like returning status code.

If the model is throwing exceptions containing HTTP status codes, the model was built wrong. An exception is an exceptional condition that cannot be handled by the code being called, and so must be handled by the caller. Validations, as a separate entity from Models (although validators should be calling methods on Models while performing validation) and Controllers should take care of checking the data before exceptions can be thrown by the model or data access layer

When using many status codes, isn't it too much of business logic to be put in Controller?

No. Again, HTTP status codes have nothing to do with Business Logic. The very fact that you've asked this question should show you how confusing it becomes when you mix up two completely different concerns: Business Logic (Models) and Flow Control (Controllers).