Abstracting Exception propagation – Avoid throwing Model Layer SqlExceptions directly to Service layer.

Around 2 years ago when I started my career as a Java developer, one of the first things I learnt was the MVC design approach for developing a Web application. Without going into too much detail about the design paradigm, the basic idea here is to split an Application into 3 parts which is in line with the Separation of concerns design principle.

  1. View (User Interface) :: This for the most part is a templating engine like JSP
  2. Service (Business logic)  :: The core layer responsible for most of the processing.
  3. Model (DataStore) :: The component responsible for storing data.

Lets take a simple example of a User Login Module. The user enters his username and password in a JSP/Html webpage (View) and hits a ‘login’ button. If the name and password he entered are correct, the user is logged in and is redirected to a Home/Profile equivalent page. To do that, the Service layer fetches the credentials the user entered in the web page and compares it with the account details he provided during registration which are stored in a database.

MVC Process

Since nearly 99.99% of the time the Model is a type of a database, people (especially freshers) get so used to visualizing the Model as a database, that they start throwing SqlExceptions directly to the caller, which is the Service Layer. This is a very bad move as it defeats the entire modularity and goes against the MVC paradigm.

Notice how the below Service layer class LoginService has ModelLayer specific sql handling code.

The Service

package com.kartikiyer.service;

import java.sql.SQLException;
import com.kartikiyer.model.UserDao;

public class LoginService
{
    private void validateUser()
    {
        try
        {
            new UserDao().authenticateUser("username", "password");
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
    }
}

The Model

package com.kartikiyer.model;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class UserDao
{
    public boolean authenticateUser(String username, String password)
            throws SQLException
    {
        Connection c = DriverManager.getConnection(" example connecton url");

        //do some authentications
        if authenticated,
            return true
        else
            return false
    }
}

 

The Problem

Let me make myself clear, throwing exceptions is not the culprit here. In fact, the service layer must be informed and exception should be thrown by the model layer to inform its caller that something went haywire and the operation could not be successfully completed. It’s the throwing of an implementation specific exception directly into another layer which should be avoided.

The Model represents ANY persistent storage. This includes Database, FileSystems, Cloud Services etc. Now if in future we altogether change our Storage Model to a cloud database provider like Google BigTable or a conventional local FileSystem, both of which throw IOException,

Now when the model layer throws SqlExceptions, the Service Layer has to include the handling code and it subsequently becomes tightly coupled with java.sql components. Thus any other persistent storage which is not a part of the JDBC family or which throws some other kind of exception becomes incompatible with Service layer. The only solution if you have ended up in this mess is to either

  1. Change the code in Service layer
    This will involve massive changes in the core part of the app and its bad because those changes will need testing and also changes to one layer should ideally not cause a cascading change in the other layers.
  2. Workarounds
    This involves making hokey pokey adjustments like catching the now incompatible Exception and wrapping it in SqlException which the Service is coupled to, to avoid solution 1.

The Solution

The solution here is simple, instead of directly throwing implementation specific exceptions like SqlException or IOException, we throw ApplicationSpecific custom exception & configure the ServiceLayer to catch that exception instead.

Let’s take an example of an ApplicationSpecific custom exception called as PersistenceException. All the exceptions which the model layer will generate & throw will be wrapped/encapsulated in this exception, which is configured to be handled by our ServiceLayer.

This may seem like an added over head, but by enabling this layer of abstraction between the two layers, we’ve made our code much more flexible. Now no matter what changes we make on the ModelLayer, our ServiceLayer is always catching & handling our PersistenceException.

The below updated code shows how the 2 layers now interact with each other via the abstraction we included.

The Updated Service

package com.kartikiyer.service;

import com.kartikiyer.exception.PersistenceException;
import com.kartikiyer.model.UserDao;

public class LoginService
{
    private void validateUser()
    {
        try
        {
            new UserDao().authenticateUser("username", "password");
        }
        catch (PersistenceException e)
        {
            e.printStackTrace();
        }
    }
}

The Model which now throws PersistenceException

package com.kartikiyer.model;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import com.kartikiyer.exception.PersistenceException;

public class UserDao
{
    public boolean authenticateUser(String username, String password) throws PersistenceException
    {
        try
        {
            Connection c = DriverManager.getConnection(" example connecton url");
        }
        catch (SQLException e)
        {
            throw new PersistenceException("Could not perform DataPersistence Operation", e);
        }
//do some authentications
        if authenticated,
            return true
        else
            return false
    }
}

The new abstraction layer – PersistenceException

package com.kartikiyer.exception;

public class PersistenceException extends Exception
{
    public PersistenceException(String errorMsg, Exception e)
    {
        super(errorMsg, e);
    }
}

The Advantage

Thus by abstracting the exception propagation, we make our app easy to maintain by decoupling the Service and Model layers. Though there is a slight overhead initially, over the long run in the application’s lifecycle, this gives us the flexibility to change the underlying implementation without creating an unnecessary cascading ripple effect.